summaryrefslogtreecommitdiffstats
path: root/packages/SystemUI/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/SystemUI/src')
-rwxr-xr-xpackages/SystemUI/src/com/android/systemui/BatteryMeterView.java60
-rw-r--r--packages/SystemUI/src/com/android/systemui/ExpandHelper.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/ImageWallpaper.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/SearchPanelCircleView.java592
-rw-r--r--packages/SystemUI/src/com/android/systemui/SearchPanelView.java345
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIApplication.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/assist/AssistGestureManager.java292
-rw-r--r--packages/SystemUI/src/com/android/systemui/assist/AssistOrbContainer.java155
-rw-r--r--packages/SystemUI/src/com/android/systemui/assist/AssistOrbView.java285
-rw-r--r--packages/SystemUI/src/com/android/systemui/egg/LLandActivity.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java53
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/DataUsageGraph.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFooter.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanel.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTileView.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java38
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/DataUsageDetailView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java220
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java39
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/recent/ColorDrawableWithDimensions.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/recent/Constants.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/recent/FadedEdgeDrawHelper.java193
-rw-r--r--packages/SystemUI/src/com/android/systemui/recent/FirstFrameAnimatorHelper.java134
-rw-r--r--packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java586
-rw-r--r--packages/SystemUI/src/com/android/systemui/recent/Recents.java326
-rw-r--r--packages/SystemUI/src/com/android/systemui/recent/RecentsActivity.java248
-rw-r--r--packages/SystemUI/src/com/android/systemui/recent/RecentsCallback.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java391
-rw-r--r--packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java813
-rw-r--r--packages/SystemUI/src/com/android/systemui/recent/RecentsPreloadReceiver.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java401
-rw-r--r--packages/SystemUI/src/com/android/systemui/recent/TaskDescription.java98
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/Constants.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/Recents.java (renamed from packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java)193
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java97
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHostView.java59
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java53
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java240
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsUserEventProxyReceiver.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java (renamed from packages/SystemUI/src/com/android/systemui/recent/ScreenPinningRequest.java)5
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java73
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java190
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java82
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/Task.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java86
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/FakeShadowDrawable.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java314
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewLayoutAlgorithm.java59
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java408
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewFilterAlgorithm.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java80
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java43
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java78
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedButton.java48
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedImageView.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java450
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java41
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/DelegateViewHelper.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/DismissViewButton.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java407
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java108
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowIconsView.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ServiceMonitor.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java59
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java238
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java176
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java137
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java1039
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java39
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java417
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/Ticker.java305
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/TickerView.java42
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/TrustDrawable.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/VelocityTrackerFactory.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java185
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java536
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothUtil.java234
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java264
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java275
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java68
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java62
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherScrim.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java495
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java1071
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/PreviewInflater.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java191
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java323
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java208
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java406
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java242
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java87
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java304
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java204
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/StackViewState.java86
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java49
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java38
-rw-r--r--packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/D.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPanel.java)10
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/Events.java190
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java378
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/Prefs.java69
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/SafetyWarningDialog.java109
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/SegmentedButtons.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/SpTexts.java78
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/Util.java167
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java1069
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java120
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java988
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java226
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumePanelComponent.java184
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java291
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java221
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java90
162 files changed, 11331 insertions, 9824 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index 7bdbd0a..292c9c2 100755
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -16,6 +16,7 @@
package com.android.systemui;
+import android.animation.ArgbEvaluator;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -23,8 +24,11 @@ import android.content.IntentFilter;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.os.BatteryManager;
@@ -56,12 +60,13 @@ public class BatteryMeterView extends View implements DemoMode,
private float mSubpixelSmoothingRight;
private final Paint mFramePaint, mBatteryPaint, mWarningTextPaint, mTextPaint, mBoltPaint;
private float mTextHeight, mWarningTextHeight;
+ private int mIconTint = Color.WHITE;
private int mHeight;
private int mWidth;
private String mWarningString;
private final int mCriticalLevel;
- private final int mChargeColor;
+ private int mChargeColor;
private final float[] mBoltPoints;
private final Path mBoltPath = new Path();
@@ -76,6 +81,12 @@ public class BatteryMeterView extends View implements DemoMode,
private BatteryController mBatteryController;
private boolean mPowerSaveEnabled;
+ private int mDarkModeBackgroundColor;
+ private int mDarkModeFillColor;
+
+ private int mLightModeBackgroundColor;
+ private int mLightModeFillColor;
+
private class BatteryTracker extends BroadcastReceiver {
public static final int UNKNOWN_LEVEL = -1;
@@ -189,7 +200,7 @@ public class BatteryMeterView extends View implements DemoMode,
TypedArray atts = context.obtainStyledAttributes(attrs, R.styleable.BatteryMeterView,
defStyle, 0);
final int frameColor = atts.getColor(R.styleable.BatteryMeterView_frameColor,
- res.getColor(R.color.batterymeter_frame_color));
+ context.getColor(R.color.batterymeter_frame_color));
TypedArray levels = res.obtainTypedArray(R.array.batterymeter_color_levels);
TypedArray colors = res.obtainTypedArray(R.array.batterymeter_color_values);
@@ -236,11 +247,18 @@ public class BatteryMeterView extends View implements DemoMode,
mWarningTextPaint.setTypeface(font);
mWarningTextPaint.setTextAlign(Paint.Align.CENTER);
- mChargeColor = getResources().getColor(R.color.batterymeter_charge_color);
+ mChargeColor = context.getColor(R.color.batterymeter_charge_color);
mBoltPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mBoltPaint.setColor(res.getColor(R.color.batterymeter_bolt_color));
+ mBoltPaint.setColor(context.getColor(R.color.batterymeter_bolt_color));
mBoltPoints = loadBoltPoints(res);
+
+ mDarkModeBackgroundColor =
+ context.getColor(R.color.dark_mode_icon_color_dual_tone_background);
+ mDarkModeFillColor = context.getColor(R.color.dark_mode_icon_color_dual_tone_fill);
+ mLightModeBackgroundColor =
+ context.getColor(R.color.light_mode_icon_color_dual_tone_background);
+ mLightModeFillColor = context.getColor(R.color.light_mode_icon_color_dual_tone_fill);
}
public void setBatteryController(BatteryController batteryController) {
@@ -292,11 +310,43 @@ public class BatteryMeterView extends View implements DemoMode,
for (int i=0; i<mColors.length; i+=2) {
thresh = mColors[i];
color = mColors[i+1];
- if (percent <= thresh) return color;
+ if (percent <= thresh) {
+
+ // Respect tinting for "normal" level
+ if (i == mColors.length-2) {
+ return mIconTint;
+ } else {
+ return color;
+ }
+ }
}
return color;
}
+ public void setDarkIntensity(float darkIntensity) {
+ int backgroundColor = getBackgroundColor(darkIntensity);
+ int fillColor = getFillColor(darkIntensity);
+ mIconTint = fillColor;
+ mFramePaint.setColor(backgroundColor);
+ mBoltPaint.setColor(backgroundColor);
+ mChargeColor = fillColor;
+ invalidate();
+ }
+
+ private int getBackgroundColor(float darkIntensity) {
+ return getColorForDarkIntensity(
+ darkIntensity, mLightModeBackgroundColor, mDarkModeBackgroundColor);
+ }
+
+ private int getFillColor(float darkIntensity) {
+ return getColorForDarkIntensity(
+ darkIntensity, mLightModeFillColor, mDarkModeFillColor);
+ }
+
+ private int getColorForDarkIntensity(float darkIntensity, int lightColor, int darkColor) {
+ return (int) ArgbEvaluator.getInstance().evaluate(darkIntensity, lightColor, darkColor);
+ }
+
@Override
public void draw(Canvas c) {
BatteryTracker tracker = mDemoMode ? mDemoTracker : mTracker;
diff --git a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
index d42ac61..bc7f745 100644
--- a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
@@ -22,7 +22,6 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.media.AudioAttributes;
-import android.media.AudioManager;
import android.os.Vibrator;
import android.util.Log;
import android.view.Gravity;
@@ -147,14 +146,14 @@ public class ExpandHelper implements Gefingerpoken {
}
public void setHeight(float h) {
if (DEBUG_SCALE) Log.v(TAG, "SetHeight: setting to " + h);
- mView.setActualHeight((int) h);
+ mView.setContentHeight((int) h);
mCurrentHeight = h;
}
public float getHeight() {
- return mView.getActualHeight();
+ return mView.getContentHeight();
}
public int getNaturalHeight(int maximum) {
- return Math.min(maximum, mView.getMaxHeight());
+ return Math.min(maximum, mView.getMaxContentHeight());
}
}
@@ -387,7 +386,8 @@ public class ExpandHelper implements Gefingerpoken {
}
private boolean isFullyExpanded(ExpandableView underFocus) {
- return underFocus.getIntrinsicHeight() == underFocus.getMaxHeight();
+ return underFocus.areChildrenExpanded() || underFocus.getIntrinsicHeight()
+ - underFocus.getBottomDecorHeight() == underFocus.getMaxContentHeight();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index 7c725b3..6888d0e 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -21,10 +21,8 @@ import static javax.microedition.khronos.egl.EGL10.*;
import android.app.ActivityManager;
import android.app.WallpaperManager;
-import android.content.BroadcastReceiver;
import android.content.ComponentCallbacks2;
import android.content.Context;
-import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Point;
diff --git a/packages/SystemUI/src/com/android/systemui/SearchPanelCircleView.java b/packages/SystemUI/src/com/android/systemui/SearchPanelCircleView.java
deleted file mode 100644
index d8fb6da..0000000
--- a/packages/SystemUI/src/com/android/systemui/SearchPanelCircleView.java
+++ /dev/null
@@ -1,592 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.PropertyValuesHolder;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Outline;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewOutlineProvider;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
-import android.view.animation.LinearInterpolator;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
-
-import java.util.ArrayList;
-
-public class SearchPanelCircleView extends FrameLayout {
-
- private final int mCircleMinSize;
- private final int mBaseMargin;
- private final int mStaticOffset;
- private final Paint mBackgroundPaint = new Paint();
- private final Paint mRipplePaint = new Paint();
- private final Rect mCircleRect = new Rect();
- private final Rect mStaticRect = new Rect();
- private final Interpolator mFastOutSlowInInterpolator;
- private final Interpolator mAppearInterpolator;
- private final Interpolator mDisappearInterpolator;
-
- private boolean mClipToOutline;
- private final int mMaxElevation;
- private boolean mAnimatingOut;
- private float mOutlineAlpha;
- private float mOffset;
- private float mCircleSize;
- private boolean mHorizontal;
- private boolean mCircleHidden;
- private ImageView mLogo;
- private boolean mDraggedFarEnough;
- private boolean mOffsetAnimatingIn;
- private float mCircleAnimationEndValue;
- private ArrayList<Ripple> mRipples = new ArrayList<Ripple>();
-
- private ValueAnimator mOffsetAnimator;
- private ValueAnimator mCircleAnimator;
- private ValueAnimator mFadeOutAnimator;
- private ValueAnimator.AnimatorUpdateListener mCircleUpdateListener
- = new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- applyCircleSize((float) animation.getAnimatedValue());
- updateElevation();
- }
- };
- private AnimatorListenerAdapter mClearAnimatorListener = new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mCircleAnimator = null;
- }
- };
- private ValueAnimator.AnimatorUpdateListener mOffsetUpdateListener
- = new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- setOffset((float) animation.getAnimatedValue());
- }
- };
-
-
- public SearchPanelCircleView(Context context) {
- this(context, null);
- }
-
- public SearchPanelCircleView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public SearchPanelCircleView(Context context, AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public SearchPanelCircleView(Context context, AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- setOutlineProvider(new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- if (mCircleSize > 0.0f) {
- outline.setOval(mCircleRect);
- } else {
- outline.setEmpty();
- }
- outline.setAlpha(mOutlineAlpha);
- }
- });
- setWillNotDraw(false);
- mCircleMinSize = context.getResources().getDimensionPixelSize(
- R.dimen.search_panel_circle_size);
- mBaseMargin = context.getResources().getDimensionPixelSize(
- R.dimen.search_panel_circle_base_margin);
- mStaticOffset = context.getResources().getDimensionPixelSize(
- R.dimen.search_panel_circle_travel_distance);
- mMaxElevation = context.getResources().getDimensionPixelSize(
- R.dimen.search_panel_circle_elevation);
- mAppearInterpolator = AnimationUtils.loadInterpolator(mContext,
- android.R.interpolator.linear_out_slow_in);
- mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(mContext,
- android.R.interpolator.fast_out_slow_in);
- mDisappearInterpolator = AnimationUtils.loadInterpolator(mContext,
- android.R.interpolator.fast_out_linear_in);
- mBackgroundPaint.setAntiAlias(true);
- mBackgroundPaint.setColor(getResources().getColor(R.color.search_panel_circle_color));
- mRipplePaint.setColor(getResources().getColor(R.color.search_panel_ripple_color));
- mRipplePaint.setAntiAlias(true);
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- drawBackground(canvas);
- drawRipples(canvas);
- }
-
- private void drawRipples(Canvas canvas) {
- for (int i = 0; i < mRipples.size(); i++) {
- Ripple ripple = mRipples.get(i);
- ripple.draw(canvas);
- }
- }
-
- private void drawBackground(Canvas canvas) {
- canvas.drawCircle(mCircleRect.centerX(), mCircleRect.centerY(), mCircleSize / 2,
- mBackgroundPaint);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mLogo = (ImageView) findViewById(R.id.search_logo);
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- mLogo.layout(0, 0, mLogo.getMeasuredWidth(), mLogo.getMeasuredHeight());
- if (changed) {
- updateCircleRect(mStaticRect, mStaticOffset, true);
- }
- }
-
- public void setCircleSize(float circleSize) {
- setCircleSize(circleSize, false, null, 0, null);
- }
-
- public void setCircleSize(float circleSize, boolean animated, final Runnable endRunnable,
- int startDelay, Interpolator interpolator) {
- boolean isAnimating = mCircleAnimator != null;
- boolean animationPending = isAnimating && !mCircleAnimator.isRunning();
- boolean animatingOut = isAnimating && mCircleAnimationEndValue == 0;
- if (animated || animationPending || animatingOut) {
- if (isAnimating) {
- if (circleSize == mCircleAnimationEndValue) {
- return;
- }
- mCircleAnimator.cancel();
- }
- mCircleAnimator = ValueAnimator.ofFloat(mCircleSize, circleSize);
- mCircleAnimator.addUpdateListener(mCircleUpdateListener);
- mCircleAnimator.addListener(mClearAnimatorListener);
- mCircleAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (endRunnable != null) {
- endRunnable.run();
- }
- }
- });
- Interpolator desiredInterpolator = interpolator != null ? interpolator
- : circleSize == 0 ? mDisappearInterpolator : mAppearInterpolator;
- mCircleAnimator.setInterpolator(desiredInterpolator);
- mCircleAnimator.setDuration(300);
- mCircleAnimator.setStartDelay(startDelay);
- mCircleAnimator.start();
- mCircleAnimationEndValue = circleSize;
- } else {
- if (isAnimating) {
- float diff = circleSize - mCircleAnimationEndValue;
- PropertyValuesHolder[] values = mCircleAnimator.getValues();
- values[0].setFloatValues(diff, circleSize);
- mCircleAnimator.setCurrentPlayTime(mCircleAnimator.getCurrentPlayTime());
- mCircleAnimationEndValue = circleSize;
- } else {
- applyCircleSize(circleSize);
- updateElevation();
- }
- }
- }
-
- private void applyCircleSize(float circleSize) {
- mCircleSize = circleSize;
- updateLayout();
- }
-
- private void updateElevation() {
- float t = (mStaticOffset - mOffset) / (float) mStaticOffset;
- t = 1.0f - Math.max(t, 0.0f);
- float offset = t * mMaxElevation;
- setElevation(offset);
- }
-
- /**
- * Sets the offset to the edge of the screen. By default this not not animated.
- *
- * @param offset The offset to apply.
- */
- public void setOffset(float offset) {
- setOffset(offset, false, 0, null, null);
- }
-
- /**
- * Sets the offset to the edge of the screen.
- *
- * @param offset The offset to apply.
- * @param animate Whether an animation should be performed.
- * @param startDelay The desired start delay if animated.
- * @param interpolator The desired interpolator if animated. If null,
- * a default interpolator will be taken designed for appearing or
- * disappearing.
- * @param endRunnable The end runnable which should be executed when the animation is finished.
- */
- private void setOffset(float offset, boolean animate, int startDelay,
- Interpolator interpolator, final Runnable endRunnable) {
- if (!animate) {
- mOffset = offset;
- updateLayout();
- if (endRunnable != null) {
- endRunnable.run();
- }
- } else {
- if (mOffsetAnimator != null) {
- mOffsetAnimator.removeAllListeners();
- mOffsetAnimator.cancel();
- }
- mOffsetAnimator = ValueAnimator.ofFloat(mOffset, offset);
- mOffsetAnimator.addUpdateListener(mOffsetUpdateListener);
- mOffsetAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mOffsetAnimator = null;
- if (endRunnable != null) {
- endRunnable.run();
- }
- }
- });
- Interpolator desiredInterpolator = interpolator != null ?
- interpolator : offset == 0 ? mDisappearInterpolator : mAppearInterpolator;
- mOffsetAnimator.setInterpolator(desiredInterpolator);
- mOffsetAnimator.setStartDelay(startDelay);
- mOffsetAnimator.setDuration(300);
- mOffsetAnimator.start();
- mOffsetAnimatingIn = offset != 0;
- }
- }
-
- private void updateLayout() {
- updateCircleRect();
- updateLogo();
- invalidateOutline();
- invalidate();
- updateClipping();
- }
-
- private void updateClipping() {
- boolean clip = mCircleSize < mCircleMinSize || !mRipples.isEmpty();
- if (clip != mClipToOutline) {
- setClipToOutline(clip);
- mClipToOutline = clip;
- }
- }
-
- private void updateLogo() {
- boolean exitAnimationRunning = mFadeOutAnimator != null;
- Rect rect = exitAnimationRunning ? mCircleRect : mStaticRect;
- float translationX = (rect.left + rect.right) / 2.0f - mLogo.getWidth() / 2.0f;
- float translationY = (rect.top + rect.bottom) / 2.0f - mLogo.getHeight() / 2.0f;
- float t = (mStaticOffset - mOffset) / (float) mStaticOffset;
- if (!exitAnimationRunning) {
- if (mHorizontal) {
- translationX += t * mStaticOffset * 0.3f;
- } else {
- translationY += t * mStaticOffset * 0.3f;
- }
- float alpha = 1.0f-t;
- alpha = Math.max((alpha - 0.5f) * 2.0f, 0);
- mLogo.setAlpha(alpha);
- } else {
- translationY += (mOffset - mStaticOffset) / 2;
- }
- mLogo.setTranslationX(translationX);
- mLogo.setTranslationY(translationY);
- }
-
- private void updateCircleRect() {
- updateCircleRect(mCircleRect, mOffset, false);
- }
-
- private void updateCircleRect(Rect rect, float offset, boolean useStaticSize) {
- int left, top;
- float circleSize = useStaticSize ? mCircleMinSize : mCircleSize;
- if (mHorizontal) {
- left = (int) (getWidth() - circleSize / 2 - mBaseMargin - offset);
- top = (int) ((getHeight() - circleSize) / 2);
- } else {
- left = (int) (getWidth() - circleSize) / 2;
- top = (int) (getHeight() - circleSize / 2 - mBaseMargin - offset);
- }
- rect.set(left, top, (int) (left + circleSize), (int) (top + circleSize));
- }
-
- public void setHorizontal(boolean horizontal) {
- mHorizontal = horizontal;
- updateCircleRect(mStaticRect, mStaticOffset, true);
- updateLayout();
- }
-
- public void setDragDistance(float distance) {
- if (!mAnimatingOut && (!mCircleHidden || mDraggedFarEnough)) {
- float circleSize = mCircleMinSize + rubberband(distance);
- setCircleSize(circleSize);
- }
-
- }
-
- private float rubberband(float diff) {
- return (float) Math.pow(Math.abs(diff), 0.6f);
- }
-
- public void startAbortAnimation(Runnable endRunnable) {
- if (mAnimatingOut) {
- if (endRunnable != null) {
- endRunnable.run();
- }
- return;
- }
- setCircleSize(0, true, null, 0, null);
- setOffset(0, true, 0, null, endRunnable);
- mCircleHidden = true;
- }
-
- public void startEnterAnimation() {
- if (mAnimatingOut) {
- return;
- }
- applyCircleSize(0);
- setOffset(0);
- setCircleSize(mCircleMinSize, true, null, 50, null);
- setOffset(mStaticOffset, true, 50, null, null);
- mCircleHidden = false;
- }
-
-
- public void startExitAnimation(final Runnable endRunnable) {
- if (!mHorizontal) {
- float offset = getHeight() / 2.0f;
- setOffset(offset - mBaseMargin, true, 50, mFastOutSlowInInterpolator, null);
- float xMax = getWidth() / 2;
- float yMax = getHeight() / 2;
- float maxRadius = (float) Math.ceil(Math.hypot(xMax, yMax) * 2);
- setCircleSize(maxRadius, true, null, 50, mFastOutSlowInInterpolator);
- performExitFadeOutAnimation(50, 300, endRunnable);
- } else {
-
- // when in landscape, we don't wan't the animation as it interferes with the general
- // rotation animation to the homescreen.
- endRunnable.run();
- }
- }
-
- private void performExitFadeOutAnimation(int startDelay, int duration,
- final Runnable endRunnable) {
- mFadeOutAnimator = ValueAnimator.ofFloat(mBackgroundPaint.getAlpha() / 255.0f, 0.0f);
-
- // Linear since we are animating multiple values
- mFadeOutAnimator.setInterpolator(new LinearInterpolator());
- mFadeOutAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float animatedFraction = animation.getAnimatedFraction();
- float logoValue = animatedFraction > 0.5f ? 1.0f : animatedFraction / 0.5f;
- logoValue = PhoneStatusBar.ALPHA_OUT.getInterpolation(1.0f - logoValue);
- float backgroundValue = animatedFraction < 0.2f ? 0.0f :
- PhoneStatusBar.ALPHA_OUT.getInterpolation((animatedFraction - 0.2f) / 0.8f);
- backgroundValue = 1.0f - backgroundValue;
- mBackgroundPaint.setAlpha((int) (backgroundValue * 255));
- mOutlineAlpha = backgroundValue;
- mLogo.setAlpha(logoValue);
- invalidateOutline();
- invalidate();
- }
- });
- mFadeOutAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (endRunnable != null) {
- endRunnable.run();
- }
- mLogo.setAlpha(1.0f);
- mBackgroundPaint.setAlpha(255);
- mOutlineAlpha = 1.0f;
- mFadeOutAnimator = null;
- }
- });
- mFadeOutAnimator.setStartDelay(startDelay);
- mFadeOutAnimator.setDuration(duration);
- mFadeOutAnimator.start();
- }
-
- public void setDraggedFarEnough(boolean farEnough) {
- if (farEnough != mDraggedFarEnough) {
- if (farEnough) {
- if (mCircleHidden) {
- startEnterAnimation();
- }
- if (mOffsetAnimator == null) {
- addRipple();
- } else {
- postDelayed(new Runnable() {
- @Override
- public void run() {
- addRipple();
- }
- }, 100);
- }
- } else {
- startAbortAnimation(null);
- }
- mDraggedFarEnough = farEnough;
- }
-
- }
-
- private void addRipple() {
- if (mRipples.size() > 1) {
- // we only want 2 ripples at the time
- return;
- }
- float xInterpolation, yInterpolation;
- if (mHorizontal) {
- xInterpolation = 0.75f;
- yInterpolation = 0.5f;
- } else {
- xInterpolation = 0.5f;
- yInterpolation = 0.75f;
- }
- float circleCenterX = mStaticRect.left * (1.0f - xInterpolation)
- + mStaticRect.right * xInterpolation;
- float circleCenterY = mStaticRect.top * (1.0f - yInterpolation)
- + mStaticRect.bottom * yInterpolation;
- float radius = Math.max(mCircleSize, mCircleMinSize * 1.25f) * 0.75f;
- Ripple ripple = new Ripple(circleCenterX, circleCenterY, radius);
- ripple.start();
- }
-
- public void reset() {
- mDraggedFarEnough = false;
- mAnimatingOut = false;
- mCircleHidden = true;
- mClipToOutline = false;
- if (mFadeOutAnimator != null) {
- mFadeOutAnimator.cancel();
- }
- mBackgroundPaint.setAlpha(255);
- mOutlineAlpha = 1.0f;
- }
-
- /**
- * Check if an animation is currently running
- *
- * @param enterAnimation Is the animating queried the enter animation.
- */
- public boolean isAnimationRunning(boolean enterAnimation) {
- return mOffsetAnimator != null && (enterAnimation == mOffsetAnimatingIn);
- }
-
- public void performOnAnimationFinished(final Runnable runnable) {
- if (mOffsetAnimator != null) {
- mOffsetAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (runnable != null) {
- runnable.run();
- }
- }
- });
- } else {
- if (runnable != null) {
- runnable.run();
- }
- }
- }
-
- public void setAnimatingOut(boolean animatingOut) {
- mAnimatingOut = animatingOut;
- }
-
- /**
- * @return Whether the circle is currently launching to the search activity or aborting the
- * interaction
- */
- public boolean isAnimatingOut() {
- return mAnimatingOut;
- }
-
- @Override
- public boolean hasOverlappingRendering() {
- // not really true but it's ok during an animation, as it's never permanent
- return false;
- }
-
- private class Ripple {
- float x;
- float y;
- float radius;
- float endRadius;
- float alpha;
-
- Ripple(float x, float y, float endRadius) {
- this.x = x;
- this.y = y;
- this.endRadius = endRadius;
- }
-
- void start() {
- ValueAnimator animator = ValueAnimator.ofFloat(0.0f, 1.0f);
-
- // Linear since we are animating multiple values
- animator.setInterpolator(new LinearInterpolator());
- animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- alpha = 1.0f - animation.getAnimatedFraction();
- alpha = mDisappearInterpolator.getInterpolation(alpha);
- radius = mAppearInterpolator.getInterpolation(animation.getAnimatedFraction());
- radius *= endRadius;
- invalidate();
- }
- });
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mRipples.remove(Ripple.this);
- updateClipping();
- }
-
- public void onAnimationStart(Animator animation) {
- mRipples.add(Ripple.this);
- updateClipping();
- }
- });
- animator.setDuration(400);
- animator.start();
- }
-
- public void draw(Canvas canvas) {
- mRipplePaint.setAlpha((int) (alpha * 255));
- canvas.drawCircle(x, y, radius, mRipplePaint);
- }
- }
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/SearchPanelView.java b/packages/SystemUI/src/com/android/systemui/SearchPanelView.java
deleted file mode 100644
index 445b499..0000000
--- a/packages/SystemUI/src/com/android/systemui/SearchPanelView.java
+++ /dev/null
@@ -1,345 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui;
-
-import android.app.ActivityOptions;
-import android.app.SearchManager;
-import android.content.ActivityNotFoundException;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.media.AudioAttributes;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.os.Vibrator;
-import android.provider.Settings;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-
-import com.android.systemui.statusbar.BaseStatusBar;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.StatusBarPanel;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
-
-public class SearchPanelView extends FrameLayout implements StatusBarPanel {
-
- private static final String TAG = "SearchPanelView";
- private static final String ASSIST_ICON_METADATA_NAME =
- "com.android.systemui.action_assist_icon";
-
- private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
- .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
- .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
- .build();
-
- private final Context mContext;
- private BaseStatusBar mBar;
-
- private SearchPanelCircleView mCircle;
- private ImageView mLogo;
- private View mScrim;
-
- private int mThreshold;
- private boolean mHorizontal;
-
- private boolean mLaunching;
- private boolean mDragging;
- private boolean mDraggedFarEnough;
- private float mStartTouch;
- private float mStartDrag;
- private boolean mLaunchPending;
-
- public SearchPanelView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public SearchPanelView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- mContext = context;
- mThreshold = context.getResources().getDimensionPixelSize(R.dimen.search_panel_threshold);
- }
-
- private void startAssistActivity() {
- if (!mBar.isDeviceProvisioned()) return;
-
- // Close Recent Apps if needed
- mBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL);
-
- final Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
- .getAssistIntent(mContext, true, UserHandle.USER_CURRENT);
- if (intent == null) return;
-
- try {
- final ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext,
- R.anim.search_launch_enter, R.anim.search_launch_exit);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- AsyncTask.execute(new Runnable() {
- @Override
- public void run() {
- mContext.startActivityAsUser(intent, opts.toBundle(),
- new UserHandle(UserHandle.USER_CURRENT));
- }
- });
- } catch (ActivityNotFoundException e) {
- Log.w(TAG, "Activity not found for " + intent.getAction());
- }
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- mCircle = (SearchPanelCircleView) findViewById(R.id.search_panel_circle);
- mLogo = (ImageView) findViewById(R.id.search_logo);
- mScrim = findViewById(R.id.search_panel_scrim);
- }
-
- private void maybeSwapSearchIcon() {
- Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
- .getAssistIntent(mContext, false, UserHandle.USER_CURRENT);
- if (intent != null) {
- ComponentName component = intent.getComponent();
- replaceDrawable(mLogo, component, ASSIST_ICON_METADATA_NAME);
- } else {
- mLogo.setImageDrawable(null);
- }
- }
-
- public void replaceDrawable(ImageView v, ComponentName component, String name) {
- if (component != null) {
- try {
- PackageManager packageManager = mContext.getPackageManager();
- // Look for the search icon specified in the activity meta-data
- Bundle metaData = packageManager.getActivityInfo(
- component, PackageManager.GET_META_DATA).metaData;
- if (metaData != null) {
- int iconResId = metaData.getInt(name);
- if (iconResId != 0) {
- Resources res = packageManager.getResourcesForActivity(component);
- v.setImageDrawable(res.getDrawable(iconResId));
- return;
- }
- }
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "Failed to swap drawable; "
- + component.flattenToShortString() + " not found", e);
- } catch (Resources.NotFoundException nfe) {
- Log.w(TAG, "Failed to swap drawable from "
- + component.flattenToShortString(), nfe);
- }
- }
- v.setImageDrawable(null);
- }
-
- @Override
- public boolean isInContentArea(int x, int y) {
- return true;
- }
-
- private void vibrate() {
- Context context = getContext();
- if (Settings.System.getIntForUser(context.getContentResolver(),
- Settings.System.HAPTIC_FEEDBACK_ENABLED, 1, UserHandle.USER_CURRENT) != 0) {
- Resources res = context.getResources();
- Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
- vibrator.vibrate(res.getInteger(R.integer.config_search_panel_view_vibration_duration),
- VIBRATION_ATTRIBUTES);
- }
- }
-
- public void show(final boolean show, boolean animate) {
- if (show) {
- maybeSwapSearchIcon();
- if (getVisibility() != View.VISIBLE) {
- setVisibility(View.VISIBLE);
- vibrate();
- if (animate) {
- startEnterAnimation();
- } else {
- mScrim.setAlpha(1f);
- }
- }
- setFocusable(true);
- setFocusableInTouchMode(true);
- requestFocus();
- } else {
- if (animate) {
- startAbortAnimation();
- } else {
- setVisibility(View.INVISIBLE);
- }
- }
- }
-
- private void startEnterAnimation() {
- mCircle.startEnterAnimation();
- mScrim.setAlpha(0f);
- mScrim.animate()
- .alpha(1f)
- .setDuration(300)
- .setStartDelay(50)
- .setInterpolator(PhoneStatusBar.ALPHA_IN)
- .start();
-
- }
-
- private void startAbortAnimation() {
- mCircle.startAbortAnimation(new Runnable() {
- @Override
- public void run() {
- mCircle.setAnimatingOut(false);
- setVisibility(View.INVISIBLE);
- }
- });
- mCircle.setAnimatingOut(true);
- mScrim.animate()
- .alpha(0f)
- .setDuration(300)
- .setStartDelay(0)
- .setInterpolator(PhoneStatusBar.ALPHA_OUT);
- }
-
- public void hide(boolean animate) {
- if (mBar != null) {
- // This will indirectly cause show(false, ...) to get called
- mBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
- } else {
- if (animate) {
- startAbortAnimation();
- } else {
- setVisibility(View.INVISIBLE);
- }
- }
- }
-
- @Override
- public boolean dispatchHoverEvent(MotionEvent event) {
- // Ignore hover events outside of this panel bounds since such events
- // generate spurious accessibility events with the panel content when
- // tapping outside of it, thus confusing the user.
- final int x = (int) event.getX();
- final int y = (int) event.getY();
- if (x >= 0 && x < getWidth() && y >= 0 && y < getHeight()) {
- return super.dispatchHoverEvent(event);
- }
- return true;
- }
-
- /**
- * Whether the panel is showing, or, if it's animating, whether it will be
- * when the animation is done.
- */
- public boolean isShowing() {
- return getVisibility() == View.VISIBLE && !mCircle.isAnimatingOut();
- }
-
- public void setBar(BaseStatusBar bar) {
- mBar = bar;
- }
-
- public boolean isAssistantAvailable() {
- return ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
- .getAssistIntent(mContext, false, UserHandle.USER_CURRENT) != null;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- if (mLaunching || mLaunchPending) {
- return false;
- }
- int action = event.getActionMasked();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- mStartTouch = mHorizontal ? event.getX() : event.getY();
- mDragging = false;
- mDraggedFarEnough = false;
- mCircle.reset();
- break;
- case MotionEvent.ACTION_MOVE:
- float currentTouch = mHorizontal ? event.getX() : event.getY();
- if (getVisibility() == View.VISIBLE && !mDragging &&
- (!mCircle.isAnimationRunning(true /* enterAnimation */)
- || Math.abs(mStartTouch - currentTouch) > mThreshold)) {
- mStartDrag = currentTouch;
- mDragging = true;
- }
- if (mDragging) {
- float offset = Math.max(mStartDrag - currentTouch, 0.0f);
- mCircle.setDragDistance(offset);
- mDraggedFarEnough = Math.abs(mStartTouch - currentTouch) > mThreshold;
- mCircle.setDraggedFarEnough(mDraggedFarEnough);
- }
- break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- if (mDraggedFarEnough) {
- if (mCircle.isAnimationRunning(true /* enterAnimation */)) {
- mLaunchPending = true;
- mCircle.setAnimatingOut(true);
- mCircle.performOnAnimationFinished(new Runnable() {
- @Override
- public void run() {
- startExitAnimation();
- }
- });
- } else {
- startExitAnimation();
- }
- } else {
- startAbortAnimation();
- }
- break;
- }
- return true;
- }
-
- private void startExitAnimation() {
- mLaunchPending = false;
- if (mLaunching || getVisibility() != View.VISIBLE) {
- return;
- }
- mLaunching = true;
- startAssistActivity();
- vibrate();
- mCircle.setAnimatingOut(true);
- mCircle.startExitAnimation(new Runnable() {
- @Override
- public void run() {
- mLaunching = false;
- mCircle.setAnimatingOut(false);
- setVisibility(View.INVISIBLE);
- }
- });
- mScrim.animate()
- .alpha(0f)
- .setDuration(300)
- .setStartDelay(0)
- .setInterpolator(PhoneStatusBar.ALPHA_OUT);
- }
-
- public void setHorizontal(boolean horizontal) {
- mHorizontal = horizontal;
- mCircle.setHorizontal(horizontal);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index b3f90d7..e302c98 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -41,7 +41,7 @@ public class SystemUIApplication extends Application {
*/
private final Class<?>[] SERVICES = new Class[] {
com.android.systemui.keyguard.KeyguardViewMediator.class,
- com.android.systemui.recent.Recents.class,
+ com.android.systemui.recents.Recents.class,
com.android.systemui.volume.VolumeUI.class,
com.android.systemui.statusbar.SystemBars.class,
com.android.systemui.usb.StorageNotification.class,
diff --git a/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java b/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java
index 2ff8f8a..eddf2b1 100644
--- a/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java
@@ -26,8 +26,6 @@ import android.view.View;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
-import com.android.systemui.statusbar.phone.NotificationPanelView;
-
/**
* Helper to invert the colors of views and fade between the states.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistGestureManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistGestureManager.java
new file mode 100644
index 0000000..36be355
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistGestureManager.java
@@ -0,0 +1,292 @@
+package com.android.systemui.assist;
+
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.SearchManager;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.media.AudioAttributes;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.os.Vibrator;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.ImageView;
+
+import com.android.internal.app.IVoiceInteractionManagerService;
+import com.android.internal.app.IVoiceInteractionSessionShowCallback;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.phone.PhoneStatusBar;
+
+/**
+ * Class to manage everything around the assist gesture.
+ */
+public class AssistGestureManager {
+
+ private static final String TAG = "AssistGestureManager";
+ private static final String ASSIST_ICON_METADATA_NAME =
+ "com.android.systemui.action_assist_icon";
+
+ private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
+ .build();
+
+ private static final long TIMEOUT_SERVICE = 2500;
+ private static final long TIMEOUT_ACTIVITY = 1000;
+
+ private final Context mContext;
+ private final WindowManager mWindowManager;
+ private AssistOrbContainer mView;
+ private final PhoneStatusBar mBar;
+ private final IVoiceInteractionManagerService mVoiceInteractionManagerService;
+
+ private IVoiceInteractionSessionShowCallback mShowCallback =
+ new IVoiceInteractionSessionShowCallback.Stub() {
+
+ @Override
+ public void onFailed() throws RemoteException {
+ mView.post(mHideRunnable);
+ }
+
+ @Override
+ public void onShown() throws RemoteException {
+ mView.post(mHideRunnable);
+ }
+ };
+
+ private Runnable mHideRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mView.removeCallbacks(this);
+ mView.show(false /* show */, true /* animate */);
+ }
+ };
+
+ public AssistGestureManager(PhoneStatusBar bar, Context context) {
+ mContext = context;
+ mBar = bar;
+ mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+ mVoiceInteractionManagerService = IVoiceInteractionManagerService.Stub.asInterface(
+ ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
+ }
+
+ public void onConfigurationChanged() {
+ boolean visible = false;
+ if (mView != null) {
+ visible = mView.isShowing();
+ mWindowManager.removeView(mView);
+ }
+
+ mView = (AssistOrbContainer) LayoutInflater.from(mContext).inflate(
+ R.layout.assist_orb, null);
+ mView.setVisibility(View.GONE);
+ mView.setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
+ WindowManager.LayoutParams lp = getLayoutParams();
+ mWindowManager.addView(mView, lp);
+ mBar.getNavigationBarView().setDelegateView(mView);
+ if (visible) {
+ mView.show(true /* show */, false /* animate */);
+ }
+ }
+
+ public void onGestureInvoked(boolean vibrate) {
+ boolean isVoiceInteractorActive = getVoiceInteractorSupportsAssistGesture();
+ if (!isVoiceInteractorActive && !isAssistantIntentAvailable()) {
+ return;
+ }
+ if (vibrate) {
+ vibrate();
+ }
+ if (!isVoiceInteractorActive || !isVoiceSessionRunning()) {
+ showOrb();
+ mView.postDelayed(mHideRunnable, isVoiceInteractorActive
+ ? TIMEOUT_SERVICE
+ : TIMEOUT_ACTIVITY);
+ }
+ startAssist();
+ }
+
+ private WindowManager.LayoutParams getLayoutParams() {
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ mContext.getResources().getDimensionPixelSize(R.dimen.assist_orb_scrim_height),
+ WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING,
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSLUCENT);
+ if (ActivityManager.isHighEndGfx()) {
+ lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+ }
+ lp.gravity = Gravity.BOTTOM | Gravity.START;
+ lp.setTitle("AssistPreviewPanel");
+ lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
+ | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+ return lp;
+ }
+
+ private void showOrb() {
+ maybeSwapSearchIcon();
+ mView.show(true /* show */, true /* animate */);
+ }
+
+ private void startAssist() {
+ if (getVoiceInteractorSupportsAssistGesture()) {
+ startVoiceInteractor();
+ } else {
+ startAssistActivity();
+ }
+ }
+
+ private void startAssistActivity() {
+ if (!mBar.isDeviceProvisioned()) {
+ return;
+ }
+
+ // Close Recent Apps if needed
+ mBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL |
+ CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL);
+
+ final Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
+ .getAssistIntent(mContext, true, UserHandle.USER_CURRENT);
+ if (intent == null) {
+ return;
+ }
+
+ try {
+ final ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext,
+ R.anim.search_launch_enter, R.anim.search_launch_exit);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ AsyncTask.execute(new Runnable() {
+ @Override
+ public void run() {
+ mContext.startActivityAsUser(intent, opts.toBundle(),
+ new UserHandle(UserHandle.USER_CURRENT));
+ }
+ });
+ } catch (ActivityNotFoundException e) {
+ Log.w(TAG, "Activity not found for " + intent.getAction());
+ }
+ }
+
+ private void startVoiceInteractor() {
+ try {
+ mVoiceInteractionManagerService.showSessionForActiveService(mShowCallback);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to call showSessionForActiveService", e);
+ }
+ }
+
+ private boolean getVoiceInteractorSupportsAssistGesture() {
+ try {
+ return mVoiceInteractionManagerService.activeServiceSupportsAssistGesture();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to call activeServiceSupportsAssistGesture", e);
+ return false;
+ }
+ }
+
+ private ComponentName getVoiceInteractorComponentName() {
+ try {
+ return mVoiceInteractionManagerService.getActiveServiceComponentName();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to call getActiveServiceComponentName", e);
+ return null;
+ }
+ }
+
+ private boolean isVoiceSessionRunning() {
+ try {
+ return mVoiceInteractionManagerService.isSessionRunning();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to call isSessionRunning", e);
+ return false;
+ }
+ }
+
+ public void destroy() {
+ mWindowManager.removeViewImmediate(mView);
+ }
+
+ private void maybeSwapSearchIcon() {
+ Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
+ .getAssistIntent(mContext, false, UserHandle.USER_CURRENT);
+ ComponentName component = null;
+ boolean isService = false;
+ if (getVoiceInteractorSupportsAssistGesture()) {
+ component = getVoiceInteractorComponentName();
+ isService = true;
+ } else if (intent != null) {
+ component = intent.getComponent();
+ }
+ if (component != null) {
+ replaceDrawable(mView.getOrb().getLogo(), component, ASSIST_ICON_METADATA_NAME,
+ isService);
+ } else {
+ mView.getOrb().getLogo().setImageDrawable(null);
+ }
+ }
+
+ public void replaceDrawable(ImageView v, ComponentName component, String name,
+ boolean isService) {
+ if (component != null) {
+ try {
+ PackageManager packageManager = mContext.getPackageManager();
+ // Look for the search icon specified in the activity meta-data
+ Bundle metaData = isService
+ ? packageManager.getServiceInfo(
+ component, PackageManager.GET_META_DATA).metaData
+ : packageManager.getActivityInfo(
+ component, PackageManager.GET_META_DATA).metaData;
+ if (metaData != null) {
+ int iconResId = metaData.getInt(name);
+ if (iconResId != 0) {
+ Resources res = packageManager.getResourcesForApplication(
+ component.getPackageName());
+ v.setImageDrawable(res.getDrawable(iconResId));
+ return;
+ }
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Failed to swap drawable; "
+ + component.flattenToShortString() + " not found", e);
+ } catch (Resources.NotFoundException nfe) {
+ Log.w(TAG, "Failed to swap drawable from "
+ + component.flattenToShortString(), nfe);
+ }
+ }
+ v.setImageDrawable(null);
+ }
+
+ private void vibrate() {
+ if (Settings.System.getIntForUser(mContext.getContentResolver(),
+ Settings.System.HAPTIC_FEEDBACK_ENABLED, 1, UserHandle.USER_CURRENT) != 0) {
+ Resources res = mContext.getResources();
+ Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
+ vibrator.vibrate(res.getInteger(R.integer.config_search_panel_view_vibration_duration),
+ VIBRATION_ATTRIBUTES);
+ }
+ }
+
+ public boolean isAssistantIntentAvailable() {
+ return ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
+ .getAssistIntent(mContext, false, UserHandle.USER_CURRENT) != null;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistOrbContainer.java b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbContainer.java
new file mode 100644
index 0000000..67017db
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbContainer.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.assist;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.widget.FrameLayout;
+
+import com.android.systemui.R;
+
+public class AssistOrbContainer extends FrameLayout {
+
+ private static final long EXIT_START_DELAY = 150;
+
+ private final Interpolator mLinearOutSlowInInterpolator;
+ private final Interpolator mFastOutLinearInInterpolator;
+
+ private View mScrim;
+ private View mNavbarScrim;
+ private AssistOrbView mOrb;
+
+ private boolean mAnimatingOut;
+
+ public AssistOrbContainer(Context context) {
+ this(context, null);
+ }
+
+ public AssistOrbContainer(Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public AssistOrbContainer(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
+ android.R.interpolator.linear_out_slow_in);
+ mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
+ android.R.interpolator.fast_out_slow_in);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mScrim = findViewById(R.id.assist_orb_scrim);
+ mNavbarScrim = findViewById(R.id.assist_orb_navbar_scrim);
+ mOrb = (AssistOrbView) findViewById(R.id.assist_orb);
+ }
+
+ public void show(final boolean show, boolean animate) {
+ if (show) {
+ if (getVisibility() != View.VISIBLE) {
+ setVisibility(View.VISIBLE);
+ if (animate) {
+ startEnterAnimation();
+ } else {
+ reset();
+ }
+ }
+ } else {
+ if (animate) {
+ startExitAnimation(new Runnable() {
+ @Override
+ public void run() {
+ mAnimatingOut = false;
+ setVisibility(View.GONE);
+ }
+ });
+ } else {
+ setVisibility(View.GONE);
+ }
+ }
+ }
+
+ private void reset() {
+ mAnimatingOut = false;
+ mOrb.reset();
+ mScrim.setAlpha(1f);
+ mNavbarScrim.setAlpha(1f);
+ }
+
+ private void startEnterAnimation() {
+ if (mAnimatingOut) {
+ return;
+ }
+ mOrb.startEnterAnimation();
+ mScrim.setAlpha(0f);
+ mNavbarScrim.setAlpha(0f);
+ post(new Runnable() {
+ @Override
+ public void run() {
+ mScrim.animate()
+ .alpha(1f)
+ .setDuration(300)
+ .setStartDelay(0)
+ .setInterpolator(mLinearOutSlowInInterpolator);
+ mNavbarScrim.animate()
+ .alpha(1f)
+ .setDuration(300)
+ .setStartDelay(0)
+ .setInterpolator(mLinearOutSlowInInterpolator);
+ }
+ });
+ }
+
+ private void startExitAnimation(final Runnable endRunnable) {
+ if (mAnimatingOut) {
+ if (endRunnable != null) {
+ endRunnable.run();
+ }
+ return;
+ }
+ mAnimatingOut = true;
+ mOrb.startExitAnimation(EXIT_START_DELAY);
+ mScrim.animate()
+ .alpha(0f)
+ .setDuration(250)
+ .setStartDelay(EXIT_START_DELAY)
+ .setInterpolator(mFastOutLinearInInterpolator);
+ mNavbarScrim.animate()
+ .alpha(0f)
+ .setDuration(250)
+ .setStartDelay(EXIT_START_DELAY)
+ .setInterpolator(mFastOutLinearInInterpolator)
+ .withEndAction(endRunnable);
+ }
+
+ /**
+ * Whether the panel is showing, or, if it's animating, whether it will be
+ * when the animation is done.
+ */
+ public boolean isShowing() {
+ return getVisibility() == View.VISIBLE && !mAnimatingOut;
+ }
+
+ public AssistOrbView getOrb() {
+ return mOrb;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistOrbView.java b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbView.java
new file mode 100644
index 0000000..a3372a8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbView.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.assist;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.view.animation.OvershootInterpolator;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import com.android.systemui.R;
+
+public class AssistOrbView extends FrameLayout {
+
+ private final int mCircleMinSize;
+ private final int mBaseMargin;
+ private final int mStaticOffset;
+ private final Paint mBackgroundPaint = new Paint();
+ private final Rect mCircleRect = new Rect();
+ private final Rect mStaticRect = new Rect();
+ private final Interpolator mAppearInterpolator;
+ private final Interpolator mDisappearInterpolator;
+ private final Interpolator mOvershootInterpolator = new OvershootInterpolator();
+
+ private boolean mClipToOutline;
+ private final int mMaxElevation;
+ private float mOutlineAlpha;
+ private float mOffset;
+ private float mCircleSize;
+ private ImageView mLogo;
+ private float mCircleAnimationEndValue;
+
+ private ValueAnimator mOffsetAnimator;
+ private ValueAnimator mCircleAnimator;
+
+ private ValueAnimator.AnimatorUpdateListener mCircleUpdateListener
+ = new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ applyCircleSize((float) animation.getAnimatedValue());
+ updateElevation();
+ }
+ };
+ private AnimatorListenerAdapter mClearAnimatorListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mCircleAnimator = null;
+ }
+ };
+ private ValueAnimator.AnimatorUpdateListener mOffsetUpdateListener
+ = new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ mOffset = (float) animation.getAnimatedValue();
+ updateLayout();
+ }
+ };
+
+
+ public AssistOrbView(Context context) {
+ this(context, null);
+ }
+
+ public AssistOrbView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public AssistOrbView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public AssistOrbView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ if (mCircleSize > 0.0f) {
+ outline.setOval(mCircleRect);
+ } else {
+ outline.setEmpty();
+ }
+ outline.setAlpha(mOutlineAlpha);
+ }
+ });
+ setWillNotDraw(false);
+ mCircleMinSize = context.getResources().getDimensionPixelSize(
+ R.dimen.assist_orb_size);
+ mBaseMargin = context.getResources().getDimensionPixelSize(
+ R.dimen.assist_orb_base_margin);
+ mStaticOffset = context.getResources().getDimensionPixelSize(
+ R.dimen.assist_orb_travel_distance);
+ mMaxElevation = context.getResources().getDimensionPixelSize(
+ R.dimen.assist_orb_elevation);
+ mAppearInterpolator = AnimationUtils.loadInterpolator(mContext,
+ android.R.interpolator.linear_out_slow_in);
+ mDisappearInterpolator = AnimationUtils.loadInterpolator(mContext,
+ android.R.interpolator.fast_out_linear_in);
+ mBackgroundPaint.setAntiAlias(true);
+ mBackgroundPaint.setColor(getResources().getColor(R.color.assist_orb_color));
+ }
+
+ public ImageView getLogo() {
+ return mLogo;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ drawBackground(canvas);
+ }
+
+ private void drawBackground(Canvas canvas) {
+ canvas.drawCircle(mCircleRect.centerX(), mCircleRect.centerY(), mCircleSize / 2,
+ mBackgroundPaint);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mLogo = (ImageView) findViewById(R.id.search_logo);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ mLogo.layout(0, 0, mLogo.getMeasuredWidth(), mLogo.getMeasuredHeight());
+ if (changed) {
+ updateCircleRect(mStaticRect, mStaticOffset, true);
+ }
+ }
+
+ public void animateCircleSize(float circleSize, long duration,
+ long startDelay, Interpolator interpolator) {
+ if (circleSize == mCircleAnimationEndValue) {
+ return;
+ }
+ if (mCircleAnimator != null) {
+ mCircleAnimator.cancel();
+ }
+ mCircleAnimator = ValueAnimator.ofFloat(mCircleSize, circleSize);
+ mCircleAnimator.addUpdateListener(mCircleUpdateListener);
+ mCircleAnimator.addListener(mClearAnimatorListener);
+ mCircleAnimator.setInterpolator(interpolator);
+ mCircleAnimator.setDuration(duration);
+ mCircleAnimator.setStartDelay(startDelay);
+ mCircleAnimator.start();
+ mCircleAnimationEndValue = circleSize;
+ }
+
+ private void applyCircleSize(float circleSize) {
+ mCircleSize = circleSize;
+ updateLayout();
+ }
+
+ private void updateElevation() {
+ float t = (mStaticOffset - mOffset) / (float) mStaticOffset;
+ t = 1.0f - Math.max(t, 0.0f);
+ float offset = t * mMaxElevation;
+ setElevation(offset);
+ }
+
+ /**
+ * Animates the offset to the edge of the screen.
+ *
+ * @param offset The offset to apply.
+ * @param startDelay The desired start delay if animated.
+ *
+ * @param interpolator The desired interpolator if animated. If null,
+ * a default interpolator will be taken designed for appearing or
+ * disappearing.
+ */
+ private void animateOffset(float offset, long duration, long startDelay,
+ Interpolator interpolator) {
+ if (mOffsetAnimator != null) {
+ mOffsetAnimator.removeAllListeners();
+ mOffsetAnimator.cancel();
+ }
+ mOffsetAnimator = ValueAnimator.ofFloat(mOffset, offset);
+ mOffsetAnimator.addUpdateListener(mOffsetUpdateListener);
+ mOffsetAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mOffsetAnimator = null;
+ }
+ });
+ mOffsetAnimator.setInterpolator(interpolator);
+ mOffsetAnimator.setStartDelay(startDelay);
+ mOffsetAnimator.setDuration(duration);
+ mOffsetAnimator.start();
+ }
+
+ private void updateLayout() {
+ updateCircleRect();
+ updateLogo();
+ invalidateOutline();
+ invalidate();
+ updateClipping();
+ }
+
+ private void updateClipping() {
+ boolean clip = mCircleSize < mCircleMinSize;
+ if (clip != mClipToOutline) {
+ setClipToOutline(clip);
+ mClipToOutline = clip;
+ }
+ }
+
+ private void updateLogo() {
+ float translationX = (mCircleRect.left + mCircleRect.right) / 2.0f - mLogo.getWidth() / 2.0f;
+ float translationY = (mCircleRect.top + mCircleRect.bottom) / 2.0f
+ - mLogo.getHeight() / 2.0f - mCircleMinSize / 7f;
+ float t = (mStaticOffset - mOffset) / (float) mStaticOffset;
+ translationY += t * mStaticOffset * 0.1f;
+ float alpha = 1.0f-t;
+ alpha = Math.max((alpha - 0.5f) * 2.0f, 0);
+ mLogo.setImageAlpha((int) (alpha * 255));
+ mLogo.setTranslationX(translationX);
+ mLogo.setTranslationY(translationY);
+ }
+
+ private void updateCircleRect() {
+ updateCircleRect(mCircleRect, mOffset, false);
+ }
+
+ private void updateCircleRect(Rect rect, float offset, boolean useStaticSize) {
+ int left, top;
+ float circleSize = useStaticSize ? mCircleMinSize : mCircleSize;
+ left = (int) (getWidth() - circleSize) / 2;
+ top = (int) (getHeight() - circleSize / 2 - mBaseMargin - offset);
+ rect.set(left, top, (int) (left + circleSize), (int) (top + circleSize));
+ }
+
+ public void startExitAnimation(long delay) {
+ animateCircleSize(0, 200, delay, mDisappearInterpolator);
+ animateOffset(0, 200, delay, mDisappearInterpolator);
+ }
+
+ public void startEnterAnimation() {
+ applyCircleSize(0);
+ post(new Runnable() {
+ @Override
+ public void run() {
+ animateCircleSize(mCircleMinSize, 300, 0 /* delay */, mOvershootInterpolator);
+ animateOffset(mStaticOffset, 400, 0 /* delay */, mAppearInterpolator);
+ }
+ });
+ }
+
+ public void reset() {
+ mClipToOutline = false;
+ mBackgroundPaint.setAlpha(255);
+ mOutlineAlpha = 1.0f;
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ // not really true but it's ok during an animation, as it's never permanent
+ return false;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/egg/LLandActivity.java b/packages/SystemUI/src/com/android/systemui/egg/LLandActivity.java
index b9f8106..50221d3 100644
--- a/packages/SystemUI/src/com/android/systemui/egg/LLandActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/egg/LLandActivity.java
@@ -18,7 +18,6 @@ package com.android.systemui.egg;
import android.app.Activity;
import android.os.Bundle;
-import android.util.Log;
import android.widget.TextView;
import com.android.systemui.R;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index e66934e..dd28734 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -63,7 +63,6 @@ import com.android.keyguard.KeyguardConstants;
import com.android.keyguard.KeyguardDisplayManager;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.keyguard.MultiUserAvatarCache;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.SystemUI;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
@@ -173,7 +172,7 @@ public class KeyguardViewMediator extends SystemUI {
private static final String KEYGUARD_ANALYTICS_SETTING = "keyguard_analytics";
/** The stream type that the lock sounds are tied to. */
- private int mMasterStreamType;
+ private int mUiSoundsStreamType;
private AlarmManager mAlarmManager;
private AudioManager mAudioManager;
@@ -314,9 +313,6 @@ public class KeyguardViewMediator extends SystemUI {
resetKeyguardDonePendingLocked();
resetStateLocked();
adjustStatusBarLocked();
- // When we switch users we want to bring the new user to the biometric unlock even
- // if the current user has gone to the backup.
- KeyguardUpdateMonitor.getInstance(mContext).setAlternateUnlockEnabled(true);
}
}
@@ -333,14 +329,7 @@ public class KeyguardViewMediator extends SystemUI {
}
@Override
- public void onUserRemoved(int userId) {
- mLockPatternUtils.removeUser(userId);
- MultiUserAvatarCache.getInstance().clear(userId);
- }
-
- @Override
public void onUserInfoChanged(int userId) {
- MultiUserAvatarCache.getInstance().clear(userId);
}
@Override
@@ -447,9 +436,12 @@ public class KeyguardViewMediator extends SystemUI {
}
}
- public void onFingerprintRecognized(int userId) {
+ @Override
+ public void onFingerprintAuthenticated(int userId) {
if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
mViewMediatorCallback.keyguardDone(true);
+ } else {
+ mStatusBarKeyguardViewManager.animateCollapsePanels();
}
};
@@ -477,11 +469,6 @@ public class KeyguardViewMediator extends SystemUI {
}
@Override
- public void onUserActivityTimeoutChanged() {
- mStatusBarKeyguardViewManager.updateUserActivityTimeout();
- }
-
- @Override
public void keyguardDonePending() {
mKeyguardDonePending = true;
mHideAnimationRun = true;
@@ -505,6 +492,11 @@ public class KeyguardViewMediator extends SystemUI {
}
@Override
+ public void resetKeyguard() {
+ resetStateLocked();
+ }
+
+ @Override
public void playTrustedSound() {
KeyguardViewMediator.this.playTrustedSound();
}
@@ -597,23 +589,6 @@ public class KeyguardViewMediator extends SystemUI {
mSystemReady = true;
mUpdateMonitor.registerCallback(mUpdateCallback);
- // Suppress biometric unlock right after boot until things have settled if it is the
- // selected security method, otherwise unsuppress it. It must be unsuppressed if it is
- // not the selected security method for the following reason: if the user starts
- // without a screen lock selected, the biometric unlock would be suppressed the first
- // time they try to use it.
- //
- // Note that the biometric unlock will still not show if it is not the selected method.
- // Calling setAlternateUnlockEnabled(true) simply says don't suppress it if it is the
- // selected method.
- if (mLockPatternUtils.usingBiometricWeak()
- && mLockPatternUtils.isBiometricWeakInstalled()) {
- if (DEBUG) Log.d(TAG, "suppressing biometric unlock during boot");
- mUpdateMonitor.setAlternateUnlockEnabled(false);
- } else {
- mUpdateMonitor.setAlternateUnlockEnabled(true);
- }
-
doKeyguardLocked(null);
}
// Most services aren't available until the system reaches the ready state, so we
@@ -662,7 +637,7 @@ public class KeyguardViewMediator extends SystemUI {
doKeyguardLocked(null);
}
}
- KeyguardUpdateMonitor.getInstance(mContext).dispatchScreenTurndOff(why);
+ KeyguardUpdateMonitor.getInstance(mContext).dispatchScreenTurnedOff(why);
}
private void doKeyguardLaterLocked() {
@@ -1270,10 +1245,10 @@ public class KeyguardViewMediator extends SystemUI {
if (mAudioManager == null) {
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
if (mAudioManager == null) return;
- mMasterStreamType = mAudioManager.getMasterStreamType();
+ mUiSoundsStreamType = mAudioManager.getUiSoundsStreamType();
}
// If the stream is muted, don't play the sound
- if (mAudioManager.isStreamMute(mMasterStreamType)) return;
+ if (mAudioManager.isStreamMute(mUiSoundsStreamType)) return;
mLockSoundStreamId = mLockSounds.play(soundId,
mLockSoundVolume, mLockSoundVolume, 1/*priortiy*/, 0/*loop*/, 1.0f/*rate*/);
@@ -1328,6 +1303,8 @@ public class KeyguardViewMediator extends SystemUI {
@Override
public void run() {
try {
+ mStatusBarKeyguardViewManager.keyguardGoingAway();
+
// Don't actually hide the Keyguard at the moment, wait for window
// manager until it tells us it's safe to do so with
// startKeyguardExitAnimation.
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index c23f45d..88d0997 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -18,8 +18,6 @@ package com.android.systemui.media;
import android.app.Activity;
import android.app.AlertDialog;
-import android.app.PendingIntent;
-import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -32,16 +30,10 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
-import android.view.LayoutInflater;
import android.view.WindowManager;
import android.widget.CheckBox;
import android.widget.CompoundButton;
-import android.widget.TextView;
-
-import com.android.internal.app.AlertActivity;
-import com.android.internal.app.AlertController;
import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.SystemUIDialog;
public class MediaProjectionPermissionActivity extends Activity
implements DialogInterface.OnClickListener, CheckBox.OnCheckedChangeListener,
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index 4391bfc..af1f795 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -50,7 +50,6 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
private static final boolean DEBUG = PowerUI.DEBUG;
private static final String TAG_NOTIFICATION = "low_battery";
- private static final int ID_NOTIFICATION = 100;
private static final int SHOWING_NOTHING = 0;
private static final int SHOWING_WARNING = 1;
@@ -145,7 +144,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
showSaverNotification();
mShowing = SHOWING_SAVER;
} else {
- mNoMan.cancelAsUser(TAG_NOTIFICATION, ID_NOTIFICATION, UserHandle.ALL);
+ mNoMan.cancelAsUser(TAG_NOTIFICATION, R.id.notification_power, UserHandle.ALL);
mShowing = SHOWING_NOTHING;
}
}
@@ -160,13 +159,13 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
.setContentText(mContext.getString(R.string.invalid_charger_text))
.setPriority(Notification.PRIORITY_MAX)
.setVisibility(Notification.VISIBILITY_PUBLIC)
- .setColor(mContext.getResources().getColor(
+ .setColor(mContext.getColor(
com.android.internal.R.color.system_notification_accent_color));
final Notification n = nb.build();
if (n.headsUpContentView != null) {
n.headsUpContentView.setViewVisibility(com.android.internal.R.id.right_icon, View.GONE);
}
- mNoMan.notifyAsUser(TAG_NOTIFICATION, ID_NOTIFICATION, n, UserHandle.ALL);
+ mNoMan.notifyAsUser(TAG_NOTIFICATION, R.id.notification_power, n, UserHandle.ALL);
}
private void showWarningNotification() {
@@ -184,7 +183,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
.setDeleteIntent(pendingBroadcast(ACTION_DISMISSED_WARNING))
.setPriority(Notification.PRIORITY_MAX)
.setVisibility(Notification.VISIBILITY_PUBLIC)
- .setColor(mContext.getResources().getColor(
+ .setColor(mContext.getColor(
com.android.internal.R.color.battery_saver_mode_color));
if (hasBatterySettings()) {
nb.setContentIntent(pendingBroadcast(ACTION_SHOW_BATTERY_SETTINGS));
@@ -204,7 +203,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
if (n.headsUpContentView != null) {
n.headsUpContentView.setViewVisibility(com.android.internal.R.id.right_icon, View.GONE);
}
- mNoMan.notifyAsUser(TAG_NOTIFICATION, ID_NOTIFICATION, n, UserHandle.ALL);
+ mNoMan.notifyAsUser(TAG_NOTIFICATION, R.id.notification_power, n, UserHandle.ALL);
}
private void showSaverNotification() {
@@ -215,13 +214,13 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
.setOngoing(true)
.setShowWhen(false)
.setVisibility(Notification.VISIBILITY_PUBLIC)
- .setColor(mContext.getResources().getColor(
+ .setColor(mContext.getColor(
com.android.internal.R.color.battery_saver_mode_color));
addStopSaverAction(nb);
if (hasSaverSettings()) {
nb.setContentIntent(pendingActivity(mOpenSaverSettings));
}
- mNoMan.notifyAsUser(TAG_NOTIFICATION, ID_NOTIFICATION, nb.build(), UserHandle.ALL);
+ mNoMan.notifyAsUser(TAG_NOTIFICATION, R.id.notification_power, nb.build(), UserHandle.ALL);
}
private void addStopSaverAction(Notification.Builder nb) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/DataUsageGraph.java b/packages/SystemUI/src/com/android/systemui/qs/DataUsageGraph.java
index d55ceaa..aff5d2b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/DataUsageGraph.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/DataUsageGraph.java
@@ -44,10 +44,10 @@ public class DataUsageGraph extends View {
public DataUsageGraph(Context context, AttributeSet attrs) {
super(context, attrs);
final Resources res = context.getResources();
- mTrackColor = res.getColor(R.color.data_usage_graph_track);
- mUsageColor = res.getColor(R.color.system_accent_color);
- mOverlimitColor = res.getColor(R.color.system_warning_color);
- mWarningColor = res.getColor(R.color.data_usage_graph_warning);
+ mTrackColor = context.getColor(R.color.data_usage_graph_track);
+ mUsageColor = context.getColor(R.color.system_accent_color);
+ mOverlimitColor = context.getColor(R.color.system_warning_color);
+ mWarningColor = context.getColor(R.color.data_usage_graph_warning);
mMarkerWidth = res.getDimensionPixelSize(R.dimen.data_usage_graph_marker_width);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
index a0b6e82..0ab644a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
@@ -18,12 +18,10 @@ package com.android.systemui.qs;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
-import android.content.res.Configuration;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
-import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 4dacacf..f4fd6a2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -26,7 +26,6 @@ import android.content.res.Resources;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
-import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -253,12 +252,6 @@ public class QSPanel extends ViewGroup {
@Override
public void onStateChanged(QSTile.State state) {
int visibility = state.visible ? VISIBLE : GONE;
- if (state.visible && !mGridContentVisible) {
-
- // We don't want to show it if the content is hidden,
- // then we just set it to invisible, to ensure that it gets visible again
- visibility = INVISIBLE;
- }
setTileVisibility(r.tileView, visibility);
r.tileView.onStateChanged(state);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
index 16ae6b4..ec83ca7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
@@ -88,7 +88,7 @@ public class QSTileView extends ViewGroup {
addView(mIcon);
mDivider = new View(mContext);
- mDivider.setBackgroundColor(res.getColor(R.color.qs_tile_divider));
+ mDivider.setBackgroundColor(context.getColor(R.color.qs_tile_divider));
final int dh = res.getDimensionPixelSize(R.dimen.qs_tile_divider_height);
mDivider.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, dh));
addView(mDivider);
@@ -138,8 +138,8 @@ 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.setFirstLineCaret(mContext.getDrawable(R.drawable.qs_dual_tile_caret));
+ mDualLabel.setTextColor(mContext.getColor(R.color.qs_tile_text));
mDualLabel.setPadding(0, mDualTileVerticalPaddingPx, 0, mDualTileVerticalPaddingPx);
mDualLabel.setTypeface(CONDENSED);
mDualLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX,
@@ -157,7 +157,7 @@ public class QSTileView extends ViewGroup {
} else {
mLabel = new TextView(mContext);
mLabel.setId(android.R.id.title);
- mLabel.setTextColor(res.getColor(R.color.qs_tile_text));
+ mLabel.setTextColor(mContext.getColor(R.color.qs_tile_text));
mLabel.setGravity(Gravity.CENTER_HORIZONTAL);
mLabel.setMinLines(2);
mLabel.setPadding(0, 0, 0, 0);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
index 2dd02a5..2bc31fc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
@@ -72,7 +72,7 @@ public class AirplaneModeTile extends QSTile<QSTile.BooleanState> {
final boolean airplaneMode = value != 0;
state.value = airplaneMode;
state.visible = true;
- state.label = mContext.getString(R.string.quick_settings_airplane_mode_label);
+ state.label = mContext.getString(R.string.airplane_mode);
if (airplaneMode) {
state.icon = mEnable;
state.contentDescription = mContext.getString(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index c15566f..b42b5f6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -16,6 +16,8 @@
package com.android.systemui.qs.tiles;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
import android.provider.Settings;
@@ -23,13 +25,14 @@ import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.systemui.R;
import com.android.systemui.qs.QSDetailItems;
import com.android.systemui.qs.QSDetailItems.Item;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.BluetoothController;
-import com.android.systemui.statusbar.policy.BluetoothController.PairedDevice;
+import java.util.Collection;
import java.util.Set;
/** Quick settings tile: Bluetooth **/
@@ -143,7 +146,7 @@ public class BluetoothTile extends QSTile<QSTile.BooleanState> {
refreshState();
}
@Override
- public void onBluetoothPairedDevicesChanged() {
+ public void onBluetoothDevicesChanged() {
mUiHandler.post(new Runnable() {
@Override
public void run() {
@@ -199,19 +202,21 @@ public class BluetoothTile extends QSTile<QSTile.BooleanState> {
private void updateItems() {
if (mItems == null) return;
Item[] items = null;
- final Set<PairedDevice> devices = mController.getPairedDevices();
+ final Collection<CachedBluetoothDevice> devices = mController.getDevices();
if (devices != null) {
- items = new Item[devices.size()];
+ items = new Item[getBondedCount(devices)];
int i = 0;
- for (PairedDevice device : devices) {
+ for (CachedBluetoothDevice device : devices) {
+ if (device.getBondState() == BluetoothDevice.BOND_NONE) continue;
final Item item = new Item();
item.icon = R.drawable.ic_qs_bluetooth_on;
- item.line1 = device.name;
- if (device.state == PairedDevice.STATE_CONNECTED) {
+ item.line1 = device.getName();
+ int state = device.getMaxConnectionState();
+ if (state == BluetoothProfile.STATE_CONNECTED) {
item.icon = R.drawable.ic_qs_bluetooth_connected;
item.line2 = mContext.getString(R.string.quick_settings_connected);
item.canDisconnect = true;
- } else if (device.state == PairedDevice.STATE_CONNECTING) {
+ } else if (state == BluetoothProfile.STATE_CONNECTING) {
item.icon = R.drawable.ic_qs_bluetooth_connecting;
item.line2 = mContext.getString(R.string.quick_settings_connecting);
}
@@ -222,11 +227,22 @@ public class BluetoothTile extends QSTile<QSTile.BooleanState> {
mItems.setItems(items);
}
+ private int getBondedCount(Collection<CachedBluetoothDevice> devices) {
+ int ct = 0;
+ for (CachedBluetoothDevice device : devices) {
+ if (device.getBondState() != BluetoothDevice.BOND_NONE) {
+ ct++;
+ }
+ }
+ return ct;
+ }
+
@Override
public void onDetailItemClick(Item item) {
if (item == null || item.tag == null) return;
- final PairedDevice device = (PairedDevice) item.tag;
- if (device != null && device.state == PairedDevice.STATE_DISCONNECTED) {
+ final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
+ if (device != null && device.getMaxConnectionState()
+ == BluetoothProfile.STATE_DISCONNECTED) {
mController.connect(device);
}
}
@@ -234,7 +250,7 @@ public class BluetoothTile extends QSTile<QSTile.BooleanState> {
@Override
public void onDetailItemDisconnect(Item item) {
if (item == null || item.tag == null) return;
- final PairedDevice device = (PairedDevice) item.tag;
+ final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
if (device != null) {
mController.disconnect(device);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataUsageDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataUsageDetailView.java
index eb816b7..d0ae383 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataUsageDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataUsageDetailView.java
@@ -96,7 +96,7 @@ public class DataUsageDetailView extends LinearLayout {
title.setText(titleId);
final TextView usage = (TextView) findViewById(R.id.usage_text);
usage.setText(formatBytes(bytes));
- usage.setTextColor(res.getColor(usageColor));
+ usage.setTextColor(mContext.getColor(usageColor));
final DataUsageGraph graph = (DataUsageGraph) findViewById(R.id.usage_graph);
graph.setLevels(info.limitLevel, info.warningLevel, info.usageLevel);
final TextView carrier = (TextView) findViewById(R.id.usage_carrier_text);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
new file mode 100644
index 0000000..ff6a45a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.provider.Settings;
+import android.provider.Settings.Global;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnAttachStateChangeListener;
+import android.view.ViewGroup;
+
+import com.android.systemui.R;
+import com.android.systemui.qs.QSTile;
+import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.volume.ZenModePanel;
+
+/** Quick settings tile: Do not disturb **/
+public class DndTile extends QSTile<QSTile.BooleanState> {
+ private static final Intent ZEN_SETTINGS = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS);
+
+ private static final String ACTION_SET_VISIBLE = "com.android.systemui.dndtile.SET_VISIBLE";
+ private static final String EXTRA_VISIBLE = "visible";
+ private static final String PREF_KEY_VISIBLE = "DndTileVisible";
+ private static final String PREF_KEY_COMBINED_ICON = "DndTileCombinedIcon";
+
+ private final ZenModeController mController;
+ private final DndDetailAdapter mDetailAdapter;
+
+ private boolean mListening;
+ private boolean mVisible;
+ private boolean mShowingDetail;
+
+ public DndTile(Host host) {
+ super(host);
+ mController = host.getZenModeController();
+ mDetailAdapter = new DndDetailAdapter();
+ mVisible = isVisible(host.getContext());
+ mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_SET_VISIBLE));
+ }
+
+ public static void setVisible(Context context, boolean visible) {
+ context.sendBroadcast(new Intent(DndTile.ACTION_SET_VISIBLE)
+ .putExtra(DndTile.EXTRA_VISIBLE, visible));
+ }
+
+ public static boolean isVisible(Context context) {
+ return getSharedPrefs(context).getBoolean(PREF_KEY_VISIBLE, false);
+ }
+
+ public static void setCombinedIcon(Context context, boolean combined) {
+ getSharedPrefs(context).edit().putBoolean(PREF_KEY_COMBINED_ICON, combined).commit();
+ }
+
+ public static boolean isCombinedIcon(Context context) {
+ return getSharedPrefs(context).getBoolean(PREF_KEY_COMBINED_ICON, false);
+ }
+
+ @Override
+ public DetailAdapter getDetailAdapter() {
+ return mDetailAdapter;
+ }
+
+ @Override
+ protected BooleanState newTileState() {
+ return new BooleanState();
+ }
+
+ @Override
+ public void handleClick() {
+ if (mState.value) {
+ mController.setZen(Global.ZEN_MODE_OFF);
+ } else {
+ mController.setZen(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ showDetail(true);
+ }
+ }
+
+ @Override
+ protected void handleUpdateState(BooleanState state, Object arg) {
+ final int zen = arg instanceof Integer ? (Integer) arg : mController.getZen();
+ state.value = zen != Global.ZEN_MODE_OFF;
+ state.visible = mVisible;
+ switch (zen) {
+ case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_dnd_on);
+ state.label = mContext.getString(R.string.quick_settings_dnd_priority_label);
+ state.contentDescription = mContext.getString(
+ R.string.accessibility_quick_settings_dnd_priority_on);
+ break;
+ case Global.ZEN_MODE_NO_INTERRUPTIONS:
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_dnd_on);
+ state.label = mContext.getString(R.string.quick_settings_dnd_none_label);
+ state.contentDescription = mContext.getString(
+ R.string.accessibility_quick_settings_dnd_none_on);
+ break;
+ case Global.ZEN_MODE_ALARMS:
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_dnd_on);
+ state.label = mContext.getString(R.string.quick_settings_dnd_alarms_label);
+ state.contentDescription = mContext.getString(
+ R.string.accessibility_quick_settings_dnd_alarms_on);
+ break;
+ default:
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_dnd_off);
+ state.label = mContext.getString(R.string.quick_settings_dnd_label);
+ state.contentDescription = mContext.getString(
+ R.string.accessibility_quick_settings_dnd_off);
+ break;
+ }
+ if (mShowingDetail && !state.value) {
+ showDetail(false);
+ }
+ }
+
+ @Override
+ protected String composeChangeAnnouncement() {
+ if (mState.value) {
+ return mContext.getString(R.string.accessibility_quick_settings_dnd_changed_on);
+ } else {
+ return mContext.getString(R.string.accessibility_quick_settings_dnd_changed_off);
+ }
+ }
+
+ @Override
+ public void setListening(boolean listening) {
+ if (mListening == listening) return;
+ mListening = listening;
+ if (mListening) {
+ mController.addCallback(mZenCallback);
+ } else {
+ mController.removeCallback(mZenCallback);
+ }
+ }
+
+ private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
+ public void onZenChanged(int zen) {
+ refreshState(zen);
+ }
+ };
+
+ private static SharedPreferences getSharedPrefs(Context context) {
+ return context.getSharedPreferences(context.getPackageName(), 0);
+ }
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mVisible = intent.getBooleanExtra(EXTRA_VISIBLE, false);
+ getSharedPrefs(mContext).edit().putBoolean(PREF_KEY_VISIBLE, mVisible).commit();
+ refreshState();
+ }
+ };
+
+ private final class DndDetailAdapter implements DetailAdapter, OnAttachStateChangeListener {
+
+ @Override
+ public int getTitle() {
+ return R.string.quick_settings_dnd_label;
+ }
+
+ @Override
+ public Boolean getToggleState() {
+ return mState.value;
+ }
+
+ @Override
+ public Intent getSettingsIntent() {
+ return ZEN_SETTINGS;
+ }
+
+ @Override
+ public void setToggleState(boolean state) {
+ if (!state) {
+ mController.setZen(Global.ZEN_MODE_OFF);
+ showDetail(false);
+ }
+ }
+
+ @Override
+ public View createDetailView(Context context, View convertView, ViewGroup parent) {
+ final ZenModePanel zmp = convertView != null ? (ZenModePanel) convertView
+ : (ZenModePanel) LayoutInflater.from(context).inflate(
+ R.layout.zen_mode_panel, parent, false);
+ if (convertView == null) {
+ zmp.init(mController);
+ zmp.setEmbedded(true);
+ zmp.addOnAttachStateChangeListener(this);
+ }
+ return zmp;
+ }
+
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ mShowingDetail = true;
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ mShowingDetail = false;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
index 5c1a317..cb78deb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
@@ -17,7 +17,6 @@
package com.android.systemui.qs.tiles;
import android.app.ActivityManager;
-import android.os.SystemClock;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
@@ -27,16 +26,11 @@ import com.android.systemui.statusbar.policy.FlashlightController;
public class FlashlightTile extends QSTile<QSTile.BooleanState> implements
FlashlightController.FlashlightListener {
- /** Grace period for which we consider the flashlight
- * still available because it was recently on. */
- private static final long RECENTLY_ON_DURATION_MILLIS = 500;
-
private final AnimationIcon mEnable
= new AnimationIcon(R.drawable.ic_signal_flashlight_enable_animation);
private final AnimationIcon mDisable
= new AnimationIcon(R.drawable.ic_signal_flashlight_disable_animation);
private final FlashlightController mFlashlightController;
- private long mWasLastOn;
public FlashlightTile(Host host) {
super(host);
@@ -69,33 +63,17 @@ public class FlashlightTile extends QSTile<QSTile.BooleanState> implements
return;
}
boolean newState = !mState.value;
- mFlashlightController.setFlashlight(newState);
refreshState(newState ? UserBoolean.USER_TRUE : UserBoolean.USER_FALSE);
+ mFlashlightController.setFlashlight(newState);
}
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
- if (state.value) {
- mWasLastOn = SystemClock.uptimeMillis();
- }
-
+ state.visible = mFlashlightController.isAvailable();
+ state.label = mHost.getContext().getString(R.string.quick_settings_flashlight_label);
if (arg instanceof UserBoolean) {
state.value = ((UserBoolean) arg).value;
}
-
- if (!state.value && mWasLastOn != 0) {
- if (SystemClock.uptimeMillis() > mWasLastOn + RECENTLY_ON_DURATION_MILLIS) {
- mWasLastOn = 0;
- } else {
- mHandler.removeCallbacks(mRecentlyOnTimeout);
- mHandler.postAtTime(mRecentlyOnTimeout, mWasLastOn + RECENTLY_ON_DURATION_MILLIS);
- }
- }
-
- // Always show the tile when the flashlight is or was recently on. This is needed because
- // the camera is not available while it is being used for the flashlight.
- state.visible = mWasLastOn != 0 || mFlashlightController.isAvailable();
- state.label = mHost.getContext().getString(R.string.quick_settings_flashlight_label);
final AnimationIcon icon = state.value ? mEnable : mDisable;
icon.setAllowAnimation(arg instanceof UserBoolean && ((UserBoolean) arg).userInitiated);
state.icon = icon;
@@ -115,8 +93,8 @@ public class FlashlightTile extends QSTile<QSTile.BooleanState> implements
}
@Override
- public void onFlashlightOff() {
- refreshState(UserBoolean.BACKGROUND_FALSE);
+ public void onFlashlightChanged(boolean enabled) {
+ refreshState(enabled ? UserBoolean.BACKGROUND_TRUE : UserBoolean.BACKGROUND_FALSE);
}
@Override
@@ -128,11 +106,4 @@ public class FlashlightTile extends QSTile<QSTile.BooleanState> implements
public void onFlashlightAvailabilityChanged(boolean available) {
refreshState();
}
-
- private Runnable mRecentlyOnTimeout = new Runnable() {
- @Override
- public void run() {
- refreshState();
- }
- };
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
index c55cbcc..21c5c96 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
@@ -28,7 +28,6 @@ import android.graphics.Bitmap;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
-import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index e09024b..70746c7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -16,6 +16,8 @@
package com.android.systemui.qs.tiles;
+import java.util.List;
+
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
@@ -24,16 +26,15 @@ import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
+import com.android.settingslib.wifi.AccessPoint;
import com.android.systemui.R;
import com.android.systemui.qs.QSDetailItems;
import com.android.systemui.qs.QSDetailItems.Item;
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.QSTileView;
import com.android.systemui.qs.SignalTileView;
-import com.android.systemui.statusbar.phone.QSTileHost;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NetworkController.AccessPointController;
-import com.android.systemui.statusbar.policy.NetworkController.AccessPointController.AccessPoint;
import com.android.systemui.statusbar.policy.NetworkController.NetworkSignalChangedCallback;
/** Quick settings tile: Wifi **/
@@ -282,10 +283,10 @@ public class WifiTile extends QSTile<QSTile.SignalState> {
}
@Override
- public void onAccessPointsChanged(final AccessPoint[] accessPoints) {
- mAccessPoints = accessPoints;
+ public void onAccessPointsChanged(final List<AccessPoint> accessPoints) {
+ mAccessPoints = accessPoints.toArray(new AccessPoint[accessPoints.size()]);
updateItems();
- if (accessPoints != null && accessPoints.length > 0) {
+ if (accessPoints != null && accessPoints.size() > 0) {
fireScanStateChanged(false);
}
}
@@ -299,7 +300,7 @@ public class WifiTile extends QSTile<QSTile.SignalState> {
public void onDetailItemClick(Item item) {
if (item == null || item.tag == null) return;
final AccessPoint ap = (AccessPoint) item.tag;
- if (!ap.isConnected) {
+ if (!ap.isActive()) {
if (mWifiController.connect(ap)) {
mHost.collapsePanels();
}
@@ -326,16 +327,10 @@ public class WifiTile extends QSTile<QSTile.SignalState> {
final AccessPoint ap = mAccessPoints[i];
final Item item = new Item();
item.tag = ap;
- item.icon = ap.iconId;
- item.line1 = ap.ssid;
- if (ap.isConnected) {
- item.line2 = mContext.getString(ap.isConfigured ?
- R.string.quick_settings_connected :
- R.string.quick_settings_connected_via_wfa);
- } else if (ap.networkId >= 0) {
- // TODO: Set line 2 to wifi saved string here.
- }
- item.overlay = ap.hasSecurity
+ item.icon = mWifiController.getIcon(ap);
+ item.line1 = ap.getSsid();
+ item.line2 = ap.getSummary();
+ item.overlay = ap.getSecurity() != AccessPoint.SECURITY_NONE
? mContext.getDrawable(R.drawable.qs_ic_wifi_lock)
: null;
items[i] = item;
diff --git a/packages/SystemUI/src/com/android/systemui/recent/ColorDrawableWithDimensions.java b/packages/SystemUI/src/com/android/systemui/recent/ColorDrawableWithDimensions.java
deleted file mode 100644
index b4d3edd..0000000
--- a/packages/SystemUI/src/com/android/systemui/recent/ColorDrawableWithDimensions.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recent;
-
-import android.graphics.drawable.ColorDrawable;
-
-public class ColorDrawableWithDimensions extends ColorDrawable {
- private int mWidth;
- private int mHeight;
-
- public ColorDrawableWithDimensions(int color, int width, int height) {
- super(color);
- mWidth = width;
- mHeight = height;
- }
-
- @Override
- public int getIntrinsicWidth() {
- return mWidth;
- }
-
- @Override
- public int getIntrinsicHeight() {
- return mHeight;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/Constants.java b/packages/SystemUI/src/com/android/systemui/recent/Constants.java
deleted file mode 100644
index 8252a9f..0000000
--- a/packages/SystemUI/src/com/android/systemui/recent/Constants.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recent;
-
-public class Constants {
- static final int MAX_ESCAPE_ANIMATION_DURATION = 500; // in ms
- static final int SNAP_BACK_DURATION = 250; // in ms
- static final int ESCAPE_VELOCITY = 100; // speed of item required to "curate" it in dp/s
- public static float ALPHA_FADE_START = 0.8f; // fraction of thumbnail width where fade starts
- static final float ALPHA_FADE_END = 0.5f; // fraction of thumbnail width beyond which alpha->0
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/FadedEdgeDrawHelper.java b/packages/SystemUI/src/com/android/systemui/recent/FadedEdgeDrawHelper.java
deleted file mode 100644
index 59f7a80..0000000
--- a/packages/SystemUI/src/com/android/systemui/recent/FadedEdgeDrawHelper.java
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recent;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.LinearGradient;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.Shader;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.widget.LinearLayout;
-
-import com.android.systemui.R;
-
-public class FadedEdgeDrawHelper {
- public static final boolean OPTIMIZE_SW_RENDERED_RECENTS = true;
- public static final boolean USE_DARK_FADE_IN_HW_ACCELERATED_MODE = true;
- private View mScrollView;
-
- private int mFadingEdgeLength;
- private boolean mIsVertical;
- private boolean mSoftwareRendered = false;
- private Paint mBlackPaint;
- private Paint mFadePaint;
- private Matrix mFadeMatrix;
- private LinearGradient mFade;
-
- public static FadedEdgeDrawHelper create(Context context,
- AttributeSet attrs, View scrollView, boolean isVertical) {
- boolean isTablet = context.getResources().
- getBoolean(R.bool.config_recents_interface_for_tablets);
- if (!isTablet && (OPTIMIZE_SW_RENDERED_RECENTS || USE_DARK_FADE_IN_HW_ACCELERATED_MODE)) {
- return new FadedEdgeDrawHelper(context, attrs, scrollView, isVertical);
- } else {
- return null;
- }
- }
-
- public FadedEdgeDrawHelper(Context context,
- AttributeSet attrs, View scrollView, boolean isVertical) {
- mScrollView = scrollView;
- TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View);
- mFadingEdgeLength = a.getDimensionPixelSize(android.R.styleable.View_fadingEdgeLength,
- ViewConfiguration.get(context).getScaledFadingEdgeLength());
- mIsVertical = isVertical;
- }
-
- public void onAttachedToWindowCallback(
- LinearLayout layout, boolean hardwareAccelerated) {
- mSoftwareRendered = !hardwareAccelerated;
- if ((mSoftwareRendered && OPTIMIZE_SW_RENDERED_RECENTS)
- || USE_DARK_FADE_IN_HW_ACCELERATED_MODE) {
- mScrollView.setVerticalFadingEdgeEnabled(false);
- mScrollView.setHorizontalFadingEdgeEnabled(false);
- }
- }
-
- public void addViewCallback(View newLinearLayoutChild) {
- if (mSoftwareRendered && OPTIMIZE_SW_RENDERED_RECENTS) {
- final RecentsPanelView.ViewHolder holder =
- (RecentsPanelView.ViewHolder) newLinearLayoutChild.getTag();
- holder.labelView.setDrawingCacheEnabled(true);
- holder.labelView.buildDrawingCache();
- }
- }
-
- public void drawCallback(Canvas canvas,
- int left, int right, int top, int bottom, int scrollX, int scrollY,
- float topFadingEdgeStrength, float bottomFadingEdgeStrength,
- float leftFadingEdgeStrength, float rightFadingEdgeStrength, int mPaddingTop) {
-
- if ((mSoftwareRendered && OPTIMIZE_SW_RENDERED_RECENTS)
- || USE_DARK_FADE_IN_HW_ACCELERATED_MODE) {
- if (mFadePaint == null) {
- mFadePaint = new Paint();
- mFadeMatrix = new Matrix();
- // use use a height of 1, and then wack the matrix each time we
- // actually use it.
- mFade = new LinearGradient(0, 0, 0, 1, 0xCC000000, 0, Shader.TileMode.CLAMP);
- // PULL OUT THIS CONSTANT
- mFadePaint.setShader(mFade);
- }
-
- // draw the fade effect
- boolean drawTop = false;
- boolean drawBottom = false;
- boolean drawLeft = false;
- boolean drawRight = false;
-
- float topFadeStrength = 0.0f;
- float bottomFadeStrength = 0.0f;
- float leftFadeStrength = 0.0f;
- float rightFadeStrength = 0.0f;
-
- final float fadeHeight = mFadingEdgeLength;
- int length = (int) fadeHeight;
-
- // clip the fade length if top and bottom fades overlap
- // overlapping fades produce odd-looking artifacts
- if (mIsVertical && (top + length > bottom - length)) {
- length = (bottom - top) / 2;
- }
-
- // also clip horizontal fades if necessary
- if (!mIsVertical && (left + length > right - length)) {
- length = (right - left) / 2;
- }
-
- if (mIsVertical) {
- topFadeStrength = Math.max(0.0f, Math.min(1.0f, topFadingEdgeStrength));
- drawTop = topFadeStrength * fadeHeight > 1.0f;
- bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, bottomFadingEdgeStrength));
- drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
- }
-
- if (!mIsVertical) {
- leftFadeStrength = Math.max(0.0f, Math.min(1.0f, leftFadingEdgeStrength));
- drawLeft = leftFadeStrength * fadeHeight > 1.0f;
- rightFadeStrength = Math.max(0.0f, Math.min(1.0f, rightFadingEdgeStrength));
- drawRight = rightFadeStrength * fadeHeight > 1.0f;
- }
-
- if (drawTop) {
- mFadeMatrix.setScale(1, fadeHeight * topFadeStrength);
- mFadeMatrix.postTranslate(left, top);
- mFade.setLocalMatrix(mFadeMatrix);
- mFadePaint.setShader(mFade);
- canvas.drawRect(left, top, right, top + length, mFadePaint);
-
- if (mBlackPaint == null) {
- // Draw under the status bar at the top
- mBlackPaint = new Paint();
- mBlackPaint.setColor(0xFF000000);
- }
- canvas.drawRect(left, top - mPaddingTop, right, top, mBlackPaint);
- }
-
- if (drawBottom) {
- mFadeMatrix.setScale(1, fadeHeight * bottomFadeStrength);
- mFadeMatrix.postRotate(180);
- mFadeMatrix.postTranslate(left, bottom);
- mFade.setLocalMatrix(mFadeMatrix);
- mFadePaint.setShader(mFade);
- canvas.drawRect(left, bottom - length, right, bottom, mFadePaint);
- }
-
- if (drawLeft) {
- mFadeMatrix.setScale(1, fadeHeight * leftFadeStrength);
- mFadeMatrix.postRotate(-90);
- mFadeMatrix.postTranslate(left, top);
- mFade.setLocalMatrix(mFadeMatrix);
- mFadePaint.setShader(mFade);
- canvas.drawRect(left, top, left + length, bottom, mFadePaint);
- }
-
- if (drawRight) {
- mFadeMatrix.setScale(1, fadeHeight * rightFadeStrength);
- mFadeMatrix.postRotate(90);
- mFadeMatrix.postTranslate(right, top);
- mFade.setLocalMatrix(mFadeMatrix);
- mFadePaint.setShader(mFade);
- canvas.drawRect(right - length, top, right, bottom, mFadePaint);
- }
- }
- }
-
- public int getVerticalFadingEdgeLength() {
- return mFadingEdgeLength;
- }
-
- public int getHorizontalFadingEdgeLength() {
- return mFadingEdgeLength;
- }
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/FirstFrameAnimatorHelper.java b/packages/SystemUI/src/com/android/systemui/recent/FirstFrameAnimatorHelper.java
deleted file mode 100644
index 84d13cf..0000000
--- a/packages/SystemUI/src/com/android/systemui/recent/FirstFrameAnimatorHelper.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recent;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewPropertyAnimator;
-import android.view.ViewTreeObserver;
-
-/*
- * This is a helper class that listens to updates from the corresponding animation.
- * For the first two frames, it adjusts the current play time of the animation to
- * prevent jank at the beginning of the animation
- */
-public class FirstFrameAnimatorHelper extends AnimatorListenerAdapter
- implements ValueAnimator.AnimatorUpdateListener {
- private static final boolean DEBUG = false;
- private static final int MAX_DELAY = 1000;
- private static final int IDEAL_FRAME_DURATION = 16;
- private View mTarget;
- private long mStartFrame;
- private long mStartTime = -1;
- private boolean mHandlingOnAnimationUpdate;
- private boolean mAdjustedSecondFrameTime;
-
- private static ViewTreeObserver.OnDrawListener sGlobalDrawListener;
- private static long sGlobalFrameCounter;
-
- public FirstFrameAnimatorHelper(ValueAnimator animator, View target) {
- mTarget = target;
- animator.addUpdateListener(this);
- }
-
- public FirstFrameAnimatorHelper(ViewPropertyAnimator vpa, View target) {
- mTarget = target;
- vpa.setListener(this);
- }
-
- // only used for ViewPropertyAnimators
- public void onAnimationStart(Animator animation) {
- final ValueAnimator va = (ValueAnimator) animation;
- va.addUpdateListener(FirstFrameAnimatorHelper.this);
- onAnimationUpdate(va);
- }
-
- public static void initializeDrawListener(View view) {
- if (sGlobalDrawListener != null) {
- view.getViewTreeObserver().removeOnDrawListener(sGlobalDrawListener);
- }
- sGlobalDrawListener = new ViewTreeObserver.OnDrawListener() {
- private long mTime = System.currentTimeMillis();
- public void onDraw() {
- sGlobalFrameCounter++;
- if (DEBUG) {
- long newTime = System.currentTimeMillis();
- Log.d("FirstFrameAnimatorHelper", "TICK " + (newTime - mTime));
- mTime = newTime;
- }
- }
- };
- view.getViewTreeObserver().addOnDrawListener(sGlobalDrawListener);
- }
-
- public void onAnimationUpdate(final ValueAnimator animation) {
- final long currentTime = System.currentTimeMillis();
- if (mStartTime == -1) {
- mStartFrame = sGlobalFrameCounter;
- mStartTime = currentTime;
- }
-
- if (!mHandlingOnAnimationUpdate &&
- // If the current play time exceeds the duration, the animation
- // will get finished, even if we call setCurrentPlayTime -- therefore
- // don't adjust the animation in that case
- animation.getCurrentPlayTime() < animation.getDuration()) {
- mHandlingOnAnimationUpdate = true;
- long frameNum = sGlobalFrameCounter - mStartFrame;
- // If we haven't drawn our first frame, reset the time to t = 0
- // (give up after MAX_DELAY ms of waiting though - might happen, for example, if we
- // are no longer in the foreground and no frames are being rendered ever)
- if (frameNum == 0 && currentTime < mStartTime + MAX_DELAY) {
- // The first frame on animations doesn't always trigger an invalidate...
- // force an invalidate here to make sure the animation continues to advance
- mTarget.getRootView().invalidate();
- animation.setCurrentPlayTime(0);
-
- // For the second frame, if the first frame took more than 16ms,
- // adjust the start time and pretend it took only 16ms anyway. This
- // prevents a large jump in the animation due to an expensive first frame
- } else if (frameNum == 1 && currentTime < mStartTime + MAX_DELAY &&
- !mAdjustedSecondFrameTime &&
- currentTime > mStartTime + IDEAL_FRAME_DURATION) {
- animation.setCurrentPlayTime(IDEAL_FRAME_DURATION);
- mAdjustedSecondFrameTime = true;
- } else {
- if (frameNum > 1) {
- mTarget.post(new Runnable() {
- public void run() {
- animation.removeUpdateListener(FirstFrameAnimatorHelper.this);
- }
- });
- }
- if (DEBUG) print(animation);
- }
- mHandlingOnAnimationUpdate = false;
- } else {
- if (DEBUG) print(animation);
- }
- }
-
- public void print(ValueAnimator animation) {
- float flatFraction = animation.getCurrentPlayTime() / (float) animation.getDuration();
- Log.d("FirstFrameAnimatorHelper", sGlobalFrameCounter +
- "(" + (sGlobalFrameCounter - mStartFrame) + ") " + mTarget + " dirty? " +
- mTarget.isDirty() + " " + flatFraction + " " + this + " " + animation);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java b/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java
deleted file mode 100644
index 34430d9..0000000
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java
+++ /dev/null
@@ -1,586 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recent;
-
-import android.app.ActivityManager;
-import android.app.AppGlobals;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.AsyncTask;
-import android.os.Handler;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-
-import com.android.systemui.R;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-
-public class RecentTasksLoader implements View.OnTouchListener {
- static final String TAG = "RecentTasksLoader";
- static final boolean DEBUG = PhoneStatusBar.DEBUG || false;
-
- private static final int DISPLAY_TASKS = 20;
- private static final int MAX_TASKS = DISPLAY_TASKS + 1; // allow extra for non-apps
-
- private Context mContext;
- private RecentsPanelView mRecentsPanel;
-
- private Object mFirstTaskLock = new Object();
- private TaskDescription mFirstTask;
- private boolean mFirstTaskLoaded;
-
- private AsyncTask<Void, ArrayList<TaskDescription>, Void> mTaskLoader;
- private AsyncTask<Void, TaskDescription, Void> mThumbnailLoader;
- private Handler mHandler;
-
- private int mIconDpi;
- private ColorDrawableWithDimensions mDefaultThumbnailBackground;
- private ColorDrawableWithDimensions mDefaultIconBackground;
- private int mNumTasksInFirstScreenful = Integer.MAX_VALUE;
-
- private boolean mFirstScreenful;
- private ArrayList<TaskDescription> mLoadedTasks;
-
- private enum State { LOADING, LOADED, CANCELLED };
- private State mState = State.CANCELLED;
-
-
- private static RecentTasksLoader sInstance;
- public static RecentTasksLoader getInstance(Context context) {
- if (sInstance == null) {
- sInstance = new RecentTasksLoader(context);
- }
- return sInstance;
- }
-
- private RecentTasksLoader(Context context) {
- mContext = context;
- mHandler = new Handler();
-
- final Resources res = context.getResources();
-
- // get the icon size we want -- on tablets, we use bigger icons
- boolean isTablet = res.getBoolean(R.bool.config_recents_interface_for_tablets);
- if (isTablet) {
- ActivityManager activityManager =
- (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
- mIconDpi = activityManager.getLauncherLargeIconDensity();
- } else {
- mIconDpi = res.getDisplayMetrics().densityDpi;
- }
-
- // Render default icon (just a blank image)
- int defaultIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.app_icon_size);
- int iconSize = (int) (defaultIconSize * mIconDpi / res.getDisplayMetrics().densityDpi);
- mDefaultIconBackground = new ColorDrawableWithDimensions(0x00000000, iconSize, iconSize);
-
- // Render the default thumbnail background
- int thumbnailWidth =
- (int) res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_width);
- int thumbnailHeight =
- (int) res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_height);
- int color = res.getColor(R.drawable.status_bar_recents_app_thumbnail_background);
-
- mDefaultThumbnailBackground =
- new ColorDrawableWithDimensions(color, thumbnailWidth, thumbnailHeight);
- }
-
- public void setRecentsPanel(RecentsPanelView newRecentsPanel, RecentsPanelView caller) {
- // Only allow clearing mRecentsPanel if the caller is the current recentsPanel
- if (newRecentsPanel != null || mRecentsPanel == caller) {
- mRecentsPanel = newRecentsPanel;
- if (mRecentsPanel != null) {
- mNumTasksInFirstScreenful = mRecentsPanel.numItemsInOneScreenful();
- }
- }
- }
-
- public Drawable getDefaultThumbnail() {
- return mDefaultThumbnailBackground;
- }
-
- public Drawable getDefaultIcon() {
- return mDefaultIconBackground;
- }
-
- public ArrayList<TaskDescription> getLoadedTasks() {
- return mLoadedTasks;
- }
-
- public void remove(TaskDescription td) {
- mLoadedTasks.remove(td);
- }
-
- public boolean isFirstScreenful() {
- return mFirstScreenful;
- }
-
- private boolean isCurrentHomeActivity(ComponentName component, ActivityInfo homeInfo) {
- if (homeInfo == null) {
- final PackageManager pm = mContext.getPackageManager();
- homeInfo = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
- .resolveActivityInfo(pm, 0);
- }
- return homeInfo != null
- && homeInfo.packageName.equals(component.getPackageName())
- && homeInfo.name.equals(component.getClassName());
- }
-
- // Create an TaskDescription, returning null if the title or icon is null
- TaskDescription createTaskDescription(int taskId, int persistentTaskId, Intent baseIntent,
- ComponentName origActivity, CharSequence description, int userId) {
- Intent intent = new Intent(baseIntent);
- if (origActivity != null) {
- intent.setComponent(origActivity);
- }
- final PackageManager pm = mContext.getPackageManager();
- final IPackageManager ipm = AppGlobals.getPackageManager();
- intent.setFlags((intent.getFlags()&~Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
- | Intent.FLAG_ACTIVITY_NEW_TASK);
- ResolveInfo resolveInfo = null;
- try {
- resolveInfo = ipm.resolveIntent(intent, null, 0, userId);
- } catch (RemoteException re) {
- }
- if (resolveInfo != null) {
- final ActivityInfo info = resolveInfo.activityInfo;
- final String title = info.loadLabel(pm).toString();
-
- if (title != null && title.length() > 0) {
- if (DEBUG) Log.v(TAG, "creating activity desc for id="
- + persistentTaskId + ", label=" + title);
-
- TaskDescription item = new TaskDescription(taskId,
- persistentTaskId, resolveInfo, baseIntent, info.packageName,
- description, userId);
- item.setLabel(title);
-
- return item;
- } else {
- if (DEBUG) Log.v(TAG, "SKIPPING item " + persistentTaskId);
- }
- }
- return null;
- }
-
- void loadThumbnailAndIcon(TaskDescription td) {
- final ActivityManager am = (ActivityManager)
- mContext.getSystemService(Context.ACTIVITY_SERVICE);
- final PackageManager pm = mContext.getPackageManager();
- final Bitmap thumbnail = SystemServicesProxy.getThumbnail(am, td.persistentTaskId);
- Drawable icon = getFullResIcon(td.resolveInfo, pm);
- if (td.userId != UserHandle.myUserId()) {
- // Need to badge the icon
- icon = mContext.getPackageManager().getUserBadgedIcon(icon, new UserHandle(td.userId));
- }
- if (DEBUG) Log.v(TAG, "Loaded bitmap for task "
- + td + ": " + thumbnail);
- synchronized (td) {
- if (thumbnail != null) {
- td.setThumbnail(new BitmapDrawable(mContext.getResources(), thumbnail));
- } else {
- td.setThumbnail(mDefaultThumbnailBackground);
- }
- if (icon != null) {
- td.setIcon(icon);
- }
- td.setLoaded(true);
- }
- }
-
- Drawable getFullResDefaultActivityIcon() {
- return getFullResIcon(Resources.getSystem(),
- com.android.internal.R.mipmap.sym_def_app_icon);
- }
-
- Drawable getFullResIcon(Resources resources, int iconId) {
- try {
- return resources.getDrawableForDensity(iconId, mIconDpi);
- } catch (Resources.NotFoundException e) {
- return getFullResDefaultActivityIcon();
- }
- }
-
- private Drawable getFullResIcon(ResolveInfo info, PackageManager packageManager) {
- Resources resources;
- try {
- resources = packageManager.getResourcesForApplication(
- info.activityInfo.applicationInfo);
- } catch (PackageManager.NameNotFoundException e) {
- resources = null;
- }
- if (resources != null) {
- int iconId = info.activityInfo.getIconResource();
- if (iconId != 0) {
- return getFullResIcon(resources, iconId);
- }
- }
- return getFullResDefaultActivityIcon();
- }
-
- Runnable mPreloadTasksRunnable = new Runnable() {
- public void run() {
- loadTasksInBackground();
- }
- };
-
- // additional optimization when we have software system buttons - start loading the recent
- // tasks on touch down
- @Override
- public boolean onTouch(View v, MotionEvent ev) {
- int action = ev.getAction() & MotionEvent.ACTION_MASK;
- if (action == MotionEvent.ACTION_DOWN) {
- preloadRecentTasksList();
- } else if (action == MotionEvent.ACTION_CANCEL) {
- cancelPreloadingRecentTasksList();
- } else if (action == MotionEvent.ACTION_UP) {
- // Remove the preloader if we haven't called it yet
- mHandler.removeCallbacks(mPreloadTasksRunnable);
- if (!v.isPressed()) {
- cancelLoadingThumbnailsAndIcons();
- }
-
- }
- return false;
- }
-
- public void preloadRecentTasksList() {
- mHandler.post(mPreloadTasksRunnable);
- }
-
- public void cancelPreloadingRecentTasksList() {
- cancelLoadingThumbnailsAndIcons();
- mHandler.removeCallbacks(mPreloadTasksRunnable);
- }
-
- public void cancelLoadingThumbnailsAndIcons(RecentsPanelView caller) {
- // Only oblige this request if it comes from the current RecentsPanel
- // (eg when you rotate, the old RecentsPanel request should be ignored)
- if (mRecentsPanel == caller) {
- cancelLoadingThumbnailsAndIcons();
- }
- }
-
-
- private void cancelLoadingThumbnailsAndIcons() {
- if (mRecentsPanel != null && mRecentsPanel.isShowing()) {
- return;
- }
-
- if (mTaskLoader != null) {
- mTaskLoader.cancel(false);
- mTaskLoader = null;
- }
- if (mThumbnailLoader != null) {
- mThumbnailLoader.cancel(false);
- mThumbnailLoader = null;
- }
- mLoadedTasks = null;
- if (mRecentsPanel != null) {
- mRecentsPanel.onTaskLoadingCancelled();
- }
- mFirstScreenful = false;
- mState = State.CANCELLED;
- }
-
- private void clearFirstTask() {
- synchronized (mFirstTaskLock) {
- mFirstTask = null;
- mFirstTaskLoaded = false;
- }
- }
-
- public void preloadFirstTask() {
- Thread bgLoad = new Thread() {
- public void run() {
- TaskDescription first = loadFirstTask();
- synchronized(mFirstTaskLock) {
- if (mCancelPreloadingFirstTask) {
- clearFirstTask();
- } else {
- mFirstTask = first;
- mFirstTaskLoaded = true;
- }
- mPreloadingFirstTask = false;
- }
- }
- };
- synchronized(mFirstTaskLock) {
- if (!mPreloadingFirstTask) {
- clearFirstTask();
- mPreloadingFirstTask = true;
- bgLoad.start();
- }
- }
- }
-
- public void cancelPreloadingFirstTask() {
- synchronized(mFirstTaskLock) {
- if (mPreloadingFirstTask) {
- mCancelPreloadingFirstTask = true;
- } else {
- clearFirstTask();
- }
- }
- }
-
- boolean mPreloadingFirstTask;
- boolean mCancelPreloadingFirstTask;
- public TaskDescription getFirstTask() {
- while(true) {
- synchronized(mFirstTaskLock) {
- if (mFirstTaskLoaded) {
- return mFirstTask;
- } else if (!mFirstTaskLoaded && !mPreloadingFirstTask) {
- mFirstTask = loadFirstTask();
- mFirstTaskLoaded = true;
- return mFirstTask;
- }
- }
- try {
- Thread.sleep(3);
- } catch (InterruptedException e) {
- }
- }
- }
-
- public TaskDescription loadFirstTask() {
- final ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
-
- final List<ActivityManager.RecentTaskInfo> recentTasks = am.getRecentTasksForUser(1,
- ActivityManager.RECENT_IGNORE_UNAVAILABLE | ActivityManager.RECENT_INCLUDE_PROFILES,
- UserHandle.CURRENT.getIdentifier());
- TaskDescription item = null;
- if (recentTasks.size() > 0) {
- ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(0);
-
- Intent intent = new Intent(recentInfo.baseIntent);
- if (recentInfo.origActivity != null) {
- intent.setComponent(recentInfo.origActivity);
- }
-
- // Don't load the current home activity.
- if (isCurrentHomeActivity(intent.getComponent(), null)) {
- return null;
- }
-
- // Don't load ourselves
- if (intent.getComponent().getPackageName().equals(mContext.getPackageName())) {
- return null;
- }
-
- item = createTaskDescription(recentInfo.id,
- recentInfo.persistentId, recentInfo.baseIntent,
- recentInfo.origActivity, recentInfo.description,
- recentInfo.userId);
- if (item != null) {
- loadThumbnailAndIcon(item);
- }
- return item;
- }
- return null;
- }
-
- public void loadTasksInBackground() {
- loadTasksInBackground(false);
- }
- public void loadTasksInBackground(final boolean zeroeth) {
- if (mState != State.CANCELLED) {
- return;
- }
- mState = State.LOADING;
- mFirstScreenful = true;
-
- final LinkedBlockingQueue<TaskDescription> tasksWaitingForThumbnails =
- new LinkedBlockingQueue<TaskDescription>();
- mTaskLoader = new AsyncTask<Void, ArrayList<TaskDescription>, Void>() {
- @Override
- protected void onProgressUpdate(ArrayList<TaskDescription>... values) {
- if (!isCancelled()) {
- ArrayList<TaskDescription> newTasks = values[0];
- // do a callback to RecentsPanelView to let it know we have more values
- // how do we let it know we're all done? just always call back twice
- if (mRecentsPanel != null) {
- mRecentsPanel.onTasksLoaded(newTasks, mFirstScreenful);
- }
- if (mLoadedTasks == null) {
- mLoadedTasks = new ArrayList<TaskDescription>();
- }
- mLoadedTasks.addAll(newTasks);
- mFirstScreenful = false;
- }
- }
- @Override
- protected Void doInBackground(Void... params) {
- // We load in two stages: first, we update progress with just the first screenful
- // of items. Then, we update with the rest of the items
- final int origPri = Process.getThreadPriority(Process.myTid());
- Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- final PackageManager pm = mContext.getPackageManager();
- final ActivityManager am = (ActivityManager)
- mContext.getSystemService(Context.ACTIVITY_SERVICE);
-
- final List<ActivityManager.RecentTaskInfo> recentTasks =
- am.getRecentTasks(MAX_TASKS, ActivityManager.RECENT_IGNORE_UNAVAILABLE
- | ActivityManager.RECENT_INCLUDE_PROFILES);
- int numTasks = recentTasks.size();
- ActivityInfo homeInfo = new Intent(Intent.ACTION_MAIN)
- .addCategory(Intent.CATEGORY_HOME).resolveActivityInfo(pm, 0);
-
- boolean firstScreenful = true;
- ArrayList<TaskDescription> tasks = new ArrayList<TaskDescription>();
-
- // skip the first task - assume it's either the home screen or the current activity.
- final int first = 0;
- for (int i = first, index = 0; i < numTasks && (index < MAX_TASKS); ++i) {
- if (isCancelled()) {
- break;
- }
- final ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(i);
-
- Intent intent = new Intent(recentInfo.baseIntent);
- if (recentInfo.origActivity != null) {
- intent.setComponent(recentInfo.origActivity);
- }
-
- // Don't load the current home activity.
- if (isCurrentHomeActivity(intent.getComponent(), homeInfo)) {
- continue;
- }
-
- // Don't load ourselves
- if (intent.getComponent().getPackageName().equals(mContext.getPackageName())) {
- continue;
- }
-
- TaskDescription item = createTaskDescription(recentInfo.id,
- recentInfo.persistentId, recentInfo.baseIntent,
- recentInfo.origActivity, recentInfo.description,
- recentInfo.userId);
-
- if (item != null) {
- while (true) {
- try {
- tasksWaitingForThumbnails.put(item);
- break;
- } catch (InterruptedException e) {
- }
- }
- tasks.add(item);
- if (firstScreenful && tasks.size() == mNumTasksInFirstScreenful) {
- publishProgress(tasks);
- tasks = new ArrayList<TaskDescription>();
- firstScreenful = false;
- //break;
- }
- ++index;
- }
- }
-
- if (!isCancelled()) {
- publishProgress(tasks);
- if (firstScreenful) {
- // always should publish two updates
- publishProgress(new ArrayList<TaskDescription>());
- }
- }
-
- while (true) {
- try {
- tasksWaitingForThumbnails.put(new TaskDescription());
- break;
- } catch (InterruptedException e) {
- }
- }
-
- Process.setThreadPriority(origPri);
- return null;
- }
- };
- mTaskLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- loadThumbnailsAndIconsInBackground(tasksWaitingForThumbnails);
- }
-
- private void loadThumbnailsAndIconsInBackground(
- final BlockingQueue<TaskDescription> tasksWaitingForThumbnails) {
- // continually read items from tasksWaitingForThumbnails and load
- // thumbnails and icons for them. finish thread when cancelled or there
- // is a null item in tasksWaitingForThumbnails
- mThumbnailLoader = new AsyncTask<Void, TaskDescription, Void>() {
- @Override
- protected void onProgressUpdate(TaskDescription... values) {
- if (!isCancelled()) {
- TaskDescription td = values[0];
- if (td.isNull()) { // end sentinel
- mState = State.LOADED;
- } else {
- if (mRecentsPanel != null) {
- mRecentsPanel.onTaskThumbnailLoaded(td);
- }
- }
- }
- }
- @Override
- protected Void doInBackground(Void... params) {
- final int origPri = Process.getThreadPriority(Process.myTid());
- Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
-
- while (true) {
- if (isCancelled()) {
- break;
- }
- TaskDescription td = null;
- while (td == null) {
- try {
- td = tasksWaitingForThumbnails.take();
- } catch (InterruptedException e) {
- }
- }
- if (td.isNull()) { // end sentinel
- publishProgress(td);
- break;
- }
- loadThumbnailAndIcon(td);
-
- publishProgress(td);
- }
-
- Process.setThreadPriority(origPri);
- return null;
- }
- };
- mThumbnailLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/Recents.java b/packages/SystemUI/src/com/android/systemui/recent/Recents.java
deleted file mode 100644
index e9f3cf9..0000000
--- a/packages/SystemUI/src/com/android/systemui/recent/Recents.java
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recent;
-
-import android.app.ActivityOptions;
-import android.content.ActivityNotFoundException;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.Display;
-import android.view.View;
-import com.android.systemui.R;
-import com.android.systemui.RecentsComponent;
-import com.android.systemui.SystemUI;
-import com.android.systemui.recents.AlternateRecentsComponent;
-
-
-public class Recents extends SystemUI implements RecentsComponent {
- private static final String TAG = "Recents";
- private static final boolean DEBUG = true;
-
- // Which recents to use
- boolean mUseAlternateRecents = true;
- boolean mBootCompleted = false;
- static AlternateRecentsComponent sAlternateRecents;
-
- /** Returns the Recents component, creating a new one in-process if necessary. */
- public static AlternateRecentsComponent getRecentsComponent(Context context,
- boolean forceInitialize) {
- if (sAlternateRecents == null) {
- sAlternateRecents = new AlternateRecentsComponent(context);
- if (forceInitialize) {
- sAlternateRecents.onStart();
- sAlternateRecents.onBootCompleted();
- }
- }
- return sAlternateRecents;
- }
-
- @Override
- public void start() {
- if (mUseAlternateRecents) {
- if (sAlternateRecents == null) {
- sAlternateRecents = getRecentsComponent(mContext, false);
- }
- sAlternateRecents.onStart();
- }
-
- putComponent(RecentsComponent.class, this);
- }
-
- @Override
- protected void onBootCompleted() {
- if (mUseAlternateRecents) {
- if (sAlternateRecents != null) {
- sAlternateRecents.onBootCompleted();
- }
- }
- mBootCompleted = true;
- }
-
- @Override
- public void showRecents(boolean triggeredFromAltTab, View statusBarView) {
- if (mUseAlternateRecents) {
- sAlternateRecents.onShowRecents(triggeredFromAltTab);
- }
- }
-
- @Override
- public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
- if (mUseAlternateRecents) {
- sAlternateRecents.onHideRecents(triggeredFromAltTab, triggeredFromHomeKey);
- } else {
- Intent intent = new Intent(RecentsActivity.CLOSE_RECENTS_INTENT);
- intent.setPackage("com.android.systemui");
- sendBroadcastSafely(intent);
-
- RecentTasksLoader.getInstance(mContext).cancelPreloadingFirstTask();
- }
- }
-
- @Override
- public void toggleRecents(Display display, int layoutDirection, View statusBarView) {
- if (mUseAlternateRecents) {
- // Launch the alternate recents if required
- sAlternateRecents.onToggleRecents();
- return;
- }
-
- if (DEBUG) Log.d(TAG, "toggle recents panel");
- try {
- TaskDescription firstTask = RecentTasksLoader.getInstance(mContext).getFirstTask();
-
- Intent intent = new Intent(RecentsActivity.TOGGLE_RECENTS_INTENT);
- intent.setClassName("com.android.systemui",
- "com.android.systemui.recent.RecentsActivity");
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-
- if (firstTask == null) {
- if (RecentsActivity.forceOpaqueBackground(mContext)) {
- ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext,
- R.anim.recents_launch_from_launcher_enter,
- R.anim.recents_launch_from_launcher_exit);
- mContext.startActivityAsUser(intent, opts.toBundle(), new UserHandle(
- UserHandle.USER_CURRENT));
- } else {
- // The correct window animation will be applied via the activity's style
- mContext.startActivityAsUser(intent, new UserHandle(
- UserHandle.USER_CURRENT));
- }
-
- } else {
- Bitmap first = null;
- if (firstTask.getThumbnail() instanceof BitmapDrawable) {
- first = ((BitmapDrawable) firstTask.getThumbnail()).getBitmap();
- } else {
- first = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
- Drawable d = RecentTasksLoader.getInstance(mContext).getDefaultThumbnail();
- d.draw(new Canvas(first));
- }
- final Resources res = mContext.getResources();
-
- float thumbWidth = res
- .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_width);
- float thumbHeight = res
- .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_height);
- if (first == null) {
- throw new RuntimeException("Recents thumbnail is null");
- }
- if (first.getWidth() != thumbWidth || first.getHeight() != thumbHeight) {
- first = Bitmap.createScaledBitmap(first, (int) thumbWidth, (int) thumbHeight,
- true);
- if (first == null) {
- throw new RuntimeException("Recents thumbnail is null");
- }
- }
-
-
- DisplayMetrics dm = new DisplayMetrics();
- display.getMetrics(dm);
- // calculate it here, but consider moving it elsewhere
- // first, determine which orientation you're in.
- final Configuration config = res.getConfiguration();
- int x, y;
-
- if (config.orientation == Configuration.ORIENTATION_PORTRAIT) {
- float appLabelLeftMargin = res.getDimensionPixelSize(
- R.dimen.status_bar_recents_app_label_left_margin);
- float appLabelWidth = res.getDimensionPixelSize(
- R.dimen.status_bar_recents_app_label_width);
- float thumbLeftMargin = res.getDimensionPixelSize(
- R.dimen.status_bar_recents_thumbnail_left_margin);
- float thumbBgPadding = res.getDimensionPixelSize(
- R.dimen.status_bar_recents_thumbnail_bg_padding);
-
- float width = appLabelLeftMargin +
- +appLabelWidth
- + thumbLeftMargin
- + thumbWidth
- + 2 * thumbBgPadding;
-
- x = (int) ((dm.widthPixels - width) / 2f + appLabelLeftMargin + appLabelWidth
- + thumbBgPadding + thumbLeftMargin);
- y = (int) (dm.heightPixels
- - res.getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_height)
- - thumbBgPadding);
- if (layoutDirection == View.LAYOUT_DIRECTION_RTL) {
- x = dm.widthPixels - x - res.getDimensionPixelSize(
- R.dimen.status_bar_recents_thumbnail_width);
- }
-
- } else { // if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
- float thumbTopMargin = res.getDimensionPixelSize(
- R.dimen.status_bar_recents_thumbnail_top_margin);
- float thumbBgPadding = res.getDimensionPixelSize(
- R.dimen.status_bar_recents_thumbnail_bg_padding);
- float textPadding = res.getDimensionPixelSize(
- R.dimen.status_bar_recents_text_description_padding);
- float labelTextSize = res.getDimensionPixelSize(
- R.dimen.status_bar_recents_app_label_text_size);
- Paint p = new Paint();
- p.setTextSize(labelTextSize);
- float labelTextHeight = p.getFontMetricsInt().bottom
- - p.getFontMetricsInt().top;
- float descriptionTextSize = res.getDimensionPixelSize(
- R.dimen.status_bar_recents_app_description_text_size);
- p.setTextSize(descriptionTextSize);
- float descriptionTextHeight = p.getFontMetricsInt().bottom
- - p.getFontMetricsInt().top;
-
- float statusBarHeight = res.getDimensionPixelSize(
- com.android.internal.R.dimen.status_bar_height);
- float recentsItemTopPadding = statusBarHeight;
-
- float height = thumbTopMargin
- + thumbHeight
- + 2 * thumbBgPadding + textPadding + labelTextHeight
- + recentsItemTopPadding + textPadding + descriptionTextHeight;
- float recentsItemRightPadding = res
- .getDimensionPixelSize(R.dimen.status_bar_recents_item_padding);
- float recentsScrollViewRightPadding = res
- .getDimensionPixelSize(R.dimen.status_bar_recents_right_glow_margin);
- x = (int) (dm.widthPixels - res
- .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_width)
- - thumbBgPadding - recentsItemRightPadding
- - recentsScrollViewRightPadding);
- y = (int) ((dm.heightPixels - statusBarHeight - height) / 2f + thumbTopMargin
- + recentsItemTopPadding + thumbBgPadding + statusBarHeight);
- }
-
- ActivityOptions opts = ActivityOptions.makeThumbnailScaleDownAnimation(
- statusBarView,
- first, x, y,
- new ActivityOptions.OnAnimationStartedListener() {
- public void onAnimationStarted() {
- Intent intent =
- new Intent(RecentsActivity.WINDOW_ANIMATION_START_INTENT);
- intent.setPackage("com.android.systemui");
- sendBroadcastSafely(intent);
- }
- });
- intent.putExtra(RecentsActivity.WAITING_FOR_WINDOW_ANIMATION_PARAM, true);
- startActivitySafely(intent, opts.toBundle());
- }
- } catch (ActivityNotFoundException e) {
- Log.e(TAG, "Failed to launch RecentAppsIntent", e);
- }
- }
-
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- if (mUseAlternateRecents) {
- sAlternateRecents.onConfigurationChanged(newConfig);
- }
- }
-
- @Override
- public void preloadRecents() {
- if (mUseAlternateRecents) {
- sAlternateRecents.onPreloadRecents();
- } else {
- Intent intent = new Intent(RecentsActivity.PRELOAD_INTENT);
- intent.setClassName("com.android.systemui",
- "com.android.systemui.recent.RecentsPreloadReceiver");
- sendBroadcastSafely(intent);
-
- RecentTasksLoader.getInstance(mContext).preloadFirstTask();
- }
- }
-
- @Override
- public void cancelPreloadingRecents() {
- if (mUseAlternateRecents) {
- sAlternateRecents.onCancelPreloadingRecents();
- } else {
- Intent intent = new Intent(RecentsActivity.CANCEL_PRELOAD_INTENT);
- intent.setClassName("com.android.systemui",
- "com.android.systemui.recent.RecentsPreloadReceiver");
- sendBroadcastSafely(intent);
-
- RecentTasksLoader.getInstance(mContext).cancelPreloadingFirstTask();
- }
- }
-
- @Override
- public void showNextAffiliatedTask() {
- if (mUseAlternateRecents) {
- sAlternateRecents.onShowNextAffiliatedTask();
- }
- }
-
- @Override
- public void showPrevAffiliatedTask() {
- if (mUseAlternateRecents) {
- sAlternateRecents.onShowPrevAffiliatedTask();
- }
- }
-
- @Override
- public void setCallback(Callbacks cb) {
- if (mUseAlternateRecents) {
- sAlternateRecents.setRecentsComponentCallback(cb);
- }
- }
-
- /**
- * Send broadcast only if BOOT_COMPLETED
- */
- private void sendBroadcastSafely(Intent intent) {
- if (!mBootCompleted) return;
- mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
- }
-
- /**
- * Start activity only if BOOT_COMPLETED
- */
- private void startActivitySafely(Intent intent, Bundle opts) {
- if (!mBootCompleted) return;
- mContext.startActivityAsUser(intent, opts, new UserHandle(UserHandle.USER_CURRENT));
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsActivity.java
deleted file mode 100644
index 7ab40b0..0000000
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsActivity.java
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recent;
-
-import android.app.Activity;
-import android.app.ActivityManager;
-import android.app.WallpaperManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.WindowManager;
-
-import com.android.systemui.R;
-import com.android.systemui.statusbar.StatusBarPanel;
-
-import java.util.List;
-
-public class RecentsActivity extends Activity {
- public static final String TOGGLE_RECENTS_INTENT = "com.android.systemui.recent.action.TOGGLE_RECENTS";
- public static final String PRELOAD_INTENT = "com.android.systemui.recent.action.PRELOAD";
- public static final String CANCEL_PRELOAD_INTENT = "com.android.systemui.recent.CANCEL_PRELOAD";
- public static final String CLOSE_RECENTS_INTENT = "com.android.systemui.recent.action.CLOSE";
- public static final String WINDOW_ANIMATION_START_INTENT = "com.android.systemui.recent.action.WINDOW_ANIMATION_START";
- public static final String PRELOAD_PERMISSION = "com.android.systemui.recent.permission.PRELOAD";
- public static final String WAITING_FOR_WINDOW_ANIMATION_PARAM = "com.android.systemui.recent.WAITING_FOR_WINDOW_ANIMATION";
- private static final String WAS_SHOWING = "was_showing";
-
- private RecentsPanelView mRecentsPanel;
- private IntentFilter mIntentFilter;
- private boolean mShowing;
- private boolean mForeground;
-
- private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (CLOSE_RECENTS_INTENT.equals(intent.getAction())) {
- if (mRecentsPanel != null && mRecentsPanel.isShowing()) {
- if (mShowing && !mForeground) {
- // Captures the case right before we transition to another activity
- mRecentsPanel.show(false);
- }
- }
- } else if (WINDOW_ANIMATION_START_INTENT.equals(intent.getAction())) {
- if (mRecentsPanel != null) {
- mRecentsPanel.onWindowAnimationStart();
- }
- }
- }
- };
-
- public class TouchOutsideListener implements View.OnTouchListener {
- private StatusBarPanel mPanel;
-
- public TouchOutsideListener(StatusBarPanel panel) {
- mPanel = panel;
- }
-
- public boolean onTouch(View v, MotionEvent ev) {
- final int action = ev.getAction();
- if (action == MotionEvent.ACTION_OUTSIDE
- || (action == MotionEvent.ACTION_DOWN
- && !mPanel.isInContentArea((int) ev.getX(), (int) ev.getY()))) {
- dismissAndGoHome();
- return true;
- }
- return false;
- }
- }
-
- @Override
- public void onPause() {
- overridePendingTransition(
- R.anim.recents_return_to_launcher_enter,
- R.anim.recents_return_to_launcher_exit);
- mForeground = false;
- super.onPause();
- }
-
- @Override
- public void onStop() {
- mShowing = false;
- if (mRecentsPanel != null) {
- mRecentsPanel.onUiHidden();
- }
- super.onStop();
- }
-
- private void updateWallpaperVisibility(boolean visible) {
- int wpflags = visible ? WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER : 0;
- int curflags = getWindow().getAttributes().flags
- & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
- if (wpflags != curflags) {
- getWindow().setFlags(wpflags, WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER);
- }
- }
-
- public static boolean forceOpaqueBackground(Context context) {
- return WallpaperManager.getInstance(context).getWallpaperInfo() != null;
- }
-
- @Override
- public void onStart() {
- // Hide wallpaper if it's not a static image
- if (forceOpaqueBackground(this)) {
- updateWallpaperVisibility(false);
- } else {
- updateWallpaperVisibility(true);
- }
- mShowing = true;
- if (mRecentsPanel != null) {
- // Call and refresh the recent tasks list in case we didn't preload tasks
- // or in case we don't get an onNewIntent
- mRecentsPanel.refreshRecentTasksList();
- mRecentsPanel.refreshViews();
- }
- super.onStart();
- }
-
- @Override
- public void onResume() {
- mForeground = true;
- super.onResume();
- }
-
- @Override
- public void onBackPressed() {
- dismissAndGoBack();
- }
-
- public void dismissAndGoHome() {
- if (mRecentsPanel != null) {
- Intent homeIntent = new Intent(Intent.ACTION_MAIN, null);
- homeIntent.addCategory(Intent.CATEGORY_HOME);
- homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
- startActivityAsUser(homeIntent, new UserHandle(UserHandle.USER_CURRENT));
- mRecentsPanel.show(false);
- }
- }
-
- public void dismissAndGoBack() {
- if (mRecentsPanel != null) {
- final ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
-
- final List<ActivityManager.RecentTaskInfo> recentTasks =
- am.getRecentTasks(2,
- ActivityManager.RECENT_WITH_EXCLUDED |
- ActivityManager.RECENT_IGNORE_UNAVAILABLE |
- ActivityManager.RECENT_INCLUDE_PROFILES);
- if (recentTasks.size() > 1 &&
- mRecentsPanel.simulateClick(recentTasks.get(1).persistentId)) {
- // recents panel will take care of calling show(false) through simulateClick
- return;
- }
- mRecentsPanel.show(false);
- }
- finish();
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- getWindow().addPrivateFlags(
- WindowManager.LayoutParams.PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR);
- setContentView(R.layout.status_bar_recent_panel);
- mRecentsPanel = (RecentsPanelView) findViewById(R.id.recents_root);
- mRecentsPanel.setOnTouchListener(new TouchOutsideListener(mRecentsPanel));
- mRecentsPanel.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
- | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
- | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
-
- final RecentTasksLoader recentTasksLoader = RecentTasksLoader.getInstance(this);
- recentTasksLoader.setRecentsPanel(mRecentsPanel, mRecentsPanel);
- mRecentsPanel.setMinSwipeAlpha(
- getResources().getInteger(R.integer.config_recent_item_min_alpha) / 100f);
-
- if (savedInstanceState == null ||
- savedInstanceState.getBoolean(WAS_SHOWING)) {
- handleIntent(getIntent(), (savedInstanceState == null));
- }
- mIntentFilter = new IntentFilter();
- mIntentFilter.addAction(CLOSE_RECENTS_INTENT);
- mIntentFilter.addAction(WINDOW_ANIMATION_START_INTENT);
- registerReceiver(mIntentReceiver, mIntentFilter);
- super.onCreate(savedInstanceState);
- }
-
- @Override
- protected void onSaveInstanceState(Bundle outState) {
- outState.putBoolean(WAS_SHOWING, mRecentsPanel.isShowing());
- }
-
- @Override
- protected void onDestroy() {
- RecentTasksLoader.getInstance(this).setRecentsPanel(null, mRecentsPanel);
- unregisterReceiver(mIntentReceiver);
- super.onDestroy();
- }
-
- @Override
- protected void onNewIntent(Intent intent) {
- handleIntent(intent, true);
- }
-
- private void handleIntent(Intent intent, boolean checkWaitingForAnimationParam) {
- super.onNewIntent(intent);
-
- if (TOGGLE_RECENTS_INTENT.equals(intent.getAction())) {
- if (mRecentsPanel != null) {
- if (mRecentsPanel.isShowing()) {
- dismissAndGoBack();
- } else {
- final RecentTasksLoader recentTasksLoader = RecentTasksLoader.getInstance(this);
- boolean waitingForWindowAnimation = checkWaitingForAnimationParam &&
- intent.getBooleanExtra(WAITING_FOR_WINDOW_ANIMATION_PARAM, false);
- mRecentsPanel.show(true, recentTasksLoader.getLoadedTasks(),
- recentTasksLoader.isFirstScreenful(), waitingForWindowAnimation);
- }
- }
- }
- }
-
- boolean isForeground() {
- return mForeground;
- }
-
- boolean isActivityShowing() {
- return mShowing;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsCallback.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsCallback.java
deleted file mode 100644
index deb5670..0000000
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsCallback.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recent;
-
-import android.view.View;
-
-public interface RecentsCallback {
- static final int SWIPE_LEFT = 0;
- static final int SWIPE_RIGHT = 1;
- static final int SWIPE_UP = 2;
- static final int SWIPE_DOWN = 3;
-
- void handleOnClick(View selectedView);
- void handleSwipe(View selectedView);
- void handleLongPress(View selectedView, View anchorView, View thumbnailView);
- void dismiss();
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java
deleted file mode 100644
index cf5d3a6..0000000
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java
+++ /dev/null
@@ -1,391 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recent;
-
-import android.animation.LayoutTransition;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.database.DataSetObserver;
-import android.graphics.Canvas;
-import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewTreeObserver;
-import android.view.ViewTreeObserver.OnGlobalLayoutListener;
-import android.widget.HorizontalScrollView;
-import android.widget.LinearLayout;
-
-import com.android.systemui.R;
-import com.android.systemui.SwipeHelper;
-import com.android.systemui.recent.RecentsPanelView.TaskDescriptionAdapter;
-
-import java.util.HashSet;
-import java.util.Iterator;
-
-public class RecentsHorizontalScrollView extends HorizontalScrollView
- implements SwipeHelper.Callback, RecentsPanelView.RecentsScrollView {
- private static final String TAG = RecentsPanelView.TAG;
- private static final boolean DEBUG = RecentsPanelView.DEBUG;
- private LinearLayout mLinearLayout;
- private TaskDescriptionAdapter mAdapter;
- private RecentsCallback mCallback;
- protected int mLastScrollPosition;
- private SwipeHelper mSwipeHelper;
- private FadedEdgeDrawHelper mFadedEdgeDrawHelper;
- private HashSet<View> mRecycledViews;
- private int mNumItemsInOneScreenful;
- private Runnable mOnScrollListener;
-
- public RecentsHorizontalScrollView(Context context, AttributeSet attrs) {
- super(context, attrs, 0);
- mSwipeHelper = new SwipeHelper(SwipeHelper.Y, this, context);
- mFadedEdgeDrawHelper = FadedEdgeDrawHelper.create(context, attrs, this, false);
- mRecycledViews = new HashSet<View>();
- }
-
- public void setMinSwipeAlpha(float minAlpha) {
- mSwipeHelper.setMinSwipeProgress(minAlpha);
- }
-
- private int scrollPositionOfMostRecent() {
- return mLinearLayout.getWidth() - getWidth();
- }
-
- private void addToRecycledViews(View v) {
- if (mRecycledViews.size() < mNumItemsInOneScreenful) {
- mRecycledViews.add(v);
- }
- }
-
- public View findViewForTask(int persistentTaskId) {
- for (int i = 0; i < mLinearLayout.getChildCount(); i++) {
- View v = mLinearLayout.getChildAt(i);
- RecentsPanelView.ViewHolder holder = (RecentsPanelView.ViewHolder) v.getTag();
- if (holder.taskDescription.persistentTaskId == persistentTaskId) {
- return v;
- }
- }
- return null;
- }
-
- private void update() {
- for (int i = 0; i < mLinearLayout.getChildCount(); i++) {
- View v = mLinearLayout.getChildAt(i);
- addToRecycledViews(v);
- mAdapter.recycleView(v);
- }
- LayoutTransition transitioner = getLayoutTransition();
- setLayoutTransition(null);
-
- mLinearLayout.removeAllViews();
- Iterator<View> recycledViews = mRecycledViews.iterator();
- for (int i = 0; i < mAdapter.getCount(); i++) {
- View old = null;
- if (recycledViews.hasNext()) {
- old = recycledViews.next();
- recycledViews.remove();
- old.setVisibility(VISIBLE);
- }
-
- final View view = mAdapter.getView(i, old, mLinearLayout);
-
- if (mFadedEdgeDrawHelper != null) {
- mFadedEdgeDrawHelper.addViewCallback(view);
- }
-
- OnTouchListener noOpListener = new OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- return true;
- }
- };
-
- view.setOnClickListener(new OnClickListener() {
- public void onClick(View v) {
- mCallback.dismiss();
- }
- });
- // We don't want a click sound when we dimiss recents
- view.setSoundEffectsEnabled(false);
-
- OnClickListener launchAppListener = new OnClickListener() {
- public void onClick(View v) {
- mCallback.handleOnClick(view);
- }
- };
-
- RecentsPanelView.ViewHolder holder = (RecentsPanelView.ViewHolder) view.getTag();
- final View thumbnailView = holder.thumbnailView;
- OnLongClickListener longClickListener = new OnLongClickListener() {
- public boolean onLongClick(View v) {
- final View anchorView = view.findViewById(R.id.app_description);
- mCallback.handleLongPress(view, anchorView, thumbnailView);
- return true;
- }
- };
- thumbnailView.setClickable(true);
- thumbnailView.setOnClickListener(launchAppListener);
- thumbnailView.setOnLongClickListener(longClickListener);
-
- // We don't want to dismiss recents if a user clicks on the app title
- // (we also don't want to launch the app either, though, because the
- // app title is a small target and doesn't have great click feedback)
- final View appTitle = view.findViewById(R.id.app_label);
- appTitle.setContentDescription(" ");
- appTitle.setOnTouchListener(noOpListener);
- mLinearLayout.addView(view);
- }
- setLayoutTransition(transitioner);
-
- // Scroll to end after initial layout.
-
- final OnGlobalLayoutListener updateScroll = new OnGlobalLayoutListener() {
- public void onGlobalLayout() {
- mLastScrollPosition = scrollPositionOfMostRecent();
- scrollTo(mLastScrollPosition, 0);
- final ViewTreeObserver observer = getViewTreeObserver();
- if (observer.isAlive()) {
- observer.removeOnGlobalLayoutListener(this);
- }
- }
- };
- getViewTreeObserver().addOnGlobalLayoutListener(updateScroll);
- }
-
- @Override
- public void removeViewInLayout(final View view) {
- dismissChild(view);
- }
-
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- if (DEBUG) Log.v(TAG, "onInterceptTouchEvent()");
- return mSwipeHelper.onInterceptTouchEvent(ev) ||
- super.onInterceptTouchEvent(ev);
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- return mSwipeHelper.onTouchEvent(ev) ||
- super.onTouchEvent(ev);
- }
-
- public boolean canChildBeDismissed(View v) {
- return true;
- }
-
- @Override
- public boolean isAntiFalsingNeeded() {
- return false;
- }
-
- @Override
- public float getFalsingThresholdFactor() {
- return 1.0f;
- }
-
- public void dismissChild(View v) {
- mSwipeHelper.dismissChild(v, 0);
- }
-
- public void onChildDismissed(View v) {
- addToRecycledViews(v);
- mLinearLayout.removeView(v);
- mCallback.handleSwipe(v);
- // Restore the alpha/translation parameters to what they were before swiping
- // (for when these items are recycled)
- View contentView = getChildContentView(v);
- contentView.setAlpha(1f);
- contentView.setTranslationY(0);
- }
-
- public void onBeginDrag(View v) {
- // We do this so the underlying ScrollView knows that it won't get
- // the chance to intercept events anymore
- requestDisallowInterceptTouchEvent(true);
- }
-
- public void onDragCancelled(View v) {
- }
-
- @Override
- public void onChildSnappedBack(View animView) {
- }
-
- @Override
- public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
- return false;
- }
-
- public View getChildAtPosition(MotionEvent ev) {
- final float x = ev.getX() + getScrollX();
- final float y = ev.getY() + getScrollY();
- for (int i = 0; i < mLinearLayout.getChildCount(); i++) {
- View item = mLinearLayout.getChildAt(i);
- if (x >= item.getLeft() && x < item.getRight()
- && y >= item.getTop() && y < item.getBottom()) {
- return item;
- }
- }
- return null;
- }
-
- public View getChildContentView(View v) {
- return v.findViewById(R.id.recent_item);
- }
-
- @Override
- public void drawFadedEdges(Canvas canvas, int left, int right, int top, int bottom) {
- if (mFadedEdgeDrawHelper != null) {
-
- mFadedEdgeDrawHelper.drawCallback(canvas,
- left, right, top, bottom, getScrollX(), getScrollY(),
- 0, 0,
- getLeftFadingEdgeStrength(), getRightFadingEdgeStrength(), getPaddingTop());
- }
- }
-
- @Override
- protected void onScrollChanged(int l, int t, int oldl, int oldt) {
- super.onScrollChanged(l, t, oldl, oldt);
- if (mOnScrollListener != null) {
- mOnScrollListener.run();
- }
- }
-
- public void setOnScrollListener(Runnable listener) {
- mOnScrollListener = listener;
- }
-
- @Override
- public int getVerticalFadingEdgeLength() {
- if (mFadedEdgeDrawHelper != null) {
- return mFadedEdgeDrawHelper.getVerticalFadingEdgeLength();
- } else {
- return super.getVerticalFadingEdgeLength();
- }
- }
-
- @Override
- public int getHorizontalFadingEdgeLength() {
- if (mFadedEdgeDrawHelper != null) {
- return mFadedEdgeDrawHelper.getHorizontalFadingEdgeLength();
- } else {
- return super.getHorizontalFadingEdgeLength();
- }
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- setScrollbarFadingEnabled(true);
- mLinearLayout = (LinearLayout) findViewById(R.id.recents_linear_layout);
- final int leftPadding = getContext().getResources()
- .getDimensionPixelOffset(R.dimen.status_bar_recents_thumbnail_left_margin);
- setOverScrollEffectPadding(leftPadding, 0);
- }
-
- @Override
- public void onAttachedToWindow() {
- if (mFadedEdgeDrawHelper != null) {
- mFadedEdgeDrawHelper.onAttachedToWindowCallback(mLinearLayout, isHardwareAccelerated());
- }
- }
-
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- float densityScale = getResources().getDisplayMetrics().density;
- mSwipeHelper.setDensityScale(densityScale);
- float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
- mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
- }
-
- private void setOverScrollEffectPadding(int leftPadding, int i) {
- // TODO Add to (Vertical)ScrollView
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
-
- // Skip this work if a transition is running; it sets the scroll values independently
- // and should not have those animated values clobbered by this logic
- LayoutTransition transition = mLinearLayout.getLayoutTransition();
- if (transition != null && transition.isRunning()) {
- return;
- }
- // Keep track of the last visible item in the list so we can restore it
- // to the bottom when the orientation changes.
- mLastScrollPosition = scrollPositionOfMostRecent();
-
- // This has to happen post-layout, so run it "in the future"
- post(new Runnable() {
- public void run() {
- // Make sure we're still not clobbering the transition-set values, since this
- // runnable launches asynchronously
- LayoutTransition transition = mLinearLayout.getLayoutTransition();
- if (transition == null || !transition.isRunning()) {
- scrollTo(mLastScrollPosition, 0);
- }
- }
- });
- }
-
- public void setAdapter(TaskDescriptionAdapter adapter) {
- mAdapter = adapter;
- mAdapter.registerDataSetObserver(new DataSetObserver() {
- public void onChanged() {
- update();
- }
-
- public void onInvalidated() {
- update();
- }
- });
- DisplayMetrics dm = getResources().getDisplayMetrics();
- int childWidthMeasureSpec =
- MeasureSpec.makeMeasureSpec(dm.widthPixels, MeasureSpec.AT_MOST);
- int childheightMeasureSpec =
- MeasureSpec.makeMeasureSpec(dm.heightPixels, MeasureSpec.AT_MOST);
- View child = mAdapter.createView(mLinearLayout);
- child.measure(childWidthMeasureSpec, childheightMeasureSpec);
- mNumItemsInOneScreenful =
- (int) Math.ceil(dm.widthPixels / (double) child.getMeasuredWidth());
- addToRecycledViews(child);
-
- for (int i = 0; i < mNumItemsInOneScreenful - 1; i++) {
- addToRecycledViews(mAdapter.createView(mLinearLayout));
- }
- }
-
- public int numItemsInOneScreenful() {
- return mNumItemsInOneScreenful;
- }
-
- @Override
- public void setLayoutTransition(LayoutTransition transition) {
- // The layout transition applies to our embedded LinearLayout
- mLinearLayout.setLayoutTransition(transition);
- }
-
- public void setCallback(RecentsCallback callback) {
- mCallback = callback;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
deleted file mode 100644
index 4c3460e..0000000
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
+++ /dev/null
@@ -1,813 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recent;
-
-import android.animation.Animator;
-import android.animation.LayoutTransition;
-import android.animation.TimeInterpolator;
-import android.app.ActivityManager;
-import android.app.ActivityManagerNative;
-import android.app.ActivityOptions;
-import android.app.TaskStackBuilder;
-import android.content.ActivityNotFoundException;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.graphics.Shader.TileMode;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.MenuItem;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewPropertyAnimator;
-import android.view.ViewRootImpl;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.animation.AnimationUtils;
-import android.view.animation.DecelerateInterpolator;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.BaseAdapter;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.ImageView.ScaleType;
-import android.widget.PopupMenu;
-import android.widget.TextView;
-
-import com.android.systemui.R;
-import com.android.systemui.statusbar.BaseStatusBar;
-import com.android.systemui.statusbar.StatusBarPanel;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
-
-import java.util.ArrayList;
-
-public class RecentsPanelView extends FrameLayout implements OnItemClickListener, RecentsCallback,
- StatusBarPanel, Animator.AnimatorListener {
- static final String TAG = "RecentsPanelView";
- static final boolean DEBUG = PhoneStatusBar.DEBUG || false;
- private PopupMenu mPopup;
- private View mRecentsScrim;
- private View mRecentsNoApps;
- private RecentsScrollView mRecentsContainer;
-
- private boolean mShowing;
- private boolean mWaitingToShow;
- private ViewHolder mItemToAnimateInWhenWindowAnimationIsFinished;
- private boolean mAnimateIconOfFirstTask;
- private boolean mWaitingForWindowAnimation;
- private long mWindowAnimationStartTime;
- private boolean mCallUiHiddenBeforeNextReload;
-
- private RecentTasksLoader mRecentTasksLoader;
- private ArrayList<TaskDescription> mRecentTaskDescriptions;
- private TaskDescriptionAdapter mListAdapter;
- private int mThumbnailWidth;
- private boolean mFitThumbnailToXY;
- private int mRecentItemLayoutId;
- private boolean mHighEndGfx;
-
- public static interface RecentsScrollView {
- public int numItemsInOneScreenful();
- public void setAdapter(TaskDescriptionAdapter adapter);
- public void setCallback(RecentsCallback callback);
- public void setMinSwipeAlpha(float minAlpha);
- public View findViewForTask(int persistentTaskId);
- public void drawFadedEdges(Canvas c, int left, int right, int top, int bottom);
- public void setOnScrollListener(Runnable listener);
- }
-
- private final class OnLongClickDelegate implements View.OnLongClickListener {
- View mOtherView;
- OnLongClickDelegate(View other) {
- mOtherView = other;
- }
- public boolean onLongClick(View v) {
- return mOtherView.performLongClick();
- }
- }
-
- /* package */ final static class ViewHolder {
- View thumbnailView;
- ImageView thumbnailViewImage;
- Drawable thumbnailViewDrawable;
- ImageView iconView;
- TextView labelView;
- TextView descriptionView;
- View calloutLine;
- TaskDescription taskDescription;
- boolean loadedThumbnailAndIcon;
- }
-
- /* package */ final class TaskDescriptionAdapter extends BaseAdapter {
- private LayoutInflater mInflater;
-
- public TaskDescriptionAdapter(Context context) {
- mInflater = LayoutInflater.from(context);
- }
-
- public int getCount() {
- return mRecentTaskDescriptions != null ? mRecentTaskDescriptions.size() : 0;
- }
-
- public Object getItem(int position) {
- return position; // we only need the index
- }
-
- public long getItemId(int position) {
- return position; // we just need something unique for this position
- }
-
- public View createView(ViewGroup parent) {
- View convertView = mInflater.inflate(mRecentItemLayoutId, parent, false);
- ViewHolder holder = new ViewHolder();
- holder.thumbnailView = convertView.findViewById(R.id.app_thumbnail);
- holder.thumbnailViewImage =
- (ImageView) convertView.findViewById(R.id.app_thumbnail_image);
- // If we set the default thumbnail now, we avoid an onLayout when we update
- // the thumbnail later (if they both have the same dimensions)
- updateThumbnail(holder, mRecentTasksLoader.getDefaultThumbnail(), false, false);
- holder.iconView = (ImageView) convertView.findViewById(R.id.app_icon);
- holder.iconView.setImageDrawable(mRecentTasksLoader.getDefaultIcon());
- holder.labelView = (TextView) convertView.findViewById(R.id.app_label);
- holder.calloutLine = convertView.findViewById(R.id.recents_callout_line);
- holder.descriptionView = (TextView) convertView.findViewById(R.id.app_description);
-
- convertView.setTag(holder);
- return convertView;
- }
-
- public View getView(int position, View convertView, ViewGroup parent) {
- if (convertView == null) {
- convertView = createView(parent);
- }
- final ViewHolder holder = (ViewHolder) convertView.getTag();
-
- // index is reverse since most recent appears at the bottom...
- final int index = mRecentTaskDescriptions.size() - position - 1;
-
- final TaskDescription td = mRecentTaskDescriptions.get(index);
-
- holder.labelView.setText(td.getLabel());
- holder.thumbnailView.setContentDescription(td.getLabel());
- holder.loadedThumbnailAndIcon = td.isLoaded();
- if (td.isLoaded()) {
- updateThumbnail(holder, td.getThumbnail(), true, false);
- updateIcon(holder, td.getIcon(), true, false);
- }
- if (index == 0) {
- if (mAnimateIconOfFirstTask) {
- ViewHolder oldHolder = mItemToAnimateInWhenWindowAnimationIsFinished;
- if (oldHolder != null) {
- oldHolder.iconView.setAlpha(1f);
- oldHolder.iconView.setTranslationX(0f);
- oldHolder.iconView.setTranslationY(0f);
- oldHolder.labelView.setAlpha(1f);
- oldHolder.labelView.setTranslationX(0f);
- oldHolder.labelView.setTranslationY(0f);
- if (oldHolder.calloutLine != null) {
- oldHolder.calloutLine.setAlpha(1f);
- oldHolder.calloutLine.setTranslationX(0f);
- oldHolder.calloutLine.setTranslationY(0f);
- }
- }
- mItemToAnimateInWhenWindowAnimationIsFinished = holder;
- int translation = -getResources().getDimensionPixelSize(
- R.dimen.status_bar_recents_app_icon_translate_distance);
- final Configuration config = getResources().getConfiguration();
- if (config.orientation == Configuration.ORIENTATION_PORTRAIT) {
- if (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
- translation = -translation;
- }
- holder.iconView.setAlpha(0f);
- holder.iconView.setTranslationX(translation);
- holder.labelView.setAlpha(0f);
- holder.labelView.setTranslationX(translation);
- holder.calloutLine.setAlpha(0f);
- holder.calloutLine.setTranslationX(translation);
- } else {
- holder.iconView.setAlpha(0f);
- holder.iconView.setTranslationY(translation);
- }
- if (!mWaitingForWindowAnimation) {
- animateInIconOfFirstTask();
- }
- }
- }
-
- holder.thumbnailView.setTag(td);
- holder.thumbnailView.setOnLongClickListener(new OnLongClickDelegate(convertView));
- holder.taskDescription = td;
- return convertView;
- }
-
- public void recycleView(View v) {
- ViewHolder holder = (ViewHolder) v.getTag();
- updateThumbnail(holder, mRecentTasksLoader.getDefaultThumbnail(), false, false);
- holder.iconView.setImageDrawable(mRecentTasksLoader.getDefaultIcon());
- holder.iconView.setVisibility(INVISIBLE);
- holder.iconView.animate().cancel();
- holder.labelView.setText(null);
- holder.labelView.animate().cancel();
- holder.thumbnailView.setContentDescription(null);
- holder.thumbnailView.setTag(null);
- holder.thumbnailView.setOnLongClickListener(null);
- holder.thumbnailView.setVisibility(INVISIBLE);
- holder.iconView.setAlpha(1f);
- holder.iconView.setTranslationX(0f);
- holder.iconView.setTranslationY(0f);
- holder.labelView.setAlpha(1f);
- holder.labelView.setTranslationX(0f);
- holder.labelView.setTranslationY(0f);
- if (holder.calloutLine != null) {
- holder.calloutLine.setAlpha(1f);
- holder.calloutLine.setTranslationX(0f);
- holder.calloutLine.setTranslationY(0f);
- holder.calloutLine.animate().cancel();
- }
- holder.taskDescription = null;
- holder.loadedThumbnailAndIcon = false;
- }
- }
-
- public RecentsPanelView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public RecentsPanelView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- updateValuesFromResources();
-
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecentsPanelView,
- defStyle, 0);
-
- mRecentItemLayoutId = a.getResourceId(R.styleable.RecentsPanelView_recentItemLayout, 0);
- mRecentTasksLoader = RecentTasksLoader.getInstance(context);
- a.recycle();
- }
-
- public int numItemsInOneScreenful() {
- return mRecentsContainer.numItemsInOneScreenful();
- }
-
- private boolean pointInside(int x, int y, View v) {
- final int l = v.getLeft();
- final int r = v.getRight();
- final int t = v.getTop();
- final int b = v.getBottom();
- return x >= l && x < r && y >= t && y < b;
- }
-
- public boolean isInContentArea(int x, int y) {
- return pointInside(x, y, (View) mRecentsContainer);
- }
-
- public void show(boolean show) {
- show(show, null, false, false);
- }
-
- public void show(boolean show, ArrayList<TaskDescription> recentTaskDescriptions,
- boolean firstScreenful, boolean animateIconOfFirstTask) {
- if (show && mCallUiHiddenBeforeNextReload) {
- onUiHidden();
- recentTaskDescriptions = null;
- mAnimateIconOfFirstTask = false;
- mWaitingForWindowAnimation = false;
- } else {
- mAnimateIconOfFirstTask = animateIconOfFirstTask;
- mWaitingForWindowAnimation = animateIconOfFirstTask;
- }
- if (show) {
- mWaitingToShow = true;
- refreshRecentTasksList(recentTaskDescriptions, firstScreenful);
- showIfReady();
- } else {
- showImpl(false);
- }
- }
-
- private void showIfReady() {
- // mWaitingToShow => there was a touch up on the recents button
- // mRecentTaskDescriptions != null => we've created views for the first screenful of items
- if (mWaitingToShow && mRecentTaskDescriptions != null) {
- showImpl(true);
- }
- }
-
- static void sendCloseSystemWindows(Context context, String reason) {
- if (ActivityManagerNative.isSystemReady()) {
- try {
- ActivityManagerNative.getDefault().closeSystemDialogs(reason);
- } catch (RemoteException e) {
- }
- }
- }
-
- private void showImpl(boolean show) {
- sendCloseSystemWindows(getContext(), BaseStatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS);
-
- mShowing = show;
-
- if (show) {
- // if there are no apps, bring up a "No recent apps" message
- boolean noApps = mRecentTaskDescriptions != null
- && (mRecentTaskDescriptions.size() == 0);
- mRecentsNoApps.setAlpha(1f);
- mRecentsNoApps.setVisibility(noApps ? View.VISIBLE : View.INVISIBLE);
-
- onAnimationEnd(null);
- setFocusable(true);
- setFocusableInTouchMode(true);
- requestFocus();
- } else {
- mWaitingToShow = false;
- // call onAnimationEnd() and clearRecentTasksList() in onUiHidden()
- mCallUiHiddenBeforeNextReload = true;
- if (mPopup != null) {
- mPopup.dismiss();
- }
- }
- }
-
- protected void onAttachedToWindow () {
- super.onAttachedToWindow();
- final ViewRootImpl root = getViewRootImpl();
- if (root != null) {
- root.setDrawDuringWindowsAnimating(true);
- }
- }
-
- public void onUiHidden() {
- mCallUiHiddenBeforeNextReload = false;
- if (!mShowing && mRecentTaskDescriptions != null) {
- onAnimationEnd(null);
- clearRecentTasksList();
- }
- }
-
- public void dismiss() {
- ((RecentsActivity) getContext()).dismissAndGoHome();
- }
-
- public void dismissAndGoBack() {
- ((RecentsActivity) getContext()).dismissAndGoBack();
- }
-
- public void onAnimationCancel(Animator animation) {
- }
-
- public void onAnimationEnd(Animator animation) {
- if (mShowing) {
- final LayoutTransition transitioner = new LayoutTransition();
- ((ViewGroup)mRecentsContainer).setLayoutTransition(transitioner);
- createCustomAnimations(transitioner);
- } else {
- ((ViewGroup)mRecentsContainer).setLayoutTransition(null);
- }
- }
-
- public void onAnimationRepeat(Animator animation) {
- }
-
- public void onAnimationStart(Animator animation) {
- }
-
- @Override
- public boolean dispatchHoverEvent(MotionEvent event) {
- // Ignore hover events outside of this panel bounds since such events
- // generate spurious accessibility events with the panel content when
- // tapping outside of it, thus confusing the user.
- final int x = (int) event.getX();
- final int y = (int) event.getY();
- if (x >= 0 && x < getWidth() && y >= 0 && y < getHeight()) {
- return super.dispatchHoverEvent(event);
- }
- return true;
- }
-
- /**
- * Whether the panel is showing, or, if it's animating, whether it will be
- * when the animation is done.
- */
- public boolean isShowing() {
- return mShowing;
- }
-
- public void setRecentTasksLoader(RecentTasksLoader loader) {
- mRecentTasksLoader = loader;
- }
-
- public void updateValuesFromResources() {
- final Resources res = getContext().getResources();
- mThumbnailWidth = Math.round(res.getDimension(R.dimen.status_bar_recents_thumbnail_width));
- mFitThumbnailToXY = res.getBoolean(R.bool.config_recents_thumbnail_image_fits_to_xy);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
-
- mRecentsContainer = (RecentsScrollView) findViewById(R.id.recents_container);
- mRecentsContainer.setOnScrollListener(new Runnable() {
- public void run() {
- // need to redraw the faded edges
- invalidate();
- }
- });
- mListAdapter = new TaskDescriptionAdapter(getContext());
- mRecentsContainer.setAdapter(mListAdapter);
- mRecentsContainer.setCallback(this);
-
- mRecentsScrim = findViewById(R.id.recents_bg_protect);
- mRecentsNoApps = findViewById(R.id.recents_no_apps);
-
- if (mRecentsScrim != null) {
- mHighEndGfx = ActivityManager.isHighEndGfx();
- if (!mHighEndGfx) {
- mRecentsScrim.setBackground(null);
- } else if (mRecentsScrim.getBackground() instanceof BitmapDrawable) {
- // In order to save space, we make the background texture repeat in the Y direction
- ((BitmapDrawable) mRecentsScrim.getBackground()).setTileModeY(TileMode.REPEAT);
- }
- }
- }
-
- public void setMinSwipeAlpha(float minAlpha) {
- mRecentsContainer.setMinSwipeAlpha(minAlpha);
- }
-
- private void createCustomAnimations(LayoutTransition transitioner) {
- transitioner.setDuration(200);
- transitioner.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 0);
- transitioner.setAnimator(LayoutTransition.DISAPPEARING, null);
- }
-
- private void updateIcon(ViewHolder h, Drawable icon, boolean show, boolean anim) {
- if (icon != null) {
- h.iconView.setImageDrawable(icon);
- if (show && h.iconView.getVisibility() != View.VISIBLE) {
- if (anim) {
- h.iconView.setAnimation(
- AnimationUtils.loadAnimation(getContext(), R.anim.recent_appear));
- }
- h.iconView.setVisibility(View.VISIBLE);
- }
- }
- }
-
- private void updateThumbnail(ViewHolder h, Drawable thumbnail, boolean show, boolean anim) {
- if (thumbnail != null) {
- // Should remove the default image in the frame
- // that this now covers, to improve scrolling speed.
- // That can't be done until the anim is complete though.
- h.thumbnailViewImage.setImageDrawable(thumbnail);
-
- // scale the image to fill the full width of the ImageView. do this only if
- // we haven't set a bitmap before, or if the bitmap size has changed
- if (h.thumbnailViewDrawable == null ||
- h.thumbnailViewDrawable.getIntrinsicWidth() != thumbnail.getIntrinsicWidth() ||
- h.thumbnailViewDrawable.getIntrinsicHeight() != thumbnail.getIntrinsicHeight()) {
- if (mFitThumbnailToXY) {
- h.thumbnailViewImage.setScaleType(ScaleType.FIT_XY);
- } else {
- Matrix scaleMatrix = new Matrix();
- float scale = mThumbnailWidth / (float) thumbnail.getIntrinsicWidth();
- scaleMatrix.setScale(scale, scale);
- h.thumbnailViewImage.setScaleType(ScaleType.MATRIX);
- h.thumbnailViewImage.setImageMatrix(scaleMatrix);
- }
- }
- if (show && h.thumbnailView.getVisibility() != View.VISIBLE) {
- if (anim) {
- h.thumbnailView.setAnimation(
- AnimationUtils.loadAnimation(getContext(), R.anim.recent_appear));
- }
- h.thumbnailView.setVisibility(View.VISIBLE);
- }
- h.thumbnailViewDrawable = thumbnail;
- }
- }
-
- void onTaskThumbnailLoaded(TaskDescription td) {
- synchronized (td) {
- if (mRecentsContainer != null) {
- ViewGroup container = (ViewGroup) mRecentsContainer;
- if (container instanceof RecentsScrollView) {
- container = (ViewGroup) container.findViewById(
- R.id.recents_linear_layout);
- }
- // Look for a view showing this thumbnail, to update.
- for (int i=0; i < container.getChildCount(); i++) {
- View v = container.getChildAt(i);
- if (v.getTag() instanceof ViewHolder) {
- ViewHolder h = (ViewHolder)v.getTag();
- if (!h.loadedThumbnailAndIcon && h.taskDescription == td) {
- // only fade in the thumbnail if recents is already visible-- we
- // show it immediately otherwise
- //boolean animateShow = mShowing &&
- // mRecentsContainer.getAlpha() > ViewConfiguration.ALPHA_THRESHOLD;
- boolean animateShow = false;
- updateIcon(h, td.getIcon(), true, animateShow);
- updateThumbnail(h, td.getThumbnail(), true, animateShow);
- h.loadedThumbnailAndIcon = true;
- }
- }
- }
- }
- }
- showIfReady();
- }
-
- private void animateInIconOfFirstTask() {
- if (mItemToAnimateInWhenWindowAnimationIsFinished != null &&
- !mRecentTasksLoader.isFirstScreenful()) {
- int timeSinceWindowAnimation =
- (int) (System.currentTimeMillis() - mWindowAnimationStartTime);
- final int minStartDelay = 150;
- final int startDelay = Math.max(0, Math.min(
- minStartDelay - timeSinceWindowAnimation, minStartDelay));
- final int duration = 250;
- final ViewHolder holder = mItemToAnimateInWhenWindowAnimationIsFinished;
- final TimeInterpolator cubic = new DecelerateInterpolator(1.5f);
- FirstFrameAnimatorHelper.initializeDrawListener(holder.iconView);
- for (View v :
- new View[] { holder.iconView, holder.labelView, holder.calloutLine }) {
- if (v != null) {
- ViewPropertyAnimator vpa = v.animate().translationX(0).translationY(0)
- .alpha(1f).setStartDelay(startDelay)
- .setDuration(duration).setInterpolator(cubic);
- FirstFrameAnimatorHelper h = new FirstFrameAnimatorHelper(vpa, v);
- }
- }
- mItemToAnimateInWhenWindowAnimationIsFinished = null;
- mAnimateIconOfFirstTask = false;
- }
- }
-
- public void onWindowAnimationStart() {
- mWaitingForWindowAnimation = false;
- mWindowAnimationStartTime = System.currentTimeMillis();
- animateInIconOfFirstTask();
- }
-
- public void clearRecentTasksList() {
- // Clear memory used by screenshots
- if (mRecentTaskDescriptions != null) {
- mRecentTasksLoader.cancelLoadingThumbnailsAndIcons(this);
- onTaskLoadingCancelled();
- }
- }
-
- public void onTaskLoadingCancelled() {
- // Gets called by RecentTasksLoader when it's cancelled
- if (mRecentTaskDescriptions != null) {
- mRecentTaskDescriptions = null;
- mListAdapter.notifyDataSetInvalidated();
- }
- }
-
- public void refreshViews() {
- mListAdapter.notifyDataSetInvalidated();
- updateUiElements();
- showIfReady();
- }
-
- public void refreshRecentTasksList() {
- refreshRecentTasksList(null, false);
- }
-
- private void refreshRecentTasksList(
- ArrayList<TaskDescription> recentTasksList, boolean firstScreenful) {
- if (mRecentTaskDescriptions == null && recentTasksList != null) {
- onTasksLoaded(recentTasksList, firstScreenful);
- } else {
- mRecentTasksLoader.loadTasksInBackground();
- }
- }
-
- public void onTasksLoaded(ArrayList<TaskDescription> tasks, boolean firstScreenful) {
- if (mRecentTaskDescriptions == null) {
- mRecentTaskDescriptions = new ArrayList<TaskDescription>(tasks);
- } else {
- mRecentTaskDescriptions.addAll(tasks);
- }
- if (((RecentsActivity) getContext()).isActivityShowing()) {
- refreshViews();
- }
- }
-
- private void updateUiElements() {
- final int items = mRecentTaskDescriptions != null
- ? mRecentTaskDescriptions.size() : 0;
-
- ((View) mRecentsContainer).setVisibility(items > 0 ? View.VISIBLE : View.GONE);
-
- // Set description for accessibility
- int numRecentApps = mRecentTaskDescriptions != null
- ? mRecentTaskDescriptions.size() : 0;
- String recentAppsAccessibilityDescription;
- if (numRecentApps == 0) {
- recentAppsAccessibilityDescription =
- getResources().getString(R.string.status_bar_no_recent_apps);
- } else {
- recentAppsAccessibilityDescription = getResources().getQuantityString(
- R.plurals.status_bar_accessibility_recent_apps, numRecentApps, numRecentApps);
- }
- setContentDescription(recentAppsAccessibilityDescription);
- }
-
- public boolean simulateClick(int persistentTaskId) {
- View v = mRecentsContainer.findViewForTask(persistentTaskId);
- if (v != null) {
- handleOnClick(v);
- return true;
- }
- return false;
- }
-
- public void handleOnClick(View view) {
- ViewHolder holder = (ViewHolder) view.getTag();
- TaskDescription ad = holder.taskDescription;
- final Context context = view.getContext();
- final ActivityManager am = (ActivityManager)
- context.getSystemService(Context.ACTIVITY_SERVICE);
-
- Bitmap bm = null;
- boolean usingDrawingCache = true;
- if (holder.thumbnailViewDrawable instanceof BitmapDrawable) {
- bm = ((BitmapDrawable) holder.thumbnailViewDrawable).getBitmap();
- if (bm.getWidth() == holder.thumbnailViewImage.getWidth() &&
- bm.getHeight() == holder.thumbnailViewImage.getHeight()) {
- usingDrawingCache = false;
- }
- }
- if (usingDrawingCache) {
- holder.thumbnailViewImage.setDrawingCacheEnabled(true);
- bm = holder.thumbnailViewImage.getDrawingCache();
- }
- Bundle opts = (bm == null) ?
- null :
- ActivityOptions.makeThumbnailScaleUpAnimation(
- holder.thumbnailViewImage, bm, 0, 0, null).toBundle();
-
- show(false);
- if (ad.taskId >= 0) {
- // This is an active task; it should just go to the foreground.
- am.moveTaskToFront(ad.taskId, ActivityManager.MOVE_TASK_WITH_HOME,
- opts);
- } else {
- Intent intent = ad.intent;
- intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
- | Intent.FLAG_ACTIVITY_TASK_ON_HOME
- | Intent.FLAG_ACTIVITY_NEW_TASK);
- if (DEBUG) Log.v(TAG, "Starting activity " + intent);
- try {
- context.startActivityAsUser(intent, opts,
- new UserHandle(ad.userId));
- } catch (SecurityException e) {
- Log.e(TAG, "Recents does not have the permission to launch " + intent, e);
- } catch (ActivityNotFoundException e) {
- Log.e(TAG, "Error launching activity " + intent, e);
- }
- }
- if (usingDrawingCache) {
- holder.thumbnailViewImage.setDrawingCacheEnabled(false);
- }
- }
-
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- handleOnClick(view);
- }
-
- public void handleSwipe(View view) {
- TaskDescription ad = ((ViewHolder) view.getTag()).taskDescription;
- if (ad == null) {
- Log.v(TAG, "Not able to find activity description for swiped task; view=" + view +
- " tag=" + view.getTag());
- return;
- }
- if (DEBUG) Log.v(TAG, "Jettison " + ad.getLabel());
- mRecentTaskDescriptions.remove(ad);
- mRecentTasksLoader.remove(ad);
-
- // Handled by widget containers to enable LayoutTransitions properly
- // mListAdapter.notifyDataSetChanged();
-
- if (mRecentTaskDescriptions.size() == 0) {
- dismissAndGoBack();
- }
-
- // Currently, either direction means the same thing, so ignore direction and remove
- // the task.
- final ActivityManager am = (ActivityManager)
- getContext().getSystemService(Context.ACTIVITY_SERVICE);
- if (am != null) {
- am.removeTask(ad.persistentTaskId);
-
- // Accessibility feedback
- setContentDescription(
- getContext().getString(R.string.accessibility_recents_item_dismissed, ad.getLabel()));
- sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
- setContentDescription(null);
- }
- }
-
- private void startApplicationDetailsActivity(String packageName, int userId) {
- Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
- Uri.fromParts("package", packageName, null));
- intent.setComponent(intent.resolveActivity(getContext().getPackageManager()));
- TaskStackBuilder.create(getContext())
- .addNextIntentWithParentStack(intent).startActivities(null, new UserHandle(userId));
- }
-
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- if (mPopup != null) {
- return true;
- } else {
- return super.onInterceptTouchEvent(ev);
- }
- }
-
- public void handleLongPress(
- final View selectedView, final View anchorView, final View thumbnailView) {
- thumbnailView.setSelected(true);
- final PopupMenu popup =
- new PopupMenu(getContext(), anchorView == null ? selectedView : anchorView);
- mPopup = popup;
- popup.getMenuInflater().inflate(R.menu.recent_popup_menu, popup.getMenu());
- popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
- public boolean onMenuItemClick(MenuItem item) {
- if (item.getItemId() == R.id.recent_remove_item) {
- ((ViewGroup) mRecentsContainer).removeViewInLayout(selectedView);
- } else if (item.getItemId() == R.id.recent_inspect_item) {
- ViewHolder viewHolder = (ViewHolder) selectedView.getTag();
- if (viewHolder != null) {
- final TaskDescription ad = viewHolder.taskDescription;
- startApplicationDetailsActivity(ad.packageName, ad.userId);
- show(false);
- } else {
- throw new IllegalStateException("Oops, no tag on view " + selectedView);
- }
- } else {
- return false;
- }
- return true;
- }
- });
- popup.setOnDismissListener(new PopupMenu.OnDismissListener() {
- public void onDismiss(PopupMenu menu) {
- thumbnailView.setSelected(false);
- mPopup = null;
- }
- });
- popup.show();
- }
-
- @Override
- protected void dispatchDraw(Canvas canvas) {
- super.dispatchDraw(canvas);
-
- int paddingLeft = getPaddingLeft();
- final boolean offsetRequired = isPaddingOffsetRequired();
- if (offsetRequired) {
- paddingLeft += getLeftPaddingOffset();
- }
-
- int left = getScrollX() + paddingLeft;
- int right = left + getRight() - getLeft() - getPaddingRight() - paddingLeft;
- int top = getScrollY() + getFadeTop(offsetRequired);
- int bottom = top + getFadeHeight(offsetRequired);
-
- if (offsetRequired) {
- right += getRightPaddingOffset();
- bottom += getBottomPaddingOffset();
- }
- mRecentsContainer.drawFadedEdges(canvas, left, right, top, bottom);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsPreloadReceiver.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsPreloadReceiver.java
deleted file mode 100644
index eb58920..0000000
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsPreloadReceiver.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recent;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-public class RecentsPreloadReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (RecentsActivity.PRELOAD_INTENT.equals(intent.getAction())) {
- RecentTasksLoader.getInstance(context).preloadRecentTasksList();
- } else if (RecentsActivity.CANCEL_PRELOAD_INTENT.equals(intent.getAction())){
- RecentTasksLoader.getInstance(context).cancelPreloadingRecentTasksList();
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java
deleted file mode 100644
index d518f74..0000000
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java
+++ /dev/null
@@ -1,401 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recent;
-
-import android.animation.LayoutTransition;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.database.DataSetObserver;
-import android.graphics.Canvas;
-import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewTreeObserver;
-import android.view.ViewTreeObserver.OnGlobalLayoutListener;
-import android.widget.LinearLayout;
-import android.widget.ScrollView;
-
-import com.android.systemui.R;
-import com.android.systemui.SwipeHelper;
-import com.android.systemui.recent.RecentsPanelView.TaskDescriptionAdapter;
-
-import java.util.HashSet;
-import java.util.Iterator;
-
-public class RecentsVerticalScrollView extends ScrollView
- implements SwipeHelper.Callback, RecentsPanelView.RecentsScrollView {
- private static final String TAG = RecentsPanelView.TAG;
- private static final boolean DEBUG = RecentsPanelView.DEBUG;
- private LinearLayout mLinearLayout;
- private TaskDescriptionAdapter mAdapter;
- private RecentsCallback mCallback;
- protected int mLastScrollPosition;
- private SwipeHelper mSwipeHelper;
- private FadedEdgeDrawHelper mFadedEdgeDrawHelper;
- private HashSet<View> mRecycledViews;
- private int mNumItemsInOneScreenful;
- private Runnable mOnScrollListener;
-
- public RecentsVerticalScrollView(Context context, AttributeSet attrs) {
- super(context, attrs, 0);
- mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, context);
-
- mFadedEdgeDrawHelper = FadedEdgeDrawHelper.create(context, attrs, this, true);
- mRecycledViews = new HashSet<View>();
- }
-
- public void setMinSwipeAlpha(float minAlpha) {
- mSwipeHelper.setMinSwipeProgress(minAlpha);
- }
-
- private int scrollPositionOfMostRecent() {
- return mLinearLayout.getHeight() - getHeight() + getPaddingTop();
- }
-
- private void addToRecycledViews(View v) {
- if (mRecycledViews.size() < mNumItemsInOneScreenful) {
- mRecycledViews.add(v);
- }
- }
-
- public View findViewForTask(int persistentTaskId) {
- for (int i = 0; i < mLinearLayout.getChildCount(); i++) {
- View v = mLinearLayout.getChildAt(i);
- RecentsPanelView.ViewHolder holder = (RecentsPanelView.ViewHolder) v.getTag();
- if (holder.taskDescription.persistentTaskId == persistentTaskId) {
- return v;
- }
- }
- return null;
- }
-
- private void update() {
- for (int i = 0; i < mLinearLayout.getChildCount(); i++) {
- View v = mLinearLayout.getChildAt(i);
- addToRecycledViews(v);
- mAdapter.recycleView(v);
- }
- LayoutTransition transitioner = getLayoutTransition();
- setLayoutTransition(null);
-
- mLinearLayout.removeAllViews();
-
- // Once we can clear the data associated with individual item views,
- // we can get rid of the removeAllViews() and the code below will
- // recycle them.
- Iterator<View> recycledViews = mRecycledViews.iterator();
- for (int i = 0; i < mAdapter.getCount(); i++) {
- View old = null;
- if (recycledViews.hasNext()) {
- old = recycledViews.next();
- recycledViews.remove();
- old.setVisibility(VISIBLE);
- }
- final View view = mAdapter.getView(i, old, mLinearLayout);
-
- if (mFadedEdgeDrawHelper != null) {
- mFadedEdgeDrawHelper.addViewCallback(view);
- }
-
- OnTouchListener noOpListener = new OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- return true;
- }
- };
-
- view.setOnClickListener(new OnClickListener() {
- public void onClick(View v) {
- mCallback.dismiss();
- }
- });
- // We don't want a click sound when we dimiss recents
- view.setSoundEffectsEnabled(false);
-
- OnClickListener launchAppListener = new OnClickListener() {
- public void onClick(View v) {
- mCallback.handleOnClick(view);
- }
- };
-
- RecentsPanelView.ViewHolder holder = (RecentsPanelView.ViewHolder) view.getTag();
- final View thumbnailView = holder.thumbnailView;
- OnLongClickListener longClickListener = new OnLongClickListener() {
- public boolean onLongClick(View v) {
- final View anchorView = view.findViewById(R.id.app_description);
- mCallback.handleLongPress(view, anchorView, thumbnailView);
- return true;
- }
- };
- thumbnailView.setClickable(true);
- thumbnailView.setOnClickListener(launchAppListener);
- thumbnailView.setOnLongClickListener(longClickListener);
-
- // We don't want to dismiss recents if a user clicks on the app title
- // (we also don't want to launch the app either, though, because the
- // app title is a small target and doesn't have great click feedback)
- final View appTitle = view.findViewById(R.id.app_label);
- appTitle.setContentDescription(" ");
- appTitle.setOnTouchListener(noOpListener);
- final View calloutLine = view.findViewById(R.id.recents_callout_line);
- if (calloutLine != null) {
- calloutLine.setOnTouchListener(noOpListener);
- }
-
- mLinearLayout.addView(view);
- }
- setLayoutTransition(transitioner);
-
- // Scroll to end after initial layout.
- final OnGlobalLayoutListener updateScroll = new OnGlobalLayoutListener() {
- public void onGlobalLayout() {
- mLastScrollPosition = scrollPositionOfMostRecent();
- scrollTo(0, mLastScrollPosition);
- final ViewTreeObserver observer = getViewTreeObserver();
- if (observer.isAlive()) {
- observer.removeOnGlobalLayoutListener(this);
- }
- }
- };
- getViewTreeObserver().addOnGlobalLayoutListener(updateScroll);
- }
-
- @Override
- public void removeViewInLayout(final View view) {
- dismissChild(view);
- }
-
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- if (DEBUG) Log.v(TAG, "onInterceptTouchEvent()");
- return mSwipeHelper.onInterceptTouchEvent(ev) ||
- super.onInterceptTouchEvent(ev);
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- return mSwipeHelper.onTouchEvent(ev) ||
- super.onTouchEvent(ev);
- }
-
- public boolean canChildBeDismissed(View v) {
- return true;
- }
-
- @Override
- public boolean isAntiFalsingNeeded() {
- return false;
- }
-
- @Override
- public float getFalsingThresholdFactor() {
- return 1.0f;
- }
-
- public void dismissChild(View v) {
- mSwipeHelper.dismissChild(v, 0);
- }
-
- public void onChildDismissed(View v) {
- addToRecycledViews(v);
- mLinearLayout.removeView(v);
- mCallback.handleSwipe(v);
- // Restore the alpha/translation parameters to what they were before swiping
- // (for when these items are recycled)
- View contentView = getChildContentView(v);
- contentView.setAlpha(1f);
- contentView.setTranslationX(0);
- }
-
- public void onBeginDrag(View v) {
- // We do this so the underlying ScrollView knows that it won't get
- // the chance to intercept events anymore
- requestDisallowInterceptTouchEvent(true);
- }
-
- public void onDragCancelled(View v) {
- }
-
- @Override
- public void onChildSnappedBack(View animView) {
- }
-
- @Override
- public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
- return false;
- }
-
- public View getChildAtPosition(MotionEvent ev) {
- final float x = ev.getX() + getScrollX();
- final float y = ev.getY() + getScrollY();
- for (int i = 0; i < mLinearLayout.getChildCount(); i++) {
- View item = mLinearLayout.getChildAt(i);
- if (item.getVisibility() == View.VISIBLE
- && x >= item.getLeft() && x < item.getRight()
- && y >= item.getTop() && y < item.getBottom()) {
- return item;
- }
- }
- return null;
- }
-
- public View getChildContentView(View v) {
- return v.findViewById(R.id.recent_item);
- }
-
- @Override
- public void drawFadedEdges(Canvas canvas, int left, int right, int top, int bottom) {
- if (mFadedEdgeDrawHelper != null) {
- final boolean offsetRequired = isPaddingOffsetRequired();
- mFadedEdgeDrawHelper.drawCallback(canvas,
- left, right, top + getFadeTop(offsetRequired), bottom, getScrollX(), getScrollY(),
- getTopFadingEdgeStrength(), getBottomFadingEdgeStrength(),
- 0, 0, getPaddingTop());
- }
- }
-
- @Override
- protected void onScrollChanged(int l, int t, int oldl, int oldt) {
- super.onScrollChanged(l, t, oldl, oldt);
- if (mOnScrollListener != null) {
- mOnScrollListener.run();
- }
- }
-
- public void setOnScrollListener(Runnable listener) {
- mOnScrollListener = listener;
- }
-
- @Override
- public int getVerticalFadingEdgeLength() {
- if (mFadedEdgeDrawHelper != null) {
- return mFadedEdgeDrawHelper.getVerticalFadingEdgeLength();
- } else {
- return super.getVerticalFadingEdgeLength();
- }
- }
-
- @Override
- public int getHorizontalFadingEdgeLength() {
- if (mFadedEdgeDrawHelper != null) {
- return mFadedEdgeDrawHelper.getHorizontalFadingEdgeLength();
- } else {
- return super.getHorizontalFadingEdgeLength();
- }
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- setScrollbarFadingEnabled(true);
- mLinearLayout = (LinearLayout) findViewById(R.id.recents_linear_layout);
- final int leftPadding = getContext().getResources()
- .getDimensionPixelOffset(R.dimen.status_bar_recents_thumbnail_left_margin);
- setOverScrollEffectPadding(leftPadding, 0);
- }
-
- @Override
- public void onAttachedToWindow() {
- if (mFadedEdgeDrawHelper != null) {
- mFadedEdgeDrawHelper.onAttachedToWindowCallback(mLinearLayout, isHardwareAccelerated());
- }
- }
-
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- float densityScale = getResources().getDisplayMetrics().density;
- mSwipeHelper.setDensityScale(densityScale);
- float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
- mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
- }
-
- private void setOverScrollEffectPadding(int leftPadding, int i) {
- // TODO Add to (Vertical)ScrollView
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
-
- // Skip this work if a transition is running; it sets the scroll values independently
- // and should not have those animated values clobbered by this logic
- LayoutTransition transition = mLinearLayout.getLayoutTransition();
- if (transition != null && transition.isRunning()) {
- return;
- }
- // Keep track of the last visible item in the list so we can restore it
- // to the bottom when the orientation changes.
- mLastScrollPosition = scrollPositionOfMostRecent();
-
- // This has to happen post-layout, so run it "in the future"
- post(new Runnable() {
- public void run() {
- // Make sure we're still not clobbering the transition-set values, since this
- // runnable launches asynchronously
- LayoutTransition transition = mLinearLayout.getLayoutTransition();
- if (transition == null || !transition.isRunning()) {
- scrollTo(0, mLastScrollPosition);
- }
- }
- });
- }
-
- public void setAdapter(TaskDescriptionAdapter adapter) {
- mAdapter = adapter;
- mAdapter.registerDataSetObserver(new DataSetObserver() {
- public void onChanged() {
- update();
- }
-
- public void onInvalidated() {
- update();
- }
- });
-
- DisplayMetrics dm = getResources().getDisplayMetrics();
- int childWidthMeasureSpec =
- MeasureSpec.makeMeasureSpec(dm.widthPixels, MeasureSpec.AT_MOST);
- int childheightMeasureSpec =
- MeasureSpec.makeMeasureSpec(dm.heightPixels, MeasureSpec.AT_MOST);
- View child = mAdapter.createView(mLinearLayout);
- child.measure(childWidthMeasureSpec, childheightMeasureSpec);
- mNumItemsInOneScreenful =
- (int) Math.ceil(dm.heightPixels / (double) child.getMeasuredHeight());
- addToRecycledViews(child);
-
- for (int i = 0; i < mNumItemsInOneScreenful - 1; i++) {
- addToRecycledViews(mAdapter.createView(mLinearLayout));
- }
- }
-
- public int numItemsInOneScreenful() {
- return mNumItemsInOneScreenful;
- }
-
- @Override
- public void setLayoutTransition(LayoutTransition transition) {
- // The layout transition applies to our embedded LinearLayout
- mLinearLayout.setLayoutTransition(transition);
- }
-
- public void setCallback(RecentsCallback callback) {
- mCallback = callback;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/TaskDescription.java b/packages/SystemUI/src/com/android/systemui/recent/TaskDescription.java
deleted file mode 100644
index 5ad965f..0000000
--- a/packages/SystemUI/src/com/android/systemui/recent/TaskDescription.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recent;
-
-import android.os.UserHandle;
-import android.content.Intent;
-import android.content.pm.ResolveInfo;
-import android.graphics.drawable.Drawable;
-
-public final class TaskDescription {
- final ResolveInfo resolveInfo;
- final int taskId; // application task id for curating apps
- final int persistentTaskId; // persistent id
- final Intent intent; // launch intent for application
- final String packageName; // used to override animations (see onClick())
- final CharSequence description;
- final int userId;
-
- private Drawable mThumbnail; // generated by Activity.onCreateThumbnail()
- private Drawable mIcon; // application package icon
- private CharSequence mLabel; // application package label
- private boolean mLoaded;
-
- public TaskDescription(int _taskId, int _persistentTaskId,
- ResolveInfo _resolveInfo, Intent _intent,
- String _packageName, CharSequence _description, int _userId) {
- resolveInfo = _resolveInfo;
- intent = _intent;
- taskId = _taskId;
- persistentTaskId = _persistentTaskId;
-
- description = _description;
- packageName = _packageName;
- userId = _userId;
- }
-
- public TaskDescription() {
- resolveInfo = null;
- intent = null;
- taskId = -1;
- persistentTaskId = -1;
-
- description = null;
- packageName = null;
- userId = UserHandle.USER_NULL;
- }
-
- public void setLoaded(boolean loaded) {
- mLoaded = loaded;
- }
-
- public boolean isLoaded() {
- return mLoaded;
- }
-
- public boolean isNull() {
- return resolveInfo == null;
- }
-
- // mark all these as locked?
- public CharSequence getLabel() {
- return mLabel;
- }
-
- public void setLabel(CharSequence label) {
- mLabel = label;
- }
-
- public Drawable getIcon() {
- return mIcon;
- }
-
- public void setIcon(Drawable icon) {
- mIcon = icon;
- }
-
- public void setThumbnail(Drawable thumbnail) {
- mThumbnail = thumbnail;
- }
-
- public Drawable getThumbnail() {
- return mThumbnail;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index 0a1718d..192acc6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -29,18 +29,18 @@ public class Constants {
public static final boolean EnableTransitionThumbnailDebugMode = false;
// Enables the filtering of tasks according to their grouping
public static final boolean EnableTaskFiltering = false;
- // Enables clipping of tasks against each other
- public static final boolean EnableTaskStackClipping = true;
- // Enables tapping on the TaskBar to launch the task
- public static final boolean EnableTaskBarTouchEvents = true;
// Enables app-info pane on long-pressing the icon
public static final boolean EnableDevAppInfoOnLongPress = true;
+ // Enables dismiss-all
+ public static final boolean EnableDismissAll = false;
// Enables debug mode
public static final boolean EnableDebugMode = false;
// Enables the search bar layout
public static final boolean EnableSearchLayout = true;
// Enables the thumbnail alpha on the front-most task
public static final boolean EnableThumbnailAlphaOnFrontmost = false;
+ // Enables all system stacks to show up in the same recents stack
+ public static final boolean EnableMultiStackToSingleStack = true;
// This disables the bitmap and icon caches
public static final boolean DisableBackgroundCache = false;
// Enables the simulated task affiliations
diff --git a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 2ddab48..2d1fab0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -24,7 +24,6 @@ import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -37,10 +36,13 @@ import android.os.Handler;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Pair;
+import android.view.Display;
import android.view.LayoutInflater;
import android.view.View;
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
+import com.android.systemui.SystemUI;
+import com.android.systemui.SystemUIApplication;
import com.android.systemui.recents.misc.Console;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.model.RecentsTaskLoadPlan;
@@ -52,9 +54,9 @@ import com.android.systemui.recents.views.TaskStackView;
import com.android.systemui.recents.views.TaskStackViewLayoutAlgorithm;
import com.android.systemui.recents.views.TaskViewHeader;
import com.android.systemui.recents.views.TaskViewTransform;
+import com.android.systemui.statusbar.phone.PhoneStatusBar;
import java.util.ArrayList;
-import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -69,7 +71,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
@interface ProxyFromAnyToPrimaryUser {}
/** A proxy implementation for the recents component */
-public class AlternateRecentsComponent implements ActivityOptions.OnAnimationStartedListener {
+public class Recents extends SystemUI
+ implements ActivityOptions.OnAnimationStartedListener, RecentsComponent {
final public static String EXTRA_TRIGGERED_FROM_ALT_TAB = "triggeredFromAltTab";
final public static String EXTRA_TRIGGERED_FROM_HOME_KEY = "triggeredFromHomeKey";
@@ -78,6 +81,8 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
// Owner proxy events
final public static String ACTION_PROXY_NOTIFY_RECENTS_VISIBLITY_TO_OWNER =
"action_notify_recents_visibility_change";
+ final public static String ACTION_PROXY_SCREEN_PINNING_REQUEST_TO_OWNER =
+ "action_screen_pinning_request";
final public static String ACTION_START_ENTER_ANIMATION = "action_start_enter_animation";
final public static String ACTION_TOGGLE_RECENTS_ACTIVITY = "action_toggle_recents_activity";
@@ -85,7 +90,7 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
final static int sMinToggleDelay = 350;
- final static String sToggleRecentsAction = "com.android.systemui.recents.SHOW_RECENTS";
+ public final static String sToggleRecentsAction = "com.android.systemui.recents.SHOW_RECENTS";
public final static String sRecentsPackage = "com.android.systemui";
public final static String sRecentsActivity = "com.android.systemui.recents.RecentsActivity";
@@ -109,6 +114,9 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
/** Preloads the next task */
public void run() {
+ // Temporarily skip this if multi stack is enabled
+ if (mConfig.multiStackEnabled) return;
+
RecentsConfiguration config = RecentsConfiguration.getInstance();
if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
@@ -144,14 +152,17 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
case ACTION_PROXY_NOTIFY_RECENTS_VISIBLITY_TO_OWNER:
visibilityChanged(intent.getBooleanExtra(EXTRA_RECENTS_VISIBILITY, false));
break;
+ case ACTION_PROXY_SCREEN_PINNING_REQUEST_TO_OWNER:
+ onStartScreenPinning(context);
+ break;
}
}
}
static RecentsComponent.Callbacks sRecentsComponentCallbacks;
static RecentsTaskLoadPlan sInstanceLoadPlan;
+ static Recents sInstance;
- Context mContext;
LayoutInflater mInflater;
SystemServicesProxy mSystemServicesProxy;
Handler mHandler;
@@ -179,11 +190,43 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
boolean mTriggeredFromAltTab;
long mLastToggleTime;
- public AlternateRecentsComponent(Context context) {
- RecentsTaskLoader.initialize(context);
- mInflater = LayoutInflater.from(context);
- mContext = context;
- mSystemServicesProxy = new SystemServicesProxy(context);
+ public Recents() {
+ }
+
+ /**
+ * Gets the singleton instance and starts it if needed. On the primary user on the device, this
+ * component gets started as a normal {@link SystemUI} component. On a secondary user, this
+ * lifecycle doesn't exist, so we need to start it manually here if needed.
+ */
+ public static Recents getInstanceAndStartIfNeeded(Context ctx) {
+ if (sInstance == null) {
+ sInstance = new Recents();
+ sInstance.mContext = ctx;
+ sInstance.start();
+ sInstance.onBootCompleted();
+ }
+ return sInstance;
+ }
+
+ /** Creates a new broadcast intent */
+ static Intent createLocalBroadcastIntent(Context context, String action) {
+ Intent intent = new Intent(action);
+ intent.setPackage(context.getPackageName());
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT |
+ Intent.FLAG_RECEIVER_FOREGROUND);
+ return intent;
+ }
+
+ /** Initializes the Recents. */
+ @ProxyFromPrimaryToCurrentUser
+ @Override
+ public void start() {
+ if (sInstance == null) {
+ sInstance = this;
+ }
+ RecentsTaskLoader.initialize(mContext);
+ mInflater = LayoutInflater.from(mContext);
+ mSystemServicesProxy = new SystemServicesProxy(mContext);
mHandler = new Handler();
mTaskStackBounds = new Rect();
@@ -197,24 +240,12 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
if (mSystemServicesProxy.isForegroundUserOwner()) {
mProxyBroadcastReceiver = new RecentsOwnerEventProxyReceiver();
IntentFilter filter = new IntentFilter();
- filter.addAction(AlternateRecentsComponent.ACTION_PROXY_NOTIFY_RECENTS_VISIBLITY_TO_OWNER);
+ filter.addAction(Recents.ACTION_PROXY_NOTIFY_RECENTS_VISIBLITY_TO_OWNER);
+ filter.addAction(Recents.ACTION_PROXY_SCREEN_PINNING_REQUEST_TO_OWNER);
mContext.registerReceiverAsUser(mProxyBroadcastReceiver, UserHandle.CURRENT, filter,
null, mHandler);
}
- }
- /** Creates a new broadcast intent */
- static Intent createLocalBroadcastIntent(Context context, String action) {
- Intent intent = new Intent(action);
- intent.setPackage(context.getPackageName());
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT |
- Intent.FLAG_RECEIVER_FOREGROUND);
- return intent;
- }
-
- /** Initializes the Recents. */
- @ProxyFromPrimaryToCurrentUser
- public void onStart() {
// Initialize some static datastructures
TaskStackViewLayoutAlgorithm.initializeCurve();
// Load the header bar layout
@@ -230,17 +261,20 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
launchOpts.onlyLoadForCache = true;
loader.loadTasks(mContext, plan, launchOpts);
+ putComponent(Recents.class, this);
}
+ @Override
public void onBootCompleted() {
mBootCompleted = true;
}
/** Shows the Recents. */
@ProxyFromPrimaryToCurrentUser
- public void onShowRecents(boolean triggeredFromAltTab) {
+ @Override
+ public void showRecents(boolean triggeredFromAltTab, View statusBarView) {
if (mSystemServicesProxy.isForegroundUserOwner()) {
- showRecents(triggeredFromAltTab);
+ showRecentsInternal(triggeredFromAltTab);
} else {
Intent intent = createLocalBroadcastIntent(mContext,
RecentsUserEventProxyReceiver.ACTION_PROXY_SHOW_RECENTS_TO_USER);
@@ -248,7 +282,8 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
}
}
- void showRecents(boolean triggeredFromAltTab) {
+
+ void showRecentsInternal(boolean triggeredFromAltTab) {
mTriggeredFromAltTab = triggeredFromAltTab;
try {
@@ -260,9 +295,10 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
/** Hides the Recents. */
@ProxyFromPrimaryToCurrentUser
- public void onHideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
+ @Override
+ public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
if (mSystemServicesProxy.isForegroundUserOwner()) {
- hideRecents(triggeredFromAltTab, triggeredFromHomeKey);
+ hideRecentsInternal(triggeredFromAltTab, triggeredFromHomeKey);
} else {
Intent intent = createLocalBroadcastIntent(mContext,
RecentsUserEventProxyReceiver.ACTION_PROXY_HIDE_RECENTS_TO_USER);
@@ -271,7 +307,8 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
}
}
- void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
+
+ void hideRecentsInternal(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
if (mBootCompleted) {
ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask();
if (topTask != null && mSystemServicesProxy.isRecentsTopMost(topTask, null)) {
@@ -286,16 +323,18 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
/** Toggles the Recents activity. */
@ProxyFromPrimaryToCurrentUser
- public void onToggleRecents() {
+ @Override
+ public void toggleRecents(Display display, int layoutDirection, View statusBarView) {
if (mSystemServicesProxy.isForegroundUserOwner()) {
- toggleRecents();
+ toggleRecentsInternal();
} else {
Intent intent = createLocalBroadcastIntent(mContext,
RecentsUserEventProxyReceiver.ACTION_PROXY_TOGGLE_RECENTS_TO_USER);
mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
}
}
- void toggleRecents() {
+
+ void toggleRecentsInternal() {
mTriggeredFromAltTab = false;
try {
@@ -307,16 +346,18 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
/** Preloads info for the Recents activity. */
@ProxyFromPrimaryToCurrentUser
- public void onPreloadRecents() {
+ @Override
+ public void preloadRecents() {
if (mSystemServicesProxy.isForegroundUserOwner()) {
- preloadRecents();
+ preloadRecentsInternal();
} else {
Intent intent = createLocalBroadcastIntent(mContext,
RecentsUserEventProxyReceiver.ACTION_PROXY_PRELOAD_RECENTS_TO_USER);
mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
}
}
- void preloadRecents() {
+
+ void preloadRecentsInternal() {
// Preload only the raw task list into a new load plan (which will be consumed by the
// RecentsActivity)
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
@@ -324,18 +365,27 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
sInstanceLoadPlan.preloadRawTasks(true);
}
- public void onCancelPreloadingRecents() {
+ @Override
+ public void cancelPreloadingRecents() {
// Do nothing
}
void showRelativeAffiliatedTask(boolean showNextTask) {
+ // Return early if there is no focused stack
+ int focusedStackId = mSystemServicesProxy.getFocusedStack();
+ TaskStack focusedStack = null;
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
loader.preloadTasks(plan, true /* isTopTaskHome */);
- TaskStack stack = plan.getTaskStack();
+ if (mConfig.multiStackEnabled) {
+ if (focusedStackId < 0) return;
+ focusedStack = plan.getTaskStack(focusedStackId);
+ } else {
+ focusedStack = plan.getAllTaskStacks().get(0);
+ }
- // Return early if there are no tasks
- if (stack.getTaskCount() == 0) return;
+ // Return early if there are no tasks in the focused stack
+ if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
ActivityManager.RunningTaskInfo runningTask = mSystemServicesProxy.getTopMostTask();
// Return early if there is no running task (can't determine affiliated tasks in this case)
@@ -344,7 +394,7 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
if (mSystemServicesProxy.isInHomeStack(runningTask.id)) return;
// Find the task in the recents list
- ArrayList<Task> tasks = stack.getTasks();
+ ArrayList<Task> tasks = focusedStack.getTasks();
Task toTask = null;
ActivityOptions launchOpts = null;
int taskCount = tasks.size();
@@ -366,7 +416,7 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
R.anim.recents_launch_prev_affiliated_task_source);
}
if (toTaskKey != null) {
- toTask = stack.findTaskWithId(toTaskKey.id);
+ toTask = focusedStack.findTaskWithId(toTaskKey.id);
}
numAffiliatedTasks = group.getTaskCount();
break;
@@ -399,11 +449,13 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
}
}
- public void onShowNextAffiliatedTask() {
+ @Override
+ public void showNextAffiliatedTask() {
showRelativeAffiliatedTask(true);
}
- public void onShowPrevAffiliatedTask() {
+ @Override
+ public void showPrevAffiliatedTask() {
showRelativeAffiliatedTask(false);
}
@@ -438,8 +490,9 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
// 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);
+ mConfig.getAvailableTaskStackBounds(mWindowRect.width(), mWindowRect.height(),
+ mStatusBarHeight, (mConfig.hasTransposedNavBar ? mNavBarWidth : 0),
+ mTaskStackBounds);
if (mConfig.isLandscape && mConfig.hasTransposedNavBar) {
mSystemInsets.set(0, mStatusBarHeight, mNavBarWidth, 0);
} else {
@@ -611,14 +664,32 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
/** Starts the recents activity */
void startRecentsActivity(ActivityManager.RunningTaskInfo topTask, boolean isTopTaskHome) {
+ RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
+ RecentsConfiguration.reinitialize(mContext, mSystemServicesProxy);
+
if (sInstanceLoadPlan == null) {
// Create a new load plan if onPreloadRecents() was never triggered
- RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
sInstanceLoadPlan = loader.createLoadPlan(mContext);
}
- RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
+
+ // Temporarily skip the transition (use a dummy fade) if multi stack is enabled.
+ // For multi-stack we need to figure out where each of the tasks are going.
+ if (mConfig.multiStackEnabled) {
+ loader.preloadTasks(sInstanceLoadPlan, true);
+ ArrayList<TaskStack> stacks = sInstanceLoadPlan.getAllTaskStacks();
+ TaskStack stack = stacks.get(0);
+ mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, true);
+ TaskStackViewLayoutAlgorithm.VisibilityReport stackVr =
+ mDummyStackView.computeStackVisibilityReport();
+ ActivityOptions opts = getUnknownTransitionActivityOptions();
+ startAlternateRecentsActivity(topTask, opts, true /* fromHome */,
+ false /* fromSearchHome */, false /* fromThumbnail */, stackVr);
+ return;
+ }
+
loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome);
- TaskStack stack = sInstanceLoadPlan.getTaskStack();
+ ArrayList<TaskStack> stacks = sInstanceLoadPlan.getAllTaskStacks();
+ TaskStack stack = stacks.get(0);
// Prepare the dummy stack for the transition
mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, isTopTaskHome);
@@ -714,7 +785,8 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
}
/** Sets the RecentsComponent callbacks. */
- public void setRecentsComponentCallback(RecentsComponent.Callbacks cb) {
+ @Override
+ public void setCallback(RecentsComponent.Callbacks cb) {
sRecentsComponentCallbacks = cb;
}
@@ -737,6 +809,27 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
}
}
+ /** Notifies the status bar to trigger screen pinning. */
+ @ProxyFromAnyToPrimaryUser
+ public static void startScreenPinning(Context context, SystemServicesProxy ssp) {
+ if (ssp.isForegroundUserOwner()) {
+ onStartScreenPinning(context);
+ } else {
+ Intent intent = createLocalBroadcastIntent(context,
+ ACTION_PROXY_SCREEN_PINNING_REQUEST_TO_OWNER);
+ context.sendBroadcastAsUser(intent, UserHandle.OWNER);
+ }
+ }
+ static void onStartScreenPinning(Context context) {
+ // For the primary user, the context for the SystemUI component is the SystemUIApplication
+ SystemUIApplication app = (SystemUIApplication)
+ getInstanceAndStartIfNeeded(context).mContext;
+ PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
+ if (statusBar != null) {
+ statusBar.showScreenPinningRequest(false);
+ }
+ }
+
/**
* Returns the preloaded load plan and invalidates it.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 7422e36..130d3bc 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -19,7 +19,6 @@ package com.android.systemui.recents;
import android.app.Activity;
import android.app.ActivityOptions;
import android.app.SearchManager;
-import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.BroadcastReceiver;
@@ -35,7 +34,6 @@ 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;
@@ -43,15 +41,12 @@ import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.RecentsTaskLoadPlan;
import com.android.systemui.recents.model.RecentsTaskLoader;
-import com.android.systemui.recents.model.SpaceNode;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
import com.android.systemui.recents.views.DebugOverlayView;
import com.android.systemui.recents.views.RecentsView;
import com.android.systemui.recents.views.SystemBarScrimViews;
import com.android.systemui.recents.views.ViewAnimation;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
-import com.android.systemui.SystemUIApplication;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
@@ -75,16 +70,17 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
View mEmptyView;
DebugOverlayView mDebugOverlay;
+ // Resize task debug
+ RecentsResizeTaskDialog mResizeTaskDebugDialog;
+
// Search AppWidget
- RecentsAppWidgetHost mAppWidgetHost;
AppWidgetProviderInfo mSearchAppWidgetInfo;
- AppWidgetHostView mSearchAppWidgetHostView;
+ RecentsAppWidgetHost mAppWidgetHost;
+ RecentsAppWidgetHostView mSearchAppWidgetHostView;
// 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
@@ -129,20 +125,20 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
- if (action.equals(AlternateRecentsComponent.ACTION_HIDE_RECENTS_ACTIVITY)) {
- if (intent.getBooleanExtra(AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_ALT_TAB, false)) {
+ if (action.equals(Recents.ACTION_HIDE_RECENTS_ACTIVITY)) {
+ if (intent.getBooleanExtra(Recents.EXTRA_TRIGGERED_FROM_ALT_TAB, false)) {
// If we are hiding from releasing Alt-Tab, dismiss Recents to the focused app
dismissRecentsToFocusedTaskOrHome(false);
- } else if (intent.getBooleanExtra(AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_HOME_KEY, false)) {
+ } else if (intent.getBooleanExtra(Recents.EXTRA_TRIGGERED_FROM_HOME_KEY, false)) {
// Otherwise, dismiss Recents to Home
dismissRecentsToHome(true);
} else {
// Do nothing, another activity is being launched on top of Recents
}
- } else if (action.equals(AlternateRecentsComponent.ACTION_TOGGLE_RECENTS_ACTIVITY)) {
+ } else if (action.equals(Recents.ACTION_TOGGLE_RECENTS_ACTIVITY)) {
// If we are toggling Recents, then first unfilter any filtered stacks first
dismissRecentsToFocusedTaskOrHome(true);
- } else if (action.equals(AlternateRecentsComponent.ACTION_START_ENTER_ANIMATION)) {
+ } else if (action.equals(Recents.ACTION_START_ENTER_ANIMATION)) {
// Trigger the enter animation
onEnterAnimationTriggered();
// Notify the fallback receiver that we have successfully got the broadcast
@@ -180,17 +176,17 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
});
/** Updates the set of recent tasks */
- void updateRecentsTasks(Intent launchIntent) {
+ void updateRecentsTasks() {
// If AlternateRecentsComponent has preloaded a load plan, then use that to prevent
// reconstructing the task stack
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
- RecentsTaskLoadPlan plan = AlternateRecentsComponent.consumeInstanceLoadPlan();
+ RecentsTaskLoadPlan plan = Recents.consumeInstanceLoadPlan();
if (plan == null) {
plan = loader.createLoadPlan(this);
}
// Start loading tasks according to the load plan
- if (plan.getTaskStack() == null) {
+ if (!plan.hasTasks()) {
loader.preloadTasks(plan, mConfig.launchedFromHome);
}
RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
@@ -199,13 +195,11 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
loadOpts.numVisibleTaskThumbnails = mConfig.launchedNumVisibleThumbnails;
loader.loadTasks(this, plan, loadOpts);
- SpaceNode root = plan.getSpaceNode();
- ArrayList<TaskStack> stacks = root.getStacks();
- boolean hasTasks = root.hasTasks();
- if (hasTasks) {
+ ArrayList<TaskStack> stacks = plan.getAllTaskStacks();
+ mConfig.launchedWithNoRecentTasks = !plan.hasTasks();
+ if (!mConfig.launchedWithNoRecentTasks) {
mRecentsView.setTaskStacks(stacks);
}
- mConfig.launchedWithNoRecentTasks = !hasTasks;
// Create the home intent runnable
Intent homeIntent = new Intent(Intent.ACTION_MAIN, null);
@@ -247,7 +241,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
if (mEmptyView != null) {
mEmptyView.setVisibility(View.GONE);
}
- if (mRecentsView.hasSearchBar()) {
+ if (mRecentsView.hasValidSearchBar()) {
mRecentsView.setSearchBarVisibility(View.VISIBLE);
} else {
addSearchBarAppWidgetView();
@@ -297,8 +291,8 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
if (Constants.DebugFlags.App.EnableSearchLayout) {
int appWidgetId = mConfig.searchBarAppWidgetId;
if (appWidgetId >= 0) {
- mSearchAppWidgetHostView = mAppWidgetHost.createView(this, appWidgetId,
- mSearchAppWidgetInfo);
+ mSearchAppWidgetHostView = (RecentsAppWidgetHostView) mAppWidgetHost.createView(
+ this, appWidgetId, mSearchAppWidgetInfo);
Bundle opts = new Bundle();
opts.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX);
@@ -362,12 +356,11 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- // For the non-primary user, ensure that the SystemSericesProxy is initialized
+ // For the non-primary user, ensure that the SystemServicesProxy and configuration is
+ // initialized
RecentsTaskLoader.initialize(this);
-
- // Initialize the loader and the configuration
- mConfig = RecentsConfiguration.reinitialize(this,
- RecentsTaskLoader.getInstance().getSystemServicesProxy());
+ SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
+ mConfig = RecentsConfiguration.reinitialize(this, ssp);
// Initialize the widget host (the host id is static and does not change)
mAppWidgetHost = new RecentsAppWidgetHost(this, Constants.Values.App.AppWidgetHostId);
@@ -382,8 +375,6 @@ 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
@@ -422,9 +413,6 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
super.onNewIntent(intent);
setIntent(intent);
- // Reinitialize the configuration
- RecentsConfiguration.reinitialize(this, RecentsTaskLoader.getInstance().getSystemServicesProxy());
-
// Clear any debug rects
if (mDebugOverlay != null) {
mDebugOverlay.clear();
@@ -436,20 +424,26 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
super.onStart();
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
SystemServicesProxy ssp = loader.getSystemServicesProxy();
- AlternateRecentsComponent.notifyVisibilityChanged(this, ssp, true);
+ Recents.notifyVisibilityChanged(this, ssp, true);
// Register the broadcast receiver to handle messages from our service
IntentFilter filter = new IntentFilter();
- filter.addAction(AlternateRecentsComponent.ACTION_HIDE_RECENTS_ACTIVITY);
- filter.addAction(AlternateRecentsComponent.ACTION_TOGGLE_RECENTS_ACTIVITY);
- filter.addAction(AlternateRecentsComponent.ACTION_START_ENTER_ANIMATION);
+ filter.addAction(Recents.ACTION_HIDE_RECENTS_ACTIVITY);
+ filter.addAction(Recents.ACTION_TOGGLE_RECENTS_ACTIVITY);
+ filter.addAction(Recents.ACTION_START_ENTER_ANIMATION);
registerReceiver(mServiceBroadcastReceiver, filter);
// Register any broadcast receivers for the task loader
loader.registerReceivers(this, mRecentsView);
// Update the recent tasks
- updateRecentsTasks(getIntent());
+ updateRecentsTasks();
+
+ // If this is a new instance from a configuration change, then we have to manually trigger
+ // the enter animation state
+ if (mConfig.launchedHasConfigurationChanged) {
+ onEnterAnimationTriggered();
+ }
}
@Override
@@ -457,7 +451,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
super.onStop();
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
SystemServicesProxy ssp = loader.getSystemServicesProxy();
- AlternateRecentsComponent.notifyVisibilityChanged(this, ssp, false);
+ Recents.notifyVisibilityChanged(this, ssp, false);
// Notify the views that we are no longer visible
mRecentsView.onRecentsHidden();
@@ -589,6 +583,21 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
}
}
+
+ /**** RecentsResizeTaskDialog ****/
+
+ private RecentsResizeTaskDialog getResizeTaskDebugDialog() {
+ if (mResizeTaskDebugDialog == null) {
+ mResizeTaskDebugDialog = new RecentsResizeTaskDialog(getFragmentManager(), this);
+ }
+ return mResizeTaskDebugDialog;
+ }
+
+ @Override
+ public void onTaskResize(Task t) {
+ getResizeTaskDebugDialog().showResizeTaskDialog(t, mRecentsView);
+ }
+
/**** RecentsView.RecentsViewCallbacks Implementation ****/
@Override
@@ -614,9 +623,9 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
@Override
public void onScreenPinningRequest() {
- if (mStatusBar != null) {
- mStatusBar.showScreenPinningRequest(false);
- }
+ RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
+ SystemServicesProxy ssp = loader.getSystemServicesProxy();
+ Recents.startScreenPinning(this, ssp);
}
/**** RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks Implementation ****/
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java
index 5bae37a..02a7b94 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java
@@ -17,6 +17,7 @@
package com.android.systemui.recents;
import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
import com.android.systemui.recents.misc.SystemServicesProxy;
@@ -61,6 +62,12 @@ public class RecentsAppWidgetHost extends AppWidgetHost {
}
@Override
+ protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
+ AppWidgetProviderInfo appWidget) {
+ return new RecentsAppWidgetHostView(context);
+ }
+
+ @Override
protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidgetInfo) {
if (mCb == null) return;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHostView.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHostView.java
new file mode 100644
index 0000000..1ed755a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHostView.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents;
+
+import android.appwidget.AppWidgetHostView;
+import android.content.Context;
+import android.widget.RemoteViews;
+
+public class RecentsAppWidgetHostView extends AppWidgetHostView {
+
+ private Context mContext;
+ private int mPreviousOrientation;
+
+ public RecentsAppWidgetHostView(Context context) {
+ super(context);
+ mContext = context;
+ }
+
+ @Override
+ public void updateAppWidget(RemoteViews remoteViews) {
+ // Store the orientation in which the widget was inflated
+ updateLastInflationOrientation();
+ super.updateAppWidget(remoteViews);
+ }
+
+ /**
+ * Updates the last orientation that this widget was inflated.
+ */
+ private void updateLastInflationOrientation() {
+ mPreviousOrientation = mContext.getResources().getConfiguration().orientation;
+ }
+
+ /**
+ * @return whether the search widget was updated while Recents was in a different orientation
+ * in the background.
+ */
+ public boolean isReinflateRequired() {
+ // Re-inflate is required if the orientation has changed since last inflated.
+ int orientation = mContext.getResources().getConfiguration().orientation;
+ if (mPreviousOrientation != orientation) {
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index 52e7e7f..abeb2b0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -24,7 +24,6 @@ import android.content.res.Resources;
import android.graphics.Rect;
import android.provider.Settings;
import android.util.DisplayMetrics;
-import android.util.TypedValue;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import com.android.systemui.R;
@@ -50,9 +49,6 @@ public class RecentsConfiguration {
// Disable all thumbnail loading.
public static final int SVELTE_DISABLE_LOADING = 3;
- /** Animations */
- public float animationPxMovementPerSecond;
-
/** Interpolators */
public Interpolator fastOutSlowInInterpolator;
public Interpolator fastOutLinearInInterpolator;
@@ -83,6 +79,7 @@ public class RecentsConfiguration {
public int taskStackScrollDuration;
public int taskStackMaxDim;
public int taskStackTopPaddingPx;
+ public int dismissAllButtonSizePx;
public float taskStackWidthPaddingPct;
public float taskStackOverscrollPct;
@@ -137,6 +134,7 @@ public class RecentsConfiguration {
public boolean fakeShadows;
/** Dev options and global settings */
+ public boolean multiStackEnabled;
public boolean lockToAppEnabled;
public boolean developerOptionsEnabled;
public boolean debugModeEnabled;
@@ -197,10 +195,6 @@ public class RecentsConfiguration {
// Insets
displayRect.set(0, 0, dm.widthPixels, dm.heightPixels);
- // Animations
- animationPxMovementPerSecond =
- res.getDimensionPixelSize(R.dimen.recents_animation_movement_in_dps_per_second);
-
// Filtering
filteringCurrentViewsAnimDuration =
res.getInteger(R.integer.recents_filter_animate_current_views_duration);
@@ -217,14 +211,11 @@ public class RecentsConfiguration {
// Task stack
taskStackScrollDuration =
res.getInteger(R.integer.recents_animate_task_stack_scroll_duration);
- TypedValue widthPaddingPctValue = new TypedValue();
- res.getValue(R.dimen.recents_stack_width_padding_percentage, widthPaddingPctValue, true);
- taskStackWidthPaddingPct = widthPaddingPctValue.getFloat();
- TypedValue stackOverscrollPctValue = new TypedValue();
- res.getValue(R.dimen.recents_stack_overscroll_percentage, stackOverscrollPctValue, true);
- taskStackOverscrollPct = stackOverscrollPctValue.getFloat();
+ taskStackWidthPaddingPct = res.getFloat(R.dimen.recents_stack_width_padding_percentage);
+ taskStackOverscrollPct = res.getFloat(R.dimen.recents_stack_overscroll_percentage);
taskStackMaxDim = res.getInteger(R.integer.recents_max_task_stack_view_dim);
taskStackTopPaddingPx = res.getDimensionPixelSize(R.dimen.recents_stack_top_padding);
+ dismissAllButtonSizePx = res.getDimensionPixelSize(R.dimen.recents_dismiss_all_button_size);
// Transition
transitionEnterFromAppDelay =
@@ -254,22 +245,16 @@ public class RecentsConfiguration {
taskViewTranslationZMaxPx = res.getDimensionPixelSize(R.dimen.recents_task_view_z_max);
taskViewAffiliateGroupEnterOffsetPx =
res.getDimensionPixelSize(R.dimen.recents_task_view_affiliate_group_enter_offset);
- TypedValue thumbnailAlphaValue = new TypedValue();
- res.getValue(R.dimen.recents_task_view_thumbnail_alpha, thumbnailAlphaValue, true);
- taskViewThumbnailAlpha = thumbnailAlphaValue.getFloat();
+ taskViewThumbnailAlpha = res.getFloat(R.dimen.recents_task_view_thumbnail_alpha);
// Task bar colors
- taskBarViewDefaultBackgroundColor =
- res.getColor(R.color.recents_task_bar_default_background_color);
- taskBarViewLightTextColor =
- res.getColor(R.color.recents_task_bar_light_text_color);
- taskBarViewDarkTextColor =
- res.getColor(R.color.recents_task_bar_dark_text_color);
- taskBarViewHighlightColor =
- res.getColor(R.color.recents_task_bar_highlight_color);
- TypedValue affMinAlphaPctValue = new TypedValue();
- res.getValue(R.dimen.recents_task_affiliation_color_min_alpha_percentage, affMinAlphaPctValue, true);
- taskBarViewAffiliationColorMinAlpha = affMinAlphaPctValue.getFloat();
+ taskBarViewDefaultBackgroundColor = context.getColor(
+ R.color.recents_task_bar_default_background_color);
+ taskBarViewLightTextColor = context.getColor(R.color.recents_task_bar_light_text_color);
+ taskBarViewDarkTextColor = context.getColor(R.color.recents_task_bar_dark_text_color);
+ taskBarViewHighlightColor = context.getColor(R.color.recents_task_bar_highlight_color);
+ taskBarViewAffiliationColorMinAlpha = res.getFloat(
+ R.dimen.recents_task_affiliation_color_min_alpha_percentage);
// Task bar size & animations
taskBarHeight = res.getDimensionPixelSize(R.dimen.recents_task_bar_height);
@@ -307,6 +292,7 @@ public class RecentsConfiguration {
Settings.Global.DEVELOPMENT_SETTINGS_ENABLED) != 0;
lockToAppEnabled = ssp.getSystemSetting(context,
Settings.System.LOCK_TO_APP_ENABLED) != 0;
+ multiStackEnabled = "true".equals(ssp.getSystemProperty("persist.sys.debug.multi_window"));
}
/** Called when the configuration has changed, and we want to reset any configuration specific
@@ -344,17 +330,12 @@ public class RecentsConfiguration {
return !launchedWithNoRecentTasks && (!hasTransposedNavBar || !isLandscape);
}
- /** Returns whether the current layout is horizontal. */
- public boolean hasHorizontalLayout() {
- return isLandscape && hasTransposedSearchBar;
- }
-
/**
* Returns the task stack bounds in the current orientation. These bounds do not account for
* the system insets.
*/
- public void getTaskStackBounds(int windowWidth, int windowHeight, int topInset, int rightInset,
- Rect taskStackBounds) {
+ public void getAvailableTaskStackBounds(int windowWidth, int windowHeight, int topInset,
+ int rightInset, Rect taskStackBounds) {
Rect searchBarBounds = new Rect();
getSearchBarBounds(windowWidth, windowHeight, topInset, searchBarBounds);
if (isLandscape && hasTransposedSearchBar) {
@@ -371,7 +352,7 @@ public class RecentsConfiguration {
* the system insets.
*/
public void getSearchBarBounds(int windowWidth, int windowHeight, int topInset,
- Rect searchBarSpaceBounds) {
+ Rect searchBarSpaceBounds) {
// Return empty rects if search is not enabled
int searchBarSize = searchBarSpaceHeightPx;
if (!Constants.DebugFlags.App.EnableSearchLayout || !hasSearchBarAppWidget()) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java
new file mode 100644
index 0000000..4cd577d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.FragmentManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import com.android.systemui.R;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.model.RecentsTaskLoader;
+import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.RecentsActivity;
+import com.android.systemui.recents.views.RecentsView;
+
+import java.util.ArrayList;
+
+/**
+ * A helper for the dialogs that show when task debugging is on.
+ */
+public class RecentsResizeTaskDialog extends DialogFragment {
+
+ static final String TAG = "RecentsResizeTaskDialog";
+
+ // The various window arrangements we can handle.
+ private static final int PLACE_LEFT = 1;
+ private static final int PLACE_RIGHT = 2;
+ private static final int PLACE_TOP = 3;
+ private static final int PLACE_BOTTOM = 4;
+ private static final int PLACE_TOP_LEFT = 5;
+ private static final int PLACE_TOP_RIGHT = 6;
+ private static final int PLACE_BOTTOM_LEFT = 7;
+ private static final int PLACE_BOTTOM_RIGHT = 8;
+ private static final int PLACE_FULL = 9;
+
+ // The button resource ID combined with the arrangement command.
+ private static final int[][] BUTTON_DEFINITIONS =
+ {{R.id.place_left, PLACE_LEFT},
+ {R.id.place_right, PLACE_RIGHT},
+ {R.id.place_top, PLACE_TOP},
+ {R.id.place_bottom, PLACE_BOTTOM},
+ {R.id.place_top_left, PLACE_TOP_LEFT},
+ {R.id.place_top_right, PLACE_TOP_RIGHT},
+ {R.id.place_bottom_left, PLACE_BOTTOM_LEFT},
+ {R.id.place_bottom_right, PLACE_BOTTOM_RIGHT},
+ {R.id.place_full, PLACE_FULL}};
+
+ // The task we want to resize.
+ private FragmentManager mFragmentManager;
+ private View mResizeTaskDialogContent;
+ private RecentsActivity mRecentsActivity;
+ private RecentsView mRecentsView;
+ private SystemServicesProxy mSsp;
+ private Rect[] mBounds = {new Rect(), new Rect(), new Rect(), new Rect()};
+ private Task[] mTasks = {null, null, null, null};
+
+ public RecentsResizeTaskDialog(FragmentManager mgr, RecentsActivity activity) {
+ mFragmentManager = mgr;
+ mRecentsActivity = activity;
+ mSsp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
+ }
+
+ /** Shows the resize-task dialog. */
+ void showResizeTaskDialog(Task mainTask, RecentsView rv) {
+ mTasks[0] = mainTask;
+ mRecentsView = rv;
+
+ show(mFragmentManager, TAG);
+ }
+
+ /** Creates a new resize-task dialog. */
+ private void createResizeTaskDialog(final Context context, LayoutInflater inflater,
+ AlertDialog.Builder builder) {
+ builder.setTitle(R.string.recents_caption_resize);
+ mResizeTaskDialogContent =
+ inflater.inflate(R.layout.recents_task_resize_dialog, null, false);
+
+ for (int i = 0; i < BUTTON_DEFINITIONS.length; i++) {
+ Button b = (Button)mResizeTaskDialogContent.findViewById(BUTTON_DEFINITIONS[i][0]);
+ if (b != null) {
+ final int action = BUTTON_DEFINITIONS[i][1];
+ b.setOnClickListener(
+ new View.OnClickListener() {
+ public void onClick(View v) {
+ placeTasks(action);
+ }
+ });
+ }
+ }
+
+ builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dismiss();
+ }
+ });
+
+ builder.setView(mResizeTaskDialogContent);
+ }
+
+ /** Helper function to place window(s) on the display according to an arrangement request. */
+ private void placeTasks(int arrangement) {
+ Rect rect = mSsp.getWindowRect();
+ for (int i = 0; i < mBounds.length; ++i) {
+ mBounds[i].set(rect);
+ if (i != 0) {
+ mTasks[i] = null;
+ }
+ }
+ int additionalTasks = 0;
+ switch (arrangement) {
+ case PLACE_LEFT:
+ mBounds[0].right = mBounds[0].centerX();
+ mBounds[1].left = mBounds[0].right;
+ additionalTasks = 1;
+ break;
+ case PLACE_RIGHT:
+ mBounds[1].right = mBounds[1].centerX();
+ mBounds[0].left = mBounds[1].right;
+ additionalTasks = 1;
+ break;
+ case PLACE_TOP:
+ mBounds[0].bottom = mBounds[0].centerY();
+ mBounds[1].top = mBounds[0].bottom;
+ additionalTasks = 1;
+ break;
+ case PLACE_BOTTOM:
+ mBounds[1].bottom = mBounds[1].centerY();
+ mBounds[0].top = mBounds[1].bottom;
+ additionalTasks = 1;
+ break;
+ case PLACE_TOP_LEFT: // TL, TR, BL, BR
+ mBounds[0].right = mBounds[0].centerX();
+ mBounds[0].bottom = mBounds[0].centerY();
+ mBounds[1].left = mBounds[0].right;
+ mBounds[1].bottom = mBounds[0].bottom;
+ mBounds[2].right = mBounds[0].right;
+ mBounds[2].top = mBounds[0].bottom;
+ mBounds[3].left = mBounds[0].right;
+ mBounds[3].top = mBounds[0].bottom;
+ additionalTasks = 3;
+ break;
+ case PLACE_TOP_RIGHT: // TR, TL, BR, BL
+ mBounds[0].left = mBounds[0].centerX();
+ mBounds[0].bottom = mBounds[0].centerY();
+ mBounds[1].right = mBounds[0].left;
+ mBounds[1].bottom = mBounds[0].bottom;
+ mBounds[2].left = mBounds[0].left;
+ mBounds[2].top = mBounds[0].bottom;
+ mBounds[3].right = mBounds[0].left;
+ mBounds[3].top = mBounds[0].bottom;
+ additionalTasks = 3;
+ break;
+ case PLACE_BOTTOM_LEFT: // BL, BR, TL, TR
+ mBounds[0].right = mBounds[0].centerX();
+ mBounds[0].top = mBounds[0].centerY();
+ mBounds[1].left = mBounds[0].right;
+ mBounds[1].top = mBounds[0].top;
+ mBounds[2].right = mBounds[0].right;
+ mBounds[2].bottom = mBounds[0].top;
+ mBounds[3].left = mBounds[0].right;
+ mBounds[3].bottom = mBounds[0].top;
+ additionalTasks = 3;
+ break;
+ case PLACE_BOTTOM_RIGHT: // BR, BL, TR, TL
+ mBounds[0].left = mBounds[0].centerX();
+ mBounds[0].top = mBounds[0].centerY();
+ mBounds[1].right = mBounds[0].left;
+ mBounds[1].top = mBounds[0].top;
+ mBounds[2].left = mBounds[0].left;
+ mBounds[2].bottom = mBounds[0].top;
+ mBounds[3].right = mBounds[0].left;
+ mBounds[3].bottom = mBounds[0].top;
+ additionalTasks = 3;
+ break;
+ case PLACE_FULL:
+ // Nothing to change.
+ break;
+ }
+
+ // Get the other tasks.
+ for (int i = 1; i <= additionalTasks && mTasks[i - 1] != null; ++i) {
+ mTasks[i] = mRecentsView.getNextTaskOrTopTask(mTasks[i - 1]);
+ // Do stop if we circled back to the first item.
+ if (mTasks[i] == mTasks[0]) {
+ mTasks[i] = null;
+ }
+ }
+
+ // Resize all tasks beginning from the "oldest" one.
+ for (int i = additionalTasks; i >= 0; --i) {
+ if (mTasks[i] != null) {
+ mSsp.resizeTask(mTasks[i].key.id, mBounds[i]);
+ }
+ }
+
+ // Get rid of the dialog.
+ dismiss();
+ mRecentsActivity.dismissRecentsToHomeRaw(false);
+
+ // Show tasks - beginning with the oldest so that the focus ends on the selected one.
+ // TODO: Remove this once issue b/19893373 is resolved.
+ for (int i = additionalTasks; i >= 0; --i) {
+ if (mTasks[i] != null) {
+ mRecentsView.launchTask(mTasks[i]);
+ }
+ }
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle args) {
+ final Context context = this.getActivity();
+ LayoutInflater inflater = getActivity().getLayoutInflater();
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ createResizeTaskDialog(context, inflater, builder);
+ return builder.create();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsUserEventProxyReceiver.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsUserEventProxyReceiver.java
index 236da5d..5eefbc7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsUserEventProxyReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsUserEventProxyReceiver.java
@@ -19,8 +19,6 @@ package com.android.systemui.recents;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import com.android.systemui.recent.Recents;
-
/**
* A proxy for Recents events which happens strictly for non-owner users.
@@ -39,28 +37,27 @@ public class RecentsUserEventProxyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
- AlternateRecentsComponent recents = Recents.getRecentsComponent(
- context.getApplicationContext(), true);
+ Recents recents = Recents.getInstanceAndStartIfNeeded(context);
switch (intent.getAction()) {
case ACTION_PROXY_SHOW_RECENTS_TO_USER: {
boolean triggeredFromAltTab = intent.getBooleanExtra(
- AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_ALT_TAB, false);
- recents.showRecents(triggeredFromAltTab);
+ Recents.EXTRA_TRIGGERED_FROM_ALT_TAB, false);
+ recents.showRecentsInternal(triggeredFromAltTab);
break;
}
case ACTION_PROXY_HIDE_RECENTS_TO_USER: {
boolean triggeredFromAltTab = intent.getBooleanExtra(
- AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_ALT_TAB, false);
+ Recents.EXTRA_TRIGGERED_FROM_ALT_TAB, false);
boolean triggeredFromHome = intent.getBooleanExtra(
- AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_HOME_KEY, false);
- recents.hideRecents(triggeredFromAltTab, triggeredFromHome);
+ Recents.EXTRA_TRIGGERED_FROM_HOME_KEY, false);
+ recents.hideRecentsInternal(triggeredFromAltTab, triggeredFromHome);
break;
}
case ACTION_PROXY_TOGGLE_RECENTS_TO_USER:
- recents.toggleRecents();
+ recents.toggleRecentsInternal();
break;
case ACTION_PROXY_PRELOAD_RECENTS_TO_USER:
- recents.preloadRecents();
+ recents.preloadRecentsInternal();
break;
case ACTION_PROXY_CONFIG_CHANGE_TO_USER:
recents.configurationChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/recent/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index 2fa0b58..cbf5c05 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.recent;
+package com.android.systemui.recents;
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
@@ -41,7 +41,6 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.systemui.R;
-import com.android.systemui.recents.model.RecentsTaskLoader;
import java.util.ArrayList;
@@ -145,7 +144,7 @@ public class ScreenPinningRequest implements View.OnClickListener {
boolean isLandscape = isLandscapePhone(mContext);
inflateView(isLandscape);
- int bgColor = mContext.getResources().getColor(
+ int bgColor = mContext.getColor(
R.color.screen_pinning_request_window_bg);
if (ActivityManager.isHighEndGfx()) {
mLayout.setAlpha(0f);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index 90b099c..d60df9c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -20,6 +20,7 @@ import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.app.ActivityOptions;
import android.app.AppGlobals;
+import android.app.IActivityContainer;
import android.app.IActivityManager;
import android.app.ITaskStackListener;
import android.app.SearchManager;
@@ -49,21 +50,25 @@ import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
import android.util.Pair;
+import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
import com.android.systemui.R;
-import com.android.systemui.recents.AlternateRecentsComponent;
import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.Recents;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
@@ -214,7 +219,7 @@ public class SystemServicesProxy {
}
/** Returns a list of the running tasks */
- public List<ActivityManager.RunningTaskInfo> getRunningTasks(int numTasks) {
+ private List<ActivityManager.RunningTaskInfo> getRunningTasks(int numTasks) {
if (mAm == null) return null;
return mAm.getRunningTasks(numTasks);
}
@@ -235,8 +240,8 @@ public class SystemServicesProxy {
ComponentName topActivity = topTask.topActivity;
// Check if the front most activity is recents
- if (topActivity.getPackageName().equals(AlternateRecentsComponent.sRecentsPackage) &&
- topActivity.getClassName().equals(AlternateRecentsComponent.sRecentsActivity)) {
+ if (topActivity.getPackageName().equals(Recents.sRecentsPackage) &&
+ topActivity.getClassName().equals(Recents.sRecentsActivity)) {
if (isHomeTopMost != null) {
isHomeTopMost.set(false);
}
@@ -250,6 +255,57 @@ public class SystemServicesProxy {
return false;
}
+ /** Get the bounds of a stack / task. */
+ public Rect getTaskBounds(int stackId) {
+ ActivityManager.StackInfo info = getAllStackInfos().get(stackId);
+ if (info != null)
+ return info.bounds;
+ return new Rect();
+ }
+
+ /** Resize a given task. */
+ public void resizeTask(int taskId, Rect bounds) {
+ if (mIam == null) return;
+
+ try {
+ mIam.resizeTask(taskId, bounds);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /** Returns the stack info for all stacks. */
+ public SparseArray<ActivityManager.StackInfo> getAllStackInfos() {
+ if (mIam == null) return new SparseArray<ActivityManager.StackInfo>();
+
+ try {
+ SparseArray<ActivityManager.StackInfo> stacks =
+ new SparseArray<ActivityManager.StackInfo>();
+ List<ActivityManager.StackInfo> infos = mIam.getAllStackInfos();
+ int stackCount = infos.size();
+ for (int i = 0; i < stackCount; i++) {
+ ActivityManager.StackInfo info = infos.get(i);
+ stacks.put(info.stackId, info);
+ }
+ return stacks;
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ return new SparseArray<ActivityManager.StackInfo>();
+ }
+ }
+
+ /** Returns the focused stack id. */
+ public int getFocusedStack() {
+ if (mIam == null) return -1;
+
+ try {
+ return mIam.getFocusedStackId();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ return -1;
+ }
+ }
+
/** Returns whether the specified task is in the home stack */
public boolean isInHomeStack(int taskId) {
if (mAm == null) return false;
@@ -313,7 +369,7 @@ public class SystemServicesProxy {
return thumbnail;
}
- /** Moves a task to the front with the specified activity options */
+ /** Moves a task to the front with the specified activity options. */
public void moveTaskToFront(int taskId, ActivityOptions opts) {
if (mAm == null) return;
if (Constants.DebugFlags.App.EnableSystemServicesProxy) return;
@@ -524,6 +580,13 @@ public class SystemServicesProxy {
}
/**
+ * Returns a system property.
+ */
+ public String getSystemProperty(String key) {
+ return SystemProperties.get(key);
+ }
+
+ /**
* Returns the window rect.
*/
public Rect getWindowRect() {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
index e1179fa..84544ff 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
@@ -21,7 +21,6 @@ import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.view.View;
-import com.android.systemui.recents.RecentsConfiguration;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -34,7 +33,7 @@ public class Utilities {
private static Method sPropertyMethod;
static {
try {
- Class<?> c = Class.forName("android.view.GLES20Canvas");
+ Class<?> c = Class.forName("android.view.DisplayListCanvas");
sPropertyMethod = c.getDeclaredMethod("setProperty", String.class, String.class);
if (!sPropertyMethod.isAccessible()) sPropertyMethod.setAccessible(true);
} catch (ClassNotFoundException e) {
@@ -44,19 +43,6 @@ public class Utilities {
}
}
- /**
- * Calculates a consistent animation duration (ms) for all animations depending on the movement
- * of the object being animated.
- */
- public static int calculateTranslationAnimationDuration(int distancePx) {
- return calculateTranslationAnimationDuration(distancePx, 100);
- }
- public static int calculateTranslationAnimationDuration(int distancePx, int minDuration) {
- RecentsConfiguration config = RecentsConfiguration.getInstance();
- return Math.max(minDuration, (int) (1000f /* ms/s */ *
- (Math.abs(distancePx) / config.animationPxMovementPerSecond)));
- }
-
/** Scales a rect about its centroid */
public static void scaleRectAboutCenter(Rect r, float scale) {
if (scale != 1.0f) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 0e1c01a..5d98dda 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -20,9 +20,12 @@ import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.util.Log;
+import android.util.SparseArray;
+import com.android.systemui.recents.Constants;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.misc.SystemServicesProxy;
@@ -34,11 +37,11 @@ import java.util.List;
/**
* This class stores the loading state as it goes through multiple stages of loading:
- * - preloadRawTasks() will load the raw set of recents tasks from the system
- * - preloadPlan() will construct a new task stack with all metadata and only icons and thumbnails
- * that are currently in the cache
- * - executePlan() will actually load and fill in the icons and thumbnails according to the load
- * options specified, such that we can transition into the Recents activity seamlessly
+ * 1) preloadRawTasks() will load the raw set of recents tasks from the system
+ * 2) preloadPlan() will construct a new task stack with all metadata and only icons and
+ * thumbnails that are currently in the cache
+ * 3) executePlan() will actually load and fill in the icons and thumbnails according to the load
+ * options specified, such that we can transition into the Recents activity seamlessly
*/
public class RecentsTaskLoadPlan {
static String TAG = "RecentsTaskLoadPlan";
@@ -60,7 +63,7 @@ public class RecentsTaskLoadPlan {
SystemServicesProxy mSystemServicesProxy;
List<ActivityManager.RecentTaskInfo> mRawTasks;
- TaskStack mStack;
+ SparseArray<TaskStack> mStacks = new SparseArray<>();
HashMap<Task.ComponentNameKey, ActivityInfoHandle> mActivityInfoCache =
new HashMap<Task.ComponentNameKey, ActivityInfoHandle>();
@@ -90,21 +93,28 @@ public class RecentsTaskLoadPlan {
synchronized void preloadPlan(RecentsTaskLoader loader, boolean isTopTaskHome) {
if (DEBUG) Log.d(TAG, "preloadPlan");
+ // This activity info cache will be used for both preloadPlan() and executePlan()
mActivityInfoCache.clear();
- mStack = new TaskStack();
+
+ // TODO (multi-display): Currently assume the primary display
+ Rect displayBounds = mSystemServicesProxy.getWindowRect();
Resources res = mContext.getResources();
- ArrayList<Task> loadedTasks = new ArrayList<Task>();
+ SparseArray<ArrayList<Task>> stacksTasks = new SparseArray<>();
if (mRawTasks == null) {
preloadRawTasks(isTopTaskHome);
}
+ int firstStackId = -1;
int taskCount = mRawTasks.size();
for (int i = 0; i < taskCount; i++) {
ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
+ if (firstStackId < 0) {
+ firstStackId = t.stackId;
+ }
// Compose the task key
- Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.baseIntent, t.userId,
- t.firstActiveTime, t.lastActiveTime);
+ Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.stackId, t.baseIntent,
+ t.userId, t.firstActiveTime, t.lastActiveTime);
// Get an existing activity info handle if possible
Task.ComponentNameKey cnKey = taskKey.getComponentNameKey();
@@ -143,14 +153,43 @@ public class RecentsTaskLoadPlan {
iconFilename);
task.thumbnail = loader.getAndUpdateThumbnail(taskKey, mSystemServicesProxy, false);
if (DEBUG) Log.d(TAG, "\tthumbnail: " + taskKey + ", " + task.thumbnail);
- loadedTasks.add(task);
+
+ if (!mConfig.multiStackEnabled ||
+ Constants.DebugFlags.App.EnableMultiStackToSingleStack) {
+ firstStackId = 0;
+ ArrayList<Task> stackTasks = stacksTasks.get(firstStackId);
+ if (stackTasks == null) {
+ stackTasks = new ArrayList<Task>();
+ stacksTasks.put(firstStackId, stackTasks);
+ }
+ stackTasks.add(task);
+ } else {
+ ArrayList<Task> stackTasks = stacksTasks.get(t.stackId);
+ if (stackTasks == null) {
+ stackTasks = new ArrayList<Task>();
+ stacksTasks.put(t.stackId, stackTasks);
+ }
+ stackTasks.add(task);
+ }
}
- mStack.setTasks(loadedTasks);
- mStack.createAffiliatedGroupings(mConfig);
- // Assertion
- if (mStack.getTaskCount() != mRawTasks.size()) {
- throw new RuntimeException("Loading failed");
+ // Initialize the stacks
+ SparseArray<ActivityManager.StackInfo> stackInfos = mSystemServicesProxy.getAllStackInfos();
+ mStacks.clear();
+ int stackCount = stacksTasks.size();
+ for (int i = 0; i < stackCount; i++) {
+ int stackId = stacksTasks.keyAt(i);
+ ActivityManager.StackInfo info = stackInfos.get(stackId);
+ ArrayList<Task> stackTasks = stacksTasks.valueAt(i);
+ TaskStack stack = new TaskStack(stackId);
+ if (Constants.DebugFlags.App.EnableMultiStackToSingleStack) {
+ stack.setBounds(displayBounds, displayBounds);
+ } else {
+ stack.setBounds(info.bounds, displayBounds);
+ }
+ stack.setTasks(stackTasks);
+ stack.createAffiliatedGroupings(mConfig);
+ mStacks.put(stackId, stack);
}
}
@@ -166,72 +205,93 @@ public class RecentsTaskLoadPlan {
Resources res = mContext.getResources();
// Iterate through each of the tasks and load them according to the load conditions.
- ArrayList<Task> tasks = mStack.getTasks();
- int taskCount = tasks.size();
- for (int i = 0; i < taskCount; i++) {
- ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
- Task task = tasks.get(i);
- Task.TaskKey taskKey = task.key;
+ int stackCount = mStacks.size();
+ for (int j = 0; j < stackCount; j++) {
+ ArrayList<Task> tasks = mStacks.valueAt(j).getTasks();
+ int taskCount = tasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
+ Task task = tasks.get(i);
+ Task.TaskKey taskKey = task.key;
- // Get an existing activity info handle if possible
- Task.ComponentNameKey cnKey = taskKey.getComponentNameKey();
- ActivityInfoHandle infoHandle;
- boolean hadCachedActivityInfo = false;
- if (mActivityInfoCache.containsKey(cnKey)) {
- infoHandle = mActivityInfoCache.get(cnKey);
- hadCachedActivityInfo = true;
- } else {
- infoHandle = new ActivityInfoHandle();
- }
+ // Get an existing activity info handle if possible
+ Task.ComponentNameKey cnKey = taskKey.getComponentNameKey();
+ ActivityInfoHandle infoHandle;
+ boolean hadCachedActivityInfo = false;
+ if (mActivityInfoCache.containsKey(cnKey)) {
+ infoHandle = mActivityInfoCache.get(cnKey);
+ hadCachedActivityInfo = true;
+ } else {
+ infoHandle = new ActivityInfoHandle();
+ }
- boolean isRunningTask = (task.key.id == opts.runningTaskId);
- boolean isVisibleTask = i >= (taskCount - opts.numVisibleTasks);
- boolean isVisibleThumbnail = i >= (taskCount - opts.numVisibleTaskThumbnails);
+ boolean isRunningTask = (task.key.id == opts.runningTaskId);
+ boolean isVisibleTask = i >= (taskCount - opts.numVisibleTasks);
+ boolean isVisibleThumbnail = i >= (taskCount - opts.numVisibleTaskThumbnails);
- // If requested, skip the running task
- if (opts.onlyLoadPausedActivities && isRunningTask) {
- continue;
- }
+ // If requested, skip the running task
+ if (opts.onlyLoadPausedActivities && isRunningTask) {
+ continue;
+ }
- if (opts.loadIcons && (isRunningTask || isVisibleTask)) {
- if (task.activityIcon == null) {
- if (DEBUG) Log.d(TAG, "\tLoading icon: " + taskKey);
- task.activityIcon = loader.getAndUpdateActivityIcon(taskKey, t.taskDescription,
- mSystemServicesProxy, res, infoHandle, true);
+ if (opts.loadIcons && (isRunningTask || isVisibleTask)) {
+ if (task.activityIcon == null) {
+ if (DEBUG) Log.d(TAG, "\tLoading icon: " + taskKey);
+ task.activityIcon = loader.getAndUpdateActivityIcon(taskKey,
+ t.taskDescription, mSystemServicesProxy, res, infoHandle, true);
+ }
}
- }
- if (opts.loadThumbnails && (isRunningTask || isVisibleThumbnail)) {
- if (task.thumbnail == null || isRunningTask) {
- if (DEBUG) Log.d(TAG, "\tLoading thumbnail: " + taskKey);
- if (mConfig.svelteLevel <= RecentsConfiguration.SVELTE_LIMIT_CACHE) {
- task.thumbnail = loader.getAndUpdateThumbnail(taskKey, mSystemServicesProxy,
- true);
- } else if (mConfig.svelteLevel == RecentsConfiguration.SVELTE_DISABLE_CACHE) {
- loadQueue.addTask(task);
+ if (opts.loadThumbnails && (isRunningTask || isVisibleThumbnail)) {
+ if (task.thumbnail == null || isRunningTask) {
+ if (DEBUG) Log.d(TAG, "\tLoading thumbnail: " + taskKey);
+ if (mConfig.svelteLevel <= RecentsConfiguration.SVELTE_LIMIT_CACHE) {
+ task.thumbnail = loader.getAndUpdateThumbnail(taskKey,
+ mSystemServicesProxy, true);
+ } else if (mConfig.svelteLevel == RecentsConfiguration.SVELTE_DISABLE_CACHE) {
+ loadQueue.addTask(task);
+ }
}
}
- }
- // Update the activity info cache
- if (!hadCachedActivityInfo && infoHandle.info != null) {
- mActivityInfoCache.put(cnKey, infoHandle);
+ // Update the activity info cache
+ if (!hadCachedActivityInfo && infoHandle.info != null) {
+ mActivityInfoCache.put(cnKey, infoHandle);
+ }
}
}
}
/**
- * Composes and returns a TaskStack from the preloaded list of recent tasks.
+ * Returns all TaskStacks from the preloaded list of recent tasks.
*/
- public TaskStack getTaskStack() {
- return mStack;
+ public ArrayList<TaskStack> getAllTaskStacks() {
+ ArrayList<TaskStack> stacks = new ArrayList<TaskStack>();
+ int stackCount = mStacks.size();
+ for (int i = 0; i < stackCount; i++) {
+ stacks.add(mStacks.valueAt(i));
+ }
+ // Ensure that we have at least one stack
+ if (stacks.isEmpty()) {
+ stacks.add(new TaskStack());
+ }
+ return stacks;
}
/**
- * Composes and returns a SpaceNode from the preloaded list of recent tasks.
+ * Returns a specific TaskStack from the preloaded list of recent tasks.
*/
- public SpaceNode getSpaceNode() {
- SpaceNode node = new SpaceNode();
- node.setStack(mStack);
- return node;
+ public TaskStack getTaskStack(int stackId) {
+ return mStacks.get(stackId);
+ }
+
+ /** Returns whether there are any tasks in any stacks. */
+ public boolean hasTasks() {
+ int stackCount = mStacks.size();
+ for (int i = 0; i < stackCount; i++) {
+ if (mStacks.valueAt(i).getTaskCount() > 0) {
+ return true;
+ }
+ }
+ return false;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index ba2903a..3192fe6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -33,7 +33,6 @@ import com.android.systemui.recents.Constants;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import java.util.Collection;
import java.util.concurrent.ConcurrentLinkedQueue;
@@ -47,18 +46,6 @@ class TaskResourceLoadQueue {
ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<Task>();
/** Adds a new task to the load queue */
- void addTasks(Collection<Task> tasks) {
- for (Task t : tasks) {
- if (!mQueue.contains(t)) {
- mQueue.add(t);
- }
- }
- synchronized(this) {
- notifyAll();
- }
- }
-
- /** Adds a new task to the load queue */
void addTask(Task t) {
if (!mQueue.contains(t)) {
mQueue.add(t);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java b/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java
deleted file mode 100644
index 831698a..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.model;
-
-import android.graphics.Rect;
-
-import java.util.ArrayList;
-
-
-/**
- * The full recents space is partitioned using a BSP into various nodes that define where task
- * stacks should be placed.
- */
-public class SpaceNode {
- /* BSP node callbacks */
- public interface SpaceNodeCallbacks {
- /** Notifies when a node is added */
- public void onSpaceNodeAdded(SpaceNode node);
- /** Notifies when a node is measured */
- public void onSpaceNodeMeasured(SpaceNode node, Rect rect);
- }
-
- SpaceNode mStartNode;
- SpaceNode mEndNode;
-
- TaskStack mStack;
-
- public SpaceNode() {
- // Do nothing
- }
-
- /** Sets the current stack for this space node */
- public void setStack(TaskStack stack) {
- mStack = stack;
- }
-
- /** Returns the task stack (not null if this is a leaf) */
- TaskStack getStack() {
- return mStack;
- }
-
- /** Returns whether there are any tasks in any stacks below this node. */
- public boolean hasTasks() {
- return (mStack.getTaskCount() > 0) ||
- (mStartNode != null && mStartNode.hasTasks()) ||
- (mEndNode != null && mEndNode.hasTasks());
- }
-
- /** Returns whether this is a leaf node */
- boolean isLeafNode() {
- return (mStartNode == null) && (mEndNode == null);
- }
-
- /** Returns all the descendent task stacks */
- private void getStacksRec(ArrayList<TaskStack> stacks) {
- if (isLeafNode()) {
- stacks.add(mStack);
- } else {
- mStartNode.getStacksRec(stacks);
- mEndNode.getStacksRec(stacks);
- }
- }
- public ArrayList<TaskStack> getStacks() {
- ArrayList<TaskStack> stacks = new ArrayList<TaskStack>();
- getStacksRec(stacks);
- return stacks;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index 55dfe45..0cd55d7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -36,6 +36,9 @@ public class Task {
public void onTaskDataLoaded();
/* Notifies when a task has been unbound */
public void onTaskDataUnloaded();
+
+ /* Notifies when a task's stack id has changed. */
+ public void onMultiStackDebugTaskStackIdChanged();
}
/** The ComponentNameKey represents the unique primary key for a component
@@ -68,14 +71,17 @@ public class Task {
public static class TaskKey {
final ComponentNameKey mComponentNameKey;
public final int id;
+ public int stackId;
public final Intent baseIntent;
public final int userId;
public long firstActiveTime;
public long lastActiveTime;
- public TaskKey(int id, Intent intent, int userId, long firstActiveTime, long lastActiveTime) {
+ public TaskKey(int id, int stackId, Intent intent, int userId, long firstActiveTime,
+ long lastActiveTime) {
mComponentNameKey = new ComponentNameKey(intent.getComponent(), userId);
this.id = id;
+ this.stackId = stackId;
this.baseIntent = intent;
this.userId = userId;
this.firstActiveTime = firstActiveTime;
@@ -92,18 +98,19 @@ public class Task {
if (!(o instanceof TaskKey)) {
return false;
}
- return id == ((TaskKey) o).id
- && userId == ((TaskKey) o).userId;
+ TaskKey otherKey = (TaskKey) o;
+ return id == otherKey.id && stackId == otherKey.stackId && userId == otherKey.userId;
}
@Override
public int hashCode() {
- return (id << 5) + userId;
+ return Objects.hash(id, stackId, userId);
}
@Override
public String toString() {
return "Task.Key: " + id + ", "
+ + "s: " + stackId + ", "
+ "u: " + userId + ", "
+ "lat: " + lastActiveTime + ", "
+ baseIntent.getComponent().getPackageName();
@@ -180,6 +187,14 @@ public class Task {
this.group = group;
}
+ /** Updates the stack id of this task. */
+ public void setStackId(int stackId) {
+ key.stackId = stackId;
+ if (mCb != null) {
+ mCb.onMultiStackDebugTaskStackIdChanged();
+ }
+ }
+
/** Notifies the callback listeners that this task has been loaded */
public void notifyTaskDataLoaded(Bitmap thumbnail, Drawable applicationIcon) {
this.applicationIcon = applicationIcon;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index 255d642..5aaea15 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -17,6 +17,7 @@
package com.android.systemui.recents.model;
import android.graphics.Color;
+import android.graphics.Rect;
import com.android.systemui.recents.Constants;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.misc.NamedCounter;
@@ -165,39 +166,46 @@ public class TaskStack {
public void onStackTaskAdded(TaskStack stack, Task t);
/* Notifies when a task has been removed from the stack */
public void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask);
+ /* Notifies when all task has been removed from the stack */
+ public void onStackAllTasksRemoved(TaskStack stack, ArrayList<Task> removedTasks);
/** Notifies when the stack was filtered */
public void onStackFiltered(TaskStack newStack, ArrayList<Task> curTasks, Task t);
/** Notifies when the stack was un-filtered */
public void onStackUnfiltered(TaskStack newStack, ArrayList<Task> curTasks);
}
- /** A pair of indices representing the group and task positions in the stack and group. */
- public static class GroupTaskIndex {
- public int groupIndex; // Index in the stack
- public int taskIndex; // Index in the group
-
- public GroupTaskIndex() {}
-
- public GroupTaskIndex(int gi, int ti) {
- groupIndex = gi;
- taskIndex = ti;
- }
- }
-
// The task offset to apply to a task id as a group affiliation
static final int IndividualTaskIdOffset = 1 << 16;
+ public final int id;
+ public final Rect stackBounds = new Rect();
+ public final Rect displayBounds = new Rect();
+
FilteredTaskList mTaskList = new FilteredTaskList();
TaskStackCallbacks mCb;
ArrayList<TaskGrouping> mGroups = new ArrayList<TaskGrouping>();
HashMap<Integer, TaskGrouping> mAffinitiesGroups = new HashMap<Integer, TaskGrouping>();
- /** Sets the callbacks for this task stack */
+ public TaskStack() {
+ this(0);
+ }
+
+ public TaskStack(int stackId) {
+ id = stackId;
+ }
+
+ /** Sets the callbacks for this task stack. */
public void setCallbacks(TaskStackCallbacks cb) {
mCb = cb;
}
+ /** Sets the bounds of this stack. */
+ public void setBounds(Rect stackBounds, Rect displayBounds) {
+ this.stackBounds.set(stackBounds);
+ this.displayBounds.set(displayBounds);
+ }
+
/** Resets this TaskStack. */
public void reset() {
mCb = null;
@@ -214,19 +222,24 @@ public class TaskStack {
}
}
+ /** Does the actual work associated with removing the task. */
+ void removeTaskImpl(Task t) {
+ // Remove the task from the list
+ mTaskList.remove(t);
+ // Remove it from the group as well, and if it is empty, remove the group
+ TaskGrouping group = t.group;
+ group.removeTask(t);
+ if (group.getTaskCount() == 0) {
+ removeGroup(group);
+ }
+ // Update the lock-to-app state
+ t.lockToThisTask = false;
+ }
+
/** Removes a task */
public void removeTask(Task t) {
if (mTaskList.contains(t)) {
- // Remove the task from the list
- mTaskList.remove(t);
- // Remove it from the group as well, and if it is empty, remove the group
- TaskGrouping group = t.group;
- group.removeTask(t);
- if (group.getTaskCount() == 0) {
- removeGroup(group);
- }
- // Update the lock-to-app state
- t.lockToThisTask = false;
+ removeTaskImpl(t);
Task newFrontMostTask = getFrontMostTask();
if (newFrontMostTask != null && newFrontMostTask.lockToTaskEnabled) {
newFrontMostTask.lockToThisTask = true;
@@ -238,20 +251,27 @@ public class TaskStack {
}
}
+ /** Removes all tasks */
+ public void removeAllTasks() {
+ ArrayList<Task> taskList = new ArrayList<Task>(mTaskList.getTasks());
+ int taskCount = taskList.size();
+ for (int i = taskCount - 1; i >= 0; i--) {
+ Task t = taskList.get(i);
+ removeTaskImpl(t);
+ }
+ if (mCb != null) {
+ // Notify that all tasks have been removed
+ mCb.onStackAllTasksRemoved(this, taskList);
+ }
+ }
+
/** Sets a few tasks in one go */
public void setTasks(List<Task> tasks) {
ArrayList<Task> taskList = mTaskList.getTasks();
int taskCount = taskList.size();
- for (int i = 0; i < taskCount; i++) {
+ for (int i = taskCount - 1; i >= 0; i--) {
Task t = taskList.get(i);
- // Remove the task from the list
- mTaskList.remove(t);
- // Remove it from the group as well, and if it is empty, remove the group
- TaskGrouping group = t.group;
- group.removeTask(t);
- if (group.getTaskCount() == 0) {
- removeGroup(group);
- }
+ removeTaskImpl(t);
if (mCb != null) {
// Notify that a task has been removed
mCb.onStackTaskRemoved(this, t, null);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FakeShadowDrawable.java b/packages/SystemUI/src/com/android/systemui/recents/views/FakeShadowDrawable.java
index 72f9001..509ad1b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/FakeShadowDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/FakeShadowDrawable.java
@@ -159,9 +159,9 @@ class FakeShadowDrawable extends Drawable {
}
@Override
- public void setColorFilter(ColorFilter cf) {
- mCornerShadowPaint.setColorFilter(cf);
- mEdgeShadowPaint.setColorFilter(cf);
+ public void setColorFilter(ColorFilter colorFilter) {
+ mCornerShadowPaint.setColorFilter(colorFilter);
+ mEdgeShadowPaint.setColorFilter(colorFilter);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index ee79242..1377975 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -29,10 +29,12 @@ import android.provider.Settings;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewStub;
import android.view.WindowInsets;
import android.widget.FrameLayout;
-
+import com.android.systemui.R;
import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.RecentsAppWidgetHostView;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.model.RecentsPackageMonitor;
@@ -41,6 +43,7 @@ import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
import java.util.ArrayList;
+import java.util.List;
/**
* This view is the the top level layout that contains TaskStacks (which are laid out according
@@ -56,14 +59,18 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
public void onAllTaskViewsDismissed();
public void onExitToHomeAnimationTriggered();
public void onScreenPinningRequest();
+
+ public void onTaskResize(Task t);
}
RecentsConfiguration mConfig;
LayoutInflater mInflater;
DebugOverlayView mDebugOverlay;
+ RecentsViewLayoutAlgorithm mLayoutAlgorithm;
ArrayList<TaskStack> mStacks;
- View mSearchBar;
+ List<TaskStackView> mTaskStackViews = new ArrayList<>();
+ RecentsAppWidgetHostView mSearchBar;
RecentsViewCallbacks mCb;
public RecentsView(Context context) {
@@ -82,6 +89,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
super(context, attrs, defStyleAttr, defStyleRes);
mConfig = RecentsConfiguration.getInstance();
mInflater = LayoutInflater.from(context);
+ mLayoutAlgorithm = new RecentsViewLayoutAlgorithm(mConfig);
}
/** Sets the callbacks */
@@ -98,29 +106,18 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
public void setTaskStacks(ArrayList<TaskStack> stacks) {
int numStacks = stacks.size();
- // Make a list of the stack view children only
- ArrayList<TaskStackView> stackViews = new ArrayList<TaskStackView>();
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child != mSearchBar) {
- stackViews.add((TaskStackView) child);
- }
- }
-
// Remove all/extra stack views
int numTaskStacksToKeep = 0; // Keep no tasks if we are recreating the layout
if (mConfig.launchedReuseTaskStackViews) {
- numTaskStacksToKeep = Math.min(childCount, numStacks);
+ numTaskStacksToKeep = Math.min(mTaskStackViews.size(), numStacks);
}
- for (int i = stackViews.size() - 1; i >= numTaskStacksToKeep; i--) {
- removeView(stackViews.get(i));
- stackViews.remove(i);
+ for (int i = mTaskStackViews.size() - 1; i >= numTaskStacksToKeep; i--) {
+ removeView(mTaskStackViews.remove(i));
}
// Update the stack views that we are keeping
for (int i = 0; i < numTaskStacksToKeep; i++) {
- TaskStackView tsv = stackViews.get(i);
+ TaskStackView tsv = mTaskStackViews.get(i);
// If onRecentsHidden is not triggered, we need to the stack view again here
tsv.reset();
tsv.setStack(stacks.get(i));
@@ -128,21 +125,19 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
// Add remaining/recreate stack views
mStacks = stacks;
- for (int i = stackViews.size(); i < numStacks; i++) {
+ for (int i = mTaskStackViews.size(); i < numStacks; i++) {
TaskStack stack = stacks.get(i);
TaskStackView stackView = new TaskStackView(getContext(), stack);
stackView.setCallbacks(this);
addView(stackView);
+ mTaskStackViews.add(stackView);
}
// Enable debug mode drawing on all the stacks if necessary
if (mConfig.debugModeEnabled) {
- for (int i = childCount - 1; i >= 0; i--) {
- View v = getChildAt(i);
- if (v != mSearchBar) {
- TaskStackView stackView = (TaskStackView) v;
- stackView.setDebugOverlay(mDebugOverlay);
- }
+ for (int i = mTaskStackViews.size() - 1; i >= 0; i--) {
+ TaskStackView stackView = mTaskStackViews.get(i);
+ stackView.setDebugOverlay(mDebugOverlay);
}
}
@@ -150,24 +145,75 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
requestLayout();
}
+ /** Gets the list of task views */
+ List<TaskStackView> getTaskStackViews() {
+ return mTaskStackViews;
+ }
+
+ /** Gets the next task in the stack - or if the last - the top task */
+ public Task getNextTaskOrTopTask(Task taskToSearch) {
+ Task returnTask = null;
+ boolean found = false;
+ List<TaskStackView> stackViews = getTaskStackViews();
+ int stackCount = stackViews.size();
+ for (int i = stackCount - 1; i >= 0; --i) {
+ TaskStack stack = stackViews.get(i).getStack();
+ ArrayList<Task> taskList = stack.getTasks();
+ // Iterate the stack views and try and find the focused task
+ for (int j = taskList.size() - 1; j >= 0; --j) {
+ Task task = taskList.get(j);
+ // Return the next task in the line.
+ if (found)
+ return task;
+ // Remember the first possible task as the top task.
+ if (returnTask == null)
+ returnTask = task;
+ if (task == taskToSearch)
+ found = true;
+ }
+ }
+ return returnTask;
+ }
+
/** Launches the focused task from the first stack if possible */
public boolean launchFocusedTask() {
// Get the first stack view
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child != mSearchBar) {
- TaskStackView stackView = (TaskStackView) child;
- TaskStack stack = stackView.mStack;
- // Iterate the stack views and try and find the focused task
- int taskCount = stackView.getChildCount();
- for (int j = 0; j < taskCount; j++) {
- TaskView tv = (TaskView) stackView.getChildAt(j);
- Task task = tv.getTask();
- if (tv.isFocusedTask()) {
- onTaskViewClicked(stackView, tv, stack, task, false);
- return true;
- }
+ List<TaskStackView> stackViews = getTaskStackViews();
+ int stackCount = stackViews.size();
+ for (int i = 0; i < stackCount; i++) {
+ TaskStackView stackView = stackViews.get(i);
+ TaskStack stack = stackView.getStack();
+ // Iterate the stack views and try and find the focused task
+ List<TaskView> taskViews = stackView.getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int j = 0; j < taskViewCount; j++) {
+ TaskView tv = taskViews.get(j);
+ Task task = tv.getTask();
+ if (tv.isFocusedTask()) {
+ onTaskViewClicked(stackView, tv, stack, task, false);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /** Launches a given task. */
+ public boolean launchTask(Task task) {
+ // Get the first stack view
+ List<TaskStackView> stackViews = getTaskStackViews();
+ int stackCount = stackViews.size();
+ for (int i = 0; i < stackCount; i++) {
+ TaskStackView stackView = stackViews.get(i);
+ TaskStack stack = stackView.getStack();
+ // Iterate the stack views and try and find the given task.
+ List<TaskView> taskViews = stackView.getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int j = 0; j < taskViewCount; j++) {
+ TaskView tv = taskViews.get(j);
+ if (tv.getTask() == task) {
+ onTaskViewClicked(stackView, tv, stack, task, false);
+ return true;
}
}
}
@@ -177,24 +223,22 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
/** Launches the task that Recents was launched from, if possible */
public boolean launchPreviousTask() {
// Get the first stack view
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child != mSearchBar) {
- TaskStackView stackView = (TaskStackView) child;
- TaskStack stack = stackView.mStack;
- ArrayList<Task> tasks = stack.getTasks();
-
- // Find the launch task in the stack
- if (!tasks.isEmpty()) {
- int taskCount = tasks.size();
- for (int j = 0; j < taskCount; j++) {
- if (tasks.get(j).isLaunchTarget) {
- Task task = tasks.get(j);
- TaskView tv = stackView.getChildViewForTask(task);
- onTaskViewClicked(stackView, tv, stack, task, false);
- return true;
- }
+ List<TaskStackView> stackViews = getTaskStackViews();
+ int stackCount = stackViews.size();
+ for (int i = 0; i < stackCount; i++) {
+ TaskStackView stackView = stackViews.get(i);
+ TaskStack stack = stackView.getStack();
+ ArrayList<Task> tasks = stack.getTasks();
+
+ // Find the launch task in the stack
+ if (!tasks.isEmpty()) {
+ int taskCount = tasks.size();
+ for (int j = 0; j < taskCount; j++) {
+ if (tasks.get(j).isLaunchTarget) {
+ Task task = tasks.get(j);
+ TaskView tv = stackView.getChildViewForTask(task);
+ onTaskViewClicked(stackView, tv, stack, task, false);
+ return true;
}
}
}
@@ -208,13 +252,11 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
// to ensure that it runs
ctx.postAnimationTrigger.increment();
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child != mSearchBar) {
- TaskStackView stackView = (TaskStackView) child;
- stackView.startEnterRecentsAnimation(ctx);
- }
+ List<TaskStackView> stackViews = getTaskStackViews();
+ int stackCount = stackViews.size();
+ for (int i = 0; i < stackCount; i++) {
+ TaskStackView stackView = stackViews.get(i);
+ stackView.startEnterRecentsAnimation(ctx);
}
ctx.postAnimationTrigger.decrement();
}
@@ -224,13 +266,11 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
// We have to increment/decrement the post animation trigger in case there are no children
// to ensure that it runs
ctx.postAnimationTrigger.increment();
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child != mSearchBar) {
- TaskStackView stackView = (TaskStackView) child;
- stackView.startExitToHomeAnimation(ctx);
- }
+ List<TaskStackView> stackViews = getTaskStackViews();
+ int stackCount = stackViews.size();
+ for (int i = 0; i < stackCount; i++) {
+ TaskStackView stackView = stackViews.get(i);
+ stackView.startExitToHomeAnimation(ctx);
}
ctx.postAnimationTrigger.decrement();
@@ -239,7 +279,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
}
/** Adds the search bar */
- public void setSearchBar(View searchBar) {
+ public void setSearchBar(RecentsAppWidgetHostView searchBar) {
// Create the search bar (and hide it if we have no recent tasks)
if (Constants.DebugFlags.App.EnableSearchLayout) {
// Remove the previous search bar if one exists
@@ -255,8 +295,8 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
}
/** Returns whether there is currently a search bar */
- public boolean hasSearchBar() {
- return mSearchBar != null;
+ public boolean hasValidSearchBar() {
+ return mSearchBar != null && !mSearchBar.isReinflateRequired();
}
/** Sets the visibility of the search bar */
@@ -286,19 +326,23 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
}
Rect taskStackBounds = new Rect();
- mConfig.getTaskStackBounds(width, height, mConfig.systemInsets.top,
+ mConfig.getAvailableTaskStackBounds(width, height, mConfig.systemInsets.top,
mConfig.systemInsets.right, taskStackBounds);
- // Measure each TaskStackView with the full width and height of the window since the
+ // Measure each TaskStackView with the full width and height of the window since the
// transition view is a child of that stack view
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child != mSearchBar && child.getVisibility() != GONE) {
- TaskStackView tsv = (TaskStackView) child;
- // Set the insets to be the top/left inset + search bounds
- tsv.setStackInsetRect(taskStackBounds);
- tsv.measure(widthMeasureSpec, heightMeasureSpec);
+ List<TaskStackView> stackViews = getTaskStackViews();
+ List<Rect> stackViewsBounds = mLayoutAlgorithm.computeStackRects(stackViews,
+ taskStackBounds);
+ int stackCount = stackViews.size();
+ for (int i = 0; i < stackCount; i++) {
+ TaskStackView stackView = stackViews.get(i);
+ if (stackView.getVisibility() != GONE) {
+ // We are going to measure the TaskStackView with the whole RecentsView dimensions,
+ // but the actual stack is going to be inset to the bounds calculated by the layout
+ // algorithm
+ stackView.setStackInsetRect(stackViewsBounds.get(i));
+ stackView.measure(widthMeasureSpec, heightMeasureSpec);
}
}
@@ -321,12 +365,13 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
// Layout each TaskStackView with the full width and height of the window since the
// transition view is a child of that stack view
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child != mSearchBar && child.getVisibility() != GONE) {
- child.layout(left, top, left + child.getMeasuredWidth(),
- top + child.getMeasuredHeight());
+ List<TaskStackView> stackViews = getTaskStackViews();
+ int stackCount = stackViews.size();
+ for (int i = 0; i < stackCount; i++) {
+ TaskStackView stackView = stackViews.get(i);
+ if (stackView.getVisibility() != GONE) {
+ stackView.layout(left, top, left + stackView.getMeasuredWidth(),
+ top + stackView.getMeasuredHeight());
}
}
}
@@ -342,41 +387,29 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
/** Notifies each task view of the user interaction. */
public void onUserInteraction() {
// Get the first stack view
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child != mSearchBar) {
- TaskStackView stackView = (TaskStackView) child;
- stackView.onUserInteraction();
- }
+ List<TaskStackView> stackViews = getTaskStackViews();
+ int stackCount = stackViews.size();
+ for (int i = 0; i < stackCount; i++) {
+ TaskStackView stackView = stackViews.get(i);
+ stackView.onUserInteraction();
}
}
/** Focuses the next task in the first stack view */
public void focusNextTask(boolean forward) {
// Get the first stack view
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child != mSearchBar) {
- TaskStackView stackView = (TaskStackView) child;
- stackView.focusNextTask(forward, true);
- break;
- }
+ List<TaskStackView> stackViews = getTaskStackViews();
+ if (!stackViews.isEmpty()) {
+ stackViews.get(0).focusNextTask(forward, true);
}
}
/** Dismisses the focused task. */
public void dismissFocusedTask() {
// Get the first stack view
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child != mSearchBar) {
- TaskStackView stackView = (TaskStackView) child;
- stackView.dismissFocusedTask();
- break;
- }
+ List<TaskStackView> stackViews = getTaskStackViews();
+ if (!stackViews.isEmpty()) {
+ stackViews.get(0).dismissFocusedTask();
}
}
@@ -475,9 +508,16 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
}
};
}
- opts = ActivityOptions.makeThumbnailAspectScaleUpAnimation(sourceView,
- b, offsetX, offsetY, transform.rect.width(), transform.rect.height(),
- sourceView.getHandler(), animStartedListener);
+ if (mConfig.multiStackEnabled) {
+ opts = ActivityOptions.makeCustomAnimation(sourceView.getContext(),
+ R.anim.recents_from_unknown_enter,
+ R.anim.recents_from_unknown_exit,
+ sourceView.getHandler(), animStartedListener);
+ } else {
+ opts = ActivityOptions.makeThumbnailAspectScaleUpAnimation(sourceView,
+ b, offsetX, offsetY, transform.rect.width(), transform.rect.height(),
+ sourceView.getHandler(), animStartedListener);
+ }
}
final ActivityOptions launchOpts = opts;
@@ -542,24 +582,29 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
loader.deleteTaskData(t, false);
// Remove the old task from activity manager
- RecentsTaskLoader.getInstance().getSystemServicesProxy().removeTask(t.key.id);
+ loader.getSystemServicesProxy().removeTask(t.key.id);
}
@Override
- public void onAllTaskViewsDismissed() {
+ public void onAllTaskViewsDismissed(ArrayList<Task> removedTasks) {
+ if (removedTasks != null) {
+ int taskCount = removedTasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ onTaskViewDismissed(removedTasks.get(i));
+ }
+ }
+
mCb.onAllTaskViewsDismissed();
}
/** Final callback after Recents is finally hidden. */
public void onRecentsHidden() {
// Notify each task stack view
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child != mSearchBar) {
- TaskStackView stackView = (TaskStackView) child;
- stackView.onRecentsHidden();
- }
+ List<TaskStackView> stackViews = getTaskStackViews();
+ int stackCount = stackViews.size();
+ for (int i = 0; i < stackCount; i++) {
+ TaskStackView stackView = stackViews.get(i);
+ stackView.onRecentsHidden();
}
}
@@ -591,18 +636,23 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
}
}
+ @Override
+ public void onTaskResize(Task t) {
+ if (mCb != null) {
+ mCb.onTaskResize(t);
+ }
+ }
+
/**** RecentsPackageMonitor.PackageCallbacks Implementation ****/
@Override
public void onPackagesChanged(RecentsPackageMonitor monitor, String packageName, int userId) {
// Propagate this event down to each task stack view
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child != mSearchBar) {
- TaskStackView stackView = (TaskStackView) child;
- stackView.onPackagesChanged(monitor, packageName, userId);
- }
+ List<TaskStackView> stackViews = getTaskStackViews();
+ int stackCount = stackViews.size();
+ for (int i = 0; i < stackCount; i++) {
+ TaskStackView stackView = stackViews.get(i);
+ stackView.onPackagesChanged(monitor, packageName, userId);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewLayoutAlgorithm.java
new file mode 100644
index 0000000..eea273c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewLayoutAlgorithm.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.views;
+
+import android.graphics.Rect;
+import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.model.TaskStack;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/* The layout logic for the RecentsView. */
+public class RecentsViewLayoutAlgorithm {
+
+ RecentsConfiguration mConfig;
+
+ public RecentsViewLayoutAlgorithm(RecentsConfiguration config) {
+ mConfig = config;
+ }
+
+ /** Return the relative coordinate given coordinates in another size. */
+ private int getRelativeCoordinate(int availableOffset, int availableSize, int otherCoord, int otherSize) {
+ float relPos = (float) otherCoord / otherSize;
+ return availableOffset + (int) (relPos * availableSize);
+ }
+
+ /**
+ * Computes and returns the bounds that each of the stack views should take up.
+ */
+ List<Rect> computeStackRects(List<TaskStackView> stackViews, Rect availableBounds) {
+ ArrayList<Rect> bounds = new ArrayList<Rect>(stackViews.size());
+ int stackViewsCount = stackViews.size();
+ for (int i = 0; i < stackViewsCount; i++) {
+ TaskStack stack = stackViews.get(i).getStack();
+ Rect sb = stack.stackBounds;
+ Rect db = stack.displayBounds;
+ Rect ab = availableBounds;
+ bounds.add(new Rect(getRelativeCoordinate(ab.left, ab.width(), sb.left, db.width()),
+ getRelativeCoordinate(ab.top, ab.height(), sb.top, db.height()),
+ getRelativeCoordinate(ab.left, ab.width(), sb.right, db.width()),
+ getRelativeCoordinate(ab.top, ab.height(), sb.bottom, db.height())));
+ }
+ return bounds;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 169683f..3a97a41 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -36,11 +36,14 @@ import com.android.systemui.recents.model.RecentsPackageMonitor;
import com.android.systemui.recents.model.RecentsTaskLoader;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.statusbar.DismissView;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.List;
/* The visual representation of a task stack view */
@@ -54,9 +57,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
boolean lockToTask);
public void onTaskViewAppInfoClicked(Task t);
public void onTaskViewDismissed(Task t);
- public void onAllTaskViewsDismissed();
+ public void onAllTaskViewsDismissed(ArrayList<Task> removedTasks);
public void onTaskStackFilterTriggered();
public void onTaskStackUnfilterTriggered();
+
+ public void onTaskResize(Task t);
}
RecentsConfiguration mConfig;
@@ -72,6 +77,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
DozeTrigger mUIDozeTrigger;
DebugOverlayView mDebugOverlay;
Rect mTaskStackBounds = new Rect();
+ DismissView mDismissAllButton;
+ boolean mDismissAllButtonAnimating;
int mFocusedTaskIndex = -1;
int mPrevAccessibilityFocusedIndex = -1;
@@ -89,6 +96,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
Rect mTmpRect = new Rect();
TaskViewTransform mTmpTransform = new TaskViewTransform();
HashMap<Task, TaskView> mTmpTaskViewMap = new HashMap<Task, TaskView>();
+ ArrayList<TaskView> mTaskViews = new ArrayList<TaskView>();
+ List<TaskView> mImmutableTaskViews = new ArrayList<TaskView>();
LayoutInflater mInflater;
// A convenience update listener to request updating clipping of tasks
@@ -116,9 +125,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
@Override
public void run() {
// Show the task bar dismiss buttons
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- TaskView tv = (TaskView) getChildAt(i);
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int i = 0; i < taskViewCount; i++) {
+ TaskView tv = taskViews.get(i);
tv.startNoUserInteractionAnimation();
}
}
@@ -141,21 +151,44 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
requestLayout();
}
+ /** Returns the task stack. */
+ TaskStack getStack() {
+ return mStack;
+ }
+
/** Sets the debug overlay */
public void setDebugOverlay(DebugOverlayView overlay) {
mDebugOverlay = overlay;
}
+ /** Updates the list of task views */
+ void updateTaskViewsList() {
+ mTaskViews.clear();
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View v = getChildAt(i);
+ if (v instanceof TaskView) {
+ mTaskViews.add((TaskView) v);
+ }
+ }
+ mImmutableTaskViews = Collections.unmodifiableList(mTaskViews);
+ }
+
+ /** Gets the list of task views */
+ List<TaskView> getTaskViews() {
+ return mImmutableTaskViews;
+ }
+
/** Resets this TaskStackView for reuse. */
void reset() {
// Reset the focused task
resetFocusedTask();
// Return all the views to the pool
- int childCount = getChildCount();
- for (int i = childCount - 1; i >= 0; i--) {
- TaskView tv = (TaskView) getChildAt(i);
- mViewPool.returnViewToPool(tv);
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int i = taskViewCount - 1; i >= 0; i--) {
+ mViewPool.returnViewToPool(taskViews.get(i));
}
// Mark each task view for relayout
@@ -209,9 +242,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
/** Finds the child view given a specific task. */
public TaskView getChildViewForTask(Task t) {
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- TaskView tv = (TaskView) getChildAt(i);
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int i = 0; i < taskViewCount; i++) {
+ TaskView tv = taskViews.get(i);
if (tv.getTask() == t) {
return tv;
}
@@ -299,17 +333,36 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
mDebugOverlay.setText("vis[" + visibleRange[1] + "-" + visibleRange[0] + "]");
}
+ // Inflate and add the dismiss button if necessary
+ if (Constants.DebugFlags.App.EnableDismissAll && mDismissAllButton == null) {
+ mDismissAllButton = (DismissView)
+ mInflater.inflate(R.layout.recents_dismiss_button, this, false);
+ mDismissAllButton.setOnButtonClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mStack.removeAllTasks();
+ }
+ });
+ addView(mDismissAllButton, 0);
+ }
+
// Return all the invisible children to the pool
mTmpTaskViewMap.clear();
- int childCount = getChildCount();
- for (int i = childCount - 1; i >= 0; i--) {
- TaskView tv = (TaskView) getChildAt(i);
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int i = taskViewCount - 1; i >= 0; i--) {
+ TaskView tv = taskViews.get(i);
Task task = tv.getTask();
int taskIndex = mStack.indexOfTask(task);
if (visibleRange[1] <= taskIndex && taskIndex <= visibleRange[0]) {
mTmpTaskViewMap.put(task, tv);
} else {
mViewPool.returnViewToPool(tv);
+
+ // Hide the dismiss button if the front most task is invisible
+ if (task == mStack.getFrontMostTask()) {
+ hideDismissAllButton(null);
+ }
}
}
@@ -333,6 +386,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
tv.updateViewPropertiesToTaskTransform(mTmpTransform, 0);
}
+
+ // If we show the front most task view then ensure that the dismiss button
+ // is visible too.
+ if (!mAwaitingFirstLayout && (task == mStack.getFrontMostTask())) {
+ showDismissAllButton();
+ }
}
// Animate the task into place
@@ -341,9 +400,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Request accessibility focus on the next view if we removed the task
// that previously held accessibility focus
- childCount = getChildCount();
- if (childCount > 0 && ssp.isTouchExplorationEnabled()) {
- TaskView atv = (TaskView) getChildAt(childCount - 1);
+ taskViews = getTaskViews();
+ taskViewCount = taskViews.size();
+ if (taskViewCount > 0 && ssp.isTouchExplorationEnabled()) {
+ TaskView atv = taskViews.get(taskViewCount - 1);
int indexOfTask = mStack.indexOfTask(atv.getTask());
if (mPrevAccessibilityFocusedIndex != indexOfTask) {
tv.requestAccessibilityFocus();
@@ -364,44 +424,43 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
/** Updates the clip for each of the task views. */
void clipTaskViews() {
// Update the clip on each task child
- if (Constants.DebugFlags.App.EnableTaskStackClipping) {
- int childCount = getChildCount();
- for (int i = 0; i < childCount - 1; i++) {
- TaskView tv = (TaskView) getChildAt(i);
- TaskView nextTv = null;
- TaskView tmpTv = null;
- int clipBottom = 0;
- if (tv.shouldClipViewInStack()) {
- // Find the next view to clip against
- int nextIndex = i;
- while (nextIndex < getChildCount()) {
- tmpTv = (TaskView) getChildAt(++nextIndex);
- if (tmpTv != null && tmpTv.shouldClipViewInStack()) {
- nextTv = tmpTv;
- break;
- }
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int i = 0; i < taskViewCount - 1; i++) {
+ TaskView tv = taskViews.get(i);
+ TaskView nextTv = null;
+ TaskView tmpTv = null;
+ int clipBottom = 0;
+ if (tv.shouldClipViewInStack()) {
+ // Find the next view to clip against
+ int nextIndex = i;
+ while (nextIndex < (taskViewCount - 1)) {
+ tmpTv = taskViews.get(++nextIndex);
+ if (tmpTv != null && tmpTv.shouldClipViewInStack()) {
+ nextTv = tmpTv;
+ break;
}
+ }
- // Clip against the next view, this is just an approximation since we are
- // stacked and we can make assumptions about the visibility of the this
- // task relative to the ones in front of it.
- if (nextTv != null) {
- // Map the top edge of next task view into the local space of the current
- // task view to find the clip amount in local space
- mTmpCoord[0] = mTmpCoord[1] = 0;
- Utilities.mapCoordInDescendentToSelf(nextTv, this, mTmpCoord, false);
- Utilities.mapCoordInSelfToDescendent(tv, this, mTmpCoord, mTmpMatrix);
- clipBottom = (int) Math.floor(tv.getMeasuredHeight() - mTmpCoord[1]
- - nextTv.getPaddingTop() - 1);
- }
+ // Clip against the next view, this is just an approximation since we are
+ // stacked and we can make assumptions about the visibility of the this
+ // task relative to the ones in front of it.
+ if (nextTv != null) {
+ // Map the top edge of next task view into the local space of the current
+ // task view to find the clip amount in local space
+ mTmpCoord[0] = mTmpCoord[1] = 0;
+ Utilities.mapCoordInDescendentToSelf(nextTv, this, mTmpCoord, false);
+ Utilities.mapCoordInSelfToDescendent(tv, this, mTmpCoord, mTmpMatrix);
+ clipBottom = (int) Math.floor(tv.getMeasuredHeight() - mTmpCoord[1]
+ - nextTv.getPaddingTop() - 1);
}
- tv.getViewBounds().setClipBottom(clipBottom);
- }
- if (getChildCount() > 0) {
- // The front most task should never be clipped
- TaskView tv = (TaskView) getChildAt(getChildCount() - 1);
- tv.getViewBounds().setClipBottom(0);
}
+ tv.getViewBounds().setClipBottom(clipBottom);
+ }
+ if (taskViewCount > 0) {
+ // The front most task should never be clipped
+ TaskView tv = taskViews.get(taskViewCount - 1);
+ tv.getViewBounds().setClipBottom(0);
}
mStackViewsClipDirty = false;
}
@@ -479,9 +538,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// 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);
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int i = taskViewCount - 1; i >= 0; i--) {
+ TaskView tv = taskViews.get(i);
tv.getHitRect(mTmpRect);
if (mTmpRect.contains(x, y)) {
mFocusedTaskIndex = mStack.indexOfTask(tv.getTask());
@@ -489,8 +549,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
}
// If we can't find the center task, then use the front most index
- if (mFocusedTaskIndex < 0 && childCount > 0) {
- mFocusedTaskIndex = childCount - 1;
+ if (mFocusedTaskIndex < 0 && taskViewCount > 0) {
+ mFocusedTaskIndex = taskViewCount - 1;
}
}
return mFocusedTaskIndex >= 0;
@@ -543,10 +603,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
@Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
- int childCount = getChildCount();
- if (childCount > 0) {
- TaskView backMostTask = (TaskView) getChildAt(0);
- TaskView frontMostTask = (TaskView) getChildAt(childCount - 1);
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ if (taskViewCount > 0) {
+ TaskView backMostTask = taskViews.get(0);
+ TaskView frontMostTask = taskViews.get(taskViewCount - 1);
event.setFromIndex(mStack.indexOfTask(backMostTask.getTask()));
event.setToIndex(mStack.indexOfTask(frontMostTask.getTask()));
event.setContentDescription(frontMostTask.getTask().activityLabel);
@@ -571,12 +632,18 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
return mTouchHandler.onGenericMotionEvent(ev);
}
+ /** Returns the region that touch gestures can be started in. */
+ Rect getTouchableRegion() {
+ return mTaskStackBounds;
+ }
+
@Override
public void computeScroll() {
mStackScroller.computeScroll();
// Synchronize the views
synchronizeStackViewsWithModel();
clipTaskViews();
+ updateDismissButtonPosition();
// Notify accessibility
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED);
}
@@ -633,9 +700,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
// Measure each of the TaskViews
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- TaskView tv = (TaskView) getChildAt(i);
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int i = 0; i < taskViewCount; i++) {
+ TaskView tv = taskViews.get(i);
if (tv.getBackground() != null) {
tv.getBackground().getPadding(mTmpRect);
} else {
@@ -650,6 +718,14 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
MeasureSpec.EXACTLY));
}
+ // Measure the dismiss button
+ if (mDismissAllButton != null) {
+ int taskRectWidth = mLayoutAlgorithm.mTaskRect.width();
+ mDismissAllButton.measure(
+ MeasureSpec.makeMeasureSpec(taskRectWidth, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(mConfig.dismissAllButtonSizePx, MeasureSpec.EXACTLY));
+ }
+
setMeasuredDimension(width, height);
}
@@ -661,9 +737,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
// Layout each of the children
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- TaskView tv = (TaskView) getChildAt(i);
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int i = 0; i < taskViewCount; i++) {
+ TaskView tv = taskViews.get(i);
if (tv.getBackground() != null) {
tv.getBackground().getPadding(mTmpRect);
} else {
@@ -675,6 +752,15 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
mLayoutAlgorithm.mTaskRect.bottom + mTmpRect.bottom);
}
+ // Layout the dismiss button at the top of the screen, and just translate it accordingly
+ // when synchronizing the views with the model to attach it to the bottom of the front-most
+ // task view
+ if (mDismissAllButton != null) {
+ mDismissAllButton.layout(mLayoutAlgorithm.mTaskRect.left, 0,
+ mLayoutAlgorithm.mTaskRect.left + mDismissAllButton.getMeasuredWidth(),
+ mDismissAllButton.getMeasuredHeight());
+ }
+
if (mAwaitingFirstLayout) {
mAwaitingFirstLayout = false;
onFirstLayout();
@@ -688,9 +774,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Find the launch target task
Task launchTargetTask = null;
- int childCount = getChildCount();
- for (int i = childCount - 1; i >= 0; i--) {
- TaskView tv = (TaskView) getChildAt(i);
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int i = taskViewCount - 1; i >= 0; i--) {
+ TaskView tv = taskViews.get(i);
Task task = tv.getTask();
if (task.isLaunchTarget) {
launchTargetTask = task;
@@ -699,8 +786,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
// Prepare the first view for its enter animation
- for (int i = childCount - 1; i >= 0; i--) {
- TaskView tv = (TaskView) getChildAt(i);
+ for (int i = taskViewCount - 1; i >= 0; i--) {
+ TaskView tv = taskViews.get(i);
Task task = tv.getTask();
boolean occludesLaunchTarget = (launchTargetTask != null) &&
launchTargetTask.group.isTaskAboveTask(task, launchTargetTask);
@@ -728,7 +815,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
// Start dozing
- mUIDozeTrigger.startDozing();
+ if (!mConfig.multiStackEnabled) {
+ mUIDozeTrigger.startDozing();
+ }
}
/** Requests this task stacks to start it's enter-recents animation */
@@ -743,9 +832,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
if (mStack.getTaskCount() > 0) {
// Find the launch target task
Task launchTargetTask = null;
- int childCount = getChildCount();
- for (int i = childCount - 1; i >= 0; i--) {
- TaskView tv = (TaskView) getChildAt(i);
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int i = taskViewCount - 1; i >= 0; i--) {
+ TaskView tv = taskViews.get(i);
Task task = tv.getTask();
if (task.isLaunchTarget) {
launchTargetTask = task;
@@ -754,12 +844,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
// Animate all the task views into view
- for (int i = childCount - 1; i >= 0; i--) {
- TaskView tv = (TaskView) getChildAt(i);
+ for (int i = taskViewCount - 1; i >= 0; i--) {
+ TaskView tv = taskViews.get(i);
Task task = tv.getTask();
ctx.currentTaskTransform = new TaskViewTransform();
ctx.currentStackViewIndex = i;
- ctx.currentStackViewCount = childCount;
+ ctx.currentStackViewCount = taskViewCount;
ctx.currentTaskRect = mLayoutAlgorithm.mTaskRect;
ctx.currentTaskOccludesLaunchTarget = (launchTargetTask != null) &&
launchTargetTask.group.isTaskAboveTask(task, launchTargetTask);
@@ -778,11 +868,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
SystemServicesProxy ssp = loader.getSystemServicesProxy();
- int childCount = getChildCount();
- if (childCount > 0) {
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ if (taskViewCount > 0) {
// Focus the first view if accessibility is enabled
if (ssp.isTouchExplorationEnabled()) {
- TaskView tv = ((TaskView) getChildAt(childCount - 1));
+ TaskView tv = taskViews.get(taskViewCount - 1);
tv.requestAccessibilityFocus();
mPrevAccessibilityFocusedIndex = mStack.indexOfTask(tv.getTask());
}
@@ -790,17 +881,20 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Start the focus animation when alt-tabbing
if (mConfig.launchedWithAltTab && !mConfig.launchedHasConfigurationChanged) {
- View tv = getChildAt(mFocusedTaskIndex);
+ TaskView tv = getChildViewForTask(mStack.getTasks().get(mFocusedTaskIndex));
if (tv != null) {
- ((TaskView) tv).setFocusedTask(true);
+ tv.setFocusedTask(true);
}
}
+
+ // Show the dismiss button
+ showDismissAllButton();
}
});
}
}
- /** Requests this task stacks to start it's exit-recents animation. */
+ /** Requests this task stack to start it's exit-recents animation. */
public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
// Stop any scrolling
mStackScroller.stopScroller();
@@ -808,19 +902,44 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Animate all the task views out of view
ctx.offscreenTranslationY = mLayoutAlgorithm.mViewRect.bottom -
(mLayoutAlgorithm.mTaskRect.top - mLayoutAlgorithm.mViewRect.top);
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- TaskView tv = (TaskView) getChildAt(i);
+ // Animate the dismiss-all button
+ hideDismissAllButton(null);
+
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int i = 0; i < taskViewCount; i++) {
+ TaskView tv = taskViews.get(i);
tv.startExitToHomeAnimation(ctx);
}
}
+ /** Requests this task stack to start it's dismiss-all animation. */
+ public void startDismissAllAnimation(final Runnable postAnimationRunnable) {
+ // Clear the focused task
+ resetFocusedTask();
+ // Animate the dismiss-all button
+ hideDismissAllButton(new Runnable() {
+ @Override
+ public void run() {
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ int count = 0;
+ for (int i = taskViewCount - 1; i >= 0; i--) {
+ TaskView tv = taskViews.get(i);
+ tv.startDeleteTaskAnimation(i > 0 ? null : postAnimationRunnable, count * 50);
+ count++;
+ }
+ }
+ });
+ }
+
/** Animates a task view in this stack as it launches. */
public void startLaunchTaskAnimation(TaskView tv, Runnable r, boolean lockToTask) {
Task launchTargetTask = tv.getTask();
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- TaskView t = (TaskView) getChildAt(i);
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int i = 0; i < taskViewCount; i++) {
+ TaskView t = taskViews.get(i);
if (t == tv) {
t.setClipViewInStack(false);
t.startLaunchTaskAnimation(r, true, true, lockToTask);
@@ -832,6 +951,69 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
}
+ /** Shows the dismiss button */
+ void showDismissAllButton() {
+ if (mDismissAllButton == null) return;
+
+ if (mDismissAllButtonAnimating || mDismissAllButton.getVisibility() != View.VISIBLE ||
+ Float.compare(mDismissAllButton.getAlpha(), 0f) == 0) {
+ mDismissAllButtonAnimating = true;
+ mDismissAllButton.setVisibility(View.VISIBLE);
+ mDismissAllButton.showClearButton();
+ mDismissAllButton.findViewById(R.id.dismiss_text).setAlpha(1f);
+ mDismissAllButton.setAlpha(0f);
+ mDismissAllButton.animate()
+ .alpha(1f)
+ .setDuration(250)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ mDismissAllButtonAnimating = false;
+ }
+ })
+ .start();
+ }
+ }
+
+ /** Hides the dismiss button */
+ void hideDismissAllButton(final Runnable postAnimRunnable) {
+ if (mDismissAllButton == null) return;
+
+ mDismissAllButtonAnimating = true;
+ mDismissAllButton.animate()
+ .alpha(0f)
+ .setDuration(200)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ mDismissAllButtonAnimating = false;
+ mDismissAllButton.setVisibility(View.GONE);
+ if (postAnimRunnable != null) {
+ postAnimRunnable.run();
+ }
+ }
+ })
+ .start();
+ }
+
+ /** Updates the dismiss button position */
+ void updateDismissButtonPosition() {
+ if (mDismissAllButton == null) return;
+
+ // Update the position of the clear-all button to hang it off the first task view
+ if (mStack.getTaskCount() > 0) {
+ mTmpCoord[0] = mTmpCoord[1] = 0;
+ TaskView tv = getChildViewForTask(mStack.getFrontMostTask());
+ TaskViewTransform transform = mCurrentTaskTransforms.get(mStack.getTaskCount() - 1);
+ if (tv != null && transform.visible) {
+ Utilities.mapCoordInDescendentToSelf(tv, this, mTmpCoord, false);
+ mDismissAllButton.setTranslationY(mTmpCoord[1] + (tv.getScaleY() * tv.getHeight()));
+ mDismissAllButton.setTranslationX(-(mLayoutAlgorithm.mStackRect.width() -
+ transform.rect.width()) / 2f);
+ }
+ }
+ }
+
/** Final callback after Recents is finally hidden. */
void onRecentsHidden() {
reset();
@@ -908,12 +1090,30 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
shouldFinishActivity = (mStack.getTaskCount() == 0);
}
if (shouldFinishActivity) {
- mCb.onAllTaskViewsDismissed();
+ mCb.onAllTaskViewsDismissed(null);
}
+ } else {
+ // Fade the dismiss button back in
+ showDismissAllButton();
}
}
@Override
+ public void onStackAllTasksRemoved(TaskStack stack, final ArrayList<Task> removedTasks) {
+ // Announce for accessibility
+ String msg = getContext().getString(R.string.accessibility_recents_all_items_dismissed);
+ announceForAccessibility(msg);
+
+ startDismissAllAnimation(new Runnable() {
+ @Override
+ public void run() {
+ // Notify that all tasks have been removed
+ mCb.onAllTaskViewsDismissed(removedTasks);
+ }
+ });
+ }
+
+ @Override
public void onStackFiltered(TaskStack newStack, final ArrayList<Task> curTasks,
Task filteredTask) {
/*
@@ -998,6 +1198,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Detach the view from the hierarchy
detachViewFromParent(tv);
+ // Update the task views list after removing the task view
+ updateTaskViewsList();
// Reset the view properties
tv.resetViewProperties();
@@ -1019,7 +1221,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
RecentsTaskLoader.getInstance().loadTaskData(task);
// If the doze trigger has already fired, then update the state for this task view
- if (mUIDozeTrigger.hasTriggered()) {
+ if (mConfig.multiStackEnabled || mUIDozeTrigger.hasTriggered()) {
tv.setNoUserInteractionState();
}
@@ -1032,11 +1234,14 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
int insertIndex = -1;
int taskIndex = mStack.indexOfTask(task);
if (taskIndex != -1) {
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- Task tvTask = ((TaskView) getChildAt(i)).getTask();
+
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int i = 0; i < taskViewCount; i++) {
+ Task tvTask = taskViews.get(i).getTask();
if (taskIndex < mStack.indexOfTask(tvTask)) {
- insertIndex = i;
+ // Offset by 1 if we have a dismiss-all button
+ insertIndex = i + (Constants.DebugFlags.App.EnableDismissAll ? 1 : 0);
break;
}
}
@@ -1051,6 +1256,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
tv.requestLayout();
}
}
+ // Update the task views list after adding the new task view
+ updateTaskViewsList();
// Set the new state for this view, including the callbacks and view clipping
tv.setCallbacks(this);
@@ -1133,6 +1340,13 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
}
+ @Override
+ public void onTaskResize(TaskView tv) {
+ if (mCb != null) {
+ mCb.onTaskResize(tv.getTask());
+ }
+ }
+
/**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/
@Override
@@ -1163,7 +1377,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
public void run() {
mStack.removeTask(t);
}
- });
+ }, 0);
} else {
// Otherwise, remove the task from the stack immediately
mStack.removeTask(t);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewFilterAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewFilterAlgorithm.java
index 9cd5ae4..614ca53 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewFilterAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewFilterAlgorithm.java
@@ -22,6 +22,7 @@ import com.android.systemui.recents.model.Task;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
/* The layout logic for a TaskStackView */
public class TaskStackViewFilterAlgorithm {
@@ -142,9 +143,10 @@ public class TaskStackViewFilterAlgorithm {
// the new stack) or to their final positions in the new stack
int offset = 0;
int movement = 0;
- int childCount = mStackView.getChildCount();
- for (int i = 0; i < childCount; i++) {
- TaskView tv = (TaskView) mStackView.getChildAt(i);
+ List<TaskView> taskViews = mStackView.getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int i = 0; i < taskViewCount; i++) {
+ TaskView tv = taskViews.get(i);
Task task = tv.getTask();
int taskIndex = tasks.indexOf(task);
TaskViewTransform toTransform;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
index 49b9129..f6df881 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
@@ -17,6 +17,7 @@
package com.android.systemui.recents.views;
import android.graphics.Rect;
+import com.android.systemui.recents.Constants;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.Task;
@@ -131,6 +132,11 @@ public class TaskStackViewLayoutAlgorithm {
float pNavBarOffset = pAtBottomOfStackRect -
screenYToCurveProgress(mStackVisibleRect.bottom - (mStackVisibleRect.bottom -
mStackRect.bottom));
+ float pDismissAllButtonOffset = 0f;
+ if (Constants.DebugFlags.App.EnableDismissAll) {
+ pDismissAllButtonOffset = pAtBottomOfStackRect -
+ screenYToCurveProgress(mStackVisibleRect.bottom - mConfig.dismissAllButtonSizePx);
+ }
// Update the task offsets
float pAtBackMostCardTop = 0.5f;
@@ -148,7 +154,8 @@ public class TaskStackViewLayoutAlgorithm {
}
}
- mMaxScrollP = pAtFrontMostCardTop - ((1f - pTaskHeightOffset - pNavBarOffset));
+ mMaxScrollP = pAtFrontMostCardTop + pDismissAllButtonOffset -
+ ((1f - pTaskHeightOffset - pNavBarOffset));
mMinScrollP = tasks.size() == 1 ? Math.max(mMaxScrollP, 0f) : 0f;
if (launchedWithAltTab && launchedFromHome) {
// Center the top most task, since that will be focused first
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
index f7067be..fabc86d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -96,23 +96,13 @@ public class TaskStackViewScroller {
}
return false;
}
- /** Bounds the current scroll if necessary, but does not synchronize the stack view with the model. */
- public boolean boundScrollRaw() {
- float curScroll = getStackScroll();
- float newScroll = getBoundedStackScroll(curScroll);
- if (Float.compare(newScroll, curScroll) != 0) {
- setStackScrollRaw(newScroll);
- return true;
- }
- return false;
- }
/** Returns the bounded stack scroll */
float getBoundedStackScroll(float scroll) {
return Math.max(mLayoutAlgorithm.mMinScrollP, Math.min(mLayoutAlgorithm.mMaxScrollP, scroll));
}
- /** Returns the amount that the aboslute value of how much the scroll is out of bounds. */
+ /** Returns the amount that the absolute value of how much the scroll is out of bounds. */
float getScrollAmountOutOfBounds(float scroll) {
if (scroll < mLayoutAlgorithm.mMinScrollP) {
return Math.abs(scroll - mLayoutAlgorithm.mMinScrollP);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index 59e38f4..509560eb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -24,8 +24,11 @@ import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewParent;
import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsConfiguration;
+import java.util.List;
+
/* Handles touch events for a TaskStackView. */
class TaskStackViewTouchHandler implements SwipeHelper.Callback {
static int INACTIVE_POINTER_ID = -1;
@@ -51,6 +54,8 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
int mScrollTouchSlop;
// The page touch slop is used to calculate when we start swiping
float mPagingTouchSlop;
+ // Used to calculate when a tap is outside a task view rectangle.
+ final int mWindowTouchSlop;
SwipeHelper mSwipeHelper;
boolean mInterceptedBySwipeHelper;
@@ -62,6 +67,7 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
mScrollTouchSlop = configuration.getScaledTouchSlop();
mPagingTouchSlop = configuration.getScaledPagingTouchSlop();
+ mWindowTouchSlop = configuration.getScaledWindowTouchSlop();
mSv = sv;
mScroller = scroller;
mConfig = config;
@@ -93,9 +99,10 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
/** Returns the view at the specified coordinates */
TaskView findViewAtPoint(int x, int y) {
- int childCount = mSv.getChildCount();
- for (int i = childCount - 1; i >= 0; i--) {
- TaskView tv = (TaskView) mSv.getChildAt(i);
+ List<TaskView> taskViews = mSv.getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int i = taskViewCount - 1; i >= 0; i--) {
+ TaskView tv = taskViews.get(i);
if (tv.getVisibility() == View.VISIBLE) {
if (mSv.isTransformedTouchPointInView(x, y, tv)) {
return tv;
@@ -115,11 +122,21 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
/** Touch preprocessing for handling below */
public boolean onInterceptTouchEvent(MotionEvent ev) {
// Return early if we have no children
- boolean hasChildren = (mSv.getChildCount() > 0);
- if (!hasChildren) {
+ boolean hasTaskViews = (mSv.getTaskViews().size() > 0);
+ if (!hasTaskViews) {
return false;
}
+ int action = ev.getAction();
+ if (mConfig.multiStackEnabled) {
+ // Check if we are within the bounds of the stack view contents
+ if ((action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
+ if (!mSv.getTouchableRegion().contains((int) ev.getX(), (int) ev.getY())) {
+ return false;
+ }
+ }
+ }
+
// Pass through to swipe helper if we are swiping
mInterceptedBySwipeHelper = mSwipeHelper.onInterceptTouchEvent(ev);
if (mInterceptedBySwipeHelper) {
@@ -128,7 +145,6 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
boolean wasScrolling = mScroller.isScrolling() ||
(mScroller.mScrollAnimator != null && mScroller.mScrollAnimator.isRunning());
- int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
// Save the touch down info
@@ -190,11 +206,21 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
/** Handles touch events once we have intercepted them */
public boolean onTouchEvent(MotionEvent ev) {
// Short circuit if we have no children
- boolean hasChildren = (mSv.getChildCount() > 0);
- if (!hasChildren) {
+ boolean hasTaskViews = (mSv.getTaskViews().size() > 0);
+ if (!hasTaskViews) {
return false;
}
+ int action = ev.getAction();
+ if (mConfig.multiStackEnabled) {
+ // Check if we are within the bounds of the stack view contents
+ if ((action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
+ if (!mSv.getTouchableRegion().contains((int) ev.getX(), (int) ev.getY())) {
+ return false;
+ }
+ }
+ }
+
// Pass through to swipe helper if we are swiping
if (mInterceptedBySwipeHelper && mSwipeHelper.onTouchEvent(ev)) {
return true;
@@ -203,7 +229,6 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
// Update the velocity tracker
initVelocityTrackerIfNotExists();
- int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
// Save the touch down info
@@ -279,7 +304,7 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
float overscrollRangePct = Math.abs((float) velocity / mMaximumVelocity);
int overscrollRange = (int) (Math.min(1f, overscrollRangePct) *
(Constants.Values.TaskStackView.TaskStackMaxOverscrollRange -
- Constants.Values.TaskStackView.TaskStackMinOverscrollRange));
+ Constants.Values.TaskStackView.TaskStackMinOverscrollRange));
mScroller.mScroller.fling(0,
mScroller.progressToScrollRange(mScroller.getStackScroll()),
0, velocity,
@@ -293,6 +318,9 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
} else if (mScroller.isScrollOutOfBounds()) {
// Animate the scroll back into bounds
mScroller.animateBoundScroll();
+ } else if (mActiveTaskView == null) {
+ // This tap didn't start on a task.
+ maybeHideRecentsFromBackgroundTap((int) ev.getX(), (int) ev.getY());
}
mActivePointerId = INACTIVE_POINTER_ID;
@@ -330,6 +358,34 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
return true;
}
+ /** Hides recents if the up event at (x, y) is a tap on the background area. */
+ void maybeHideRecentsFromBackgroundTap(int x, int y) {
+ // Ignore the up event if it's too far from its start position. The user might have been
+ // trying to scroll or swipe.
+ int dx = Math.abs(mInitialMotionX - x);
+ int dy = Math.abs(mInitialMotionY - y);
+ if (dx > mScrollTouchSlop || dy > mScrollTouchSlop) {
+ return;
+ }
+
+ // Shift the tap position toward the center of the task stack and check to see if it would
+ // have hit a view. The user might have tried to tap on a task and missed slightly.
+ int shiftedX = x;
+ if (x > mSv.getTouchableRegion().centerX()) {
+ shiftedX -= mWindowTouchSlop;
+ } else {
+ shiftedX += mWindowTouchSlop;
+ }
+ if (findViewAtPoint(shiftedX, y) != null) {
+ return;
+ }
+
+ // The user intentionally tapped on the background, which is like a tap on the "desktop".
+ // Hide recents and transition to the launcher.
+ Recents recents = Recents.getInstanceAndStartIfNeeded(mSv.getContext());
+ recents.hideRecents(false /* altTab */, true /* homeKey */);
+ }
+
/** Handles generic motion events */
public boolean onGenericMotionEvent(MotionEvent ev) {
if ((ev.getSource() & InputDevice.SOURCE_CLASS_POINTER) ==
@@ -378,6 +434,8 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
+ // Fade out the dismiss button
+ mSv.hideDismissAllButton(null);
}
@Override
@@ -403,6 +461,8 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
tv.setClipViewInStack(true);
// Re-enable touch events from this task view
tv.setTouchEnabled(true);
+ // Restore the dismiss button
+ mSv.showDismissAllButton();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index faa728d..682775b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -45,6 +45,8 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
public void onTaskViewDismissed(TaskView tv);
public void onTaskViewClipStateChanged(TaskView tv);
public void onTaskViewFocusChanged(TaskView tv, boolean focused);
+
+ public void onTaskResize(TaskView tv);
}
RecentsConfiguration mConfig;
@@ -381,6 +383,11 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
ctx.postAnimationTrigger.increment();
}
+ /** Animates this task view away when dismissing all tasks. */
+ void startDismissAllAnimation() {
+ dismissTask();
+ }
+
/** Animates this task view as it exits recents */
void startLaunchTaskAnimation(final Runnable postAnimRunnable, boolean isLaunchingTask,
boolean occludesLaunchTarget, boolean lockToTask) {
@@ -428,25 +435,22 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
}
/** Animates the deletion of this task view */
- void startDeleteTaskAnimation(final Runnable r) {
+ void startDeleteTaskAnimation(final Runnable r, int delay) {
// Disabling clipping with the stack while the view is animating away
setClipViewInStack(false);
animate().translationX(mConfig.taskViewRemoveAnimTranslationXPx)
.alpha(0f)
- .setStartDelay(0)
+ .setStartDelay(delay)
.setUpdateListener(null)
.setInterpolator(mConfig.fastOutSlowInInterpolator)
.setDuration(mConfig.taskViewRemoveAnimDuration)
.withEndAction(new Runnable() {
@Override
public void run() {
- // We just throw this into a runnable because starting a view property
- // animation using layers can cause inconsisten results if we try and
- // update the layers while the animation is running. In some cases,
- // the runnabled passed in may start an animation which also uses layers
- // so we defer all this by posting this.
- r.run();
+ if (r != null) {
+ r.run();
+ }
// Re-enable clipping with the stack (we will reuse this view)
setClipViewInStack(true);
@@ -455,6 +459,11 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
.start();
}
+ /** Enables/disables handling touch on this task view. */
+ void setTouchEnabled(boolean enabled) {
+ setOnClickListener(enabled ? this : null);
+ }
+
/** Animates this task view if the user does not interact with the stack after a certain time. */
void startNoUserInteractionAnimation() {
mHeaderView.startNoUserInteractionAnimation();
@@ -481,7 +490,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
mCb.onTaskViewDismissed(tv);
}
}
- });
+ }, 0);
}
/**
@@ -665,6 +674,9 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
// Rebind any listeners
mHeaderView.mApplicationIcon.setOnClickListener(this);
mHeaderView.mDismissButton.setOnClickListener(this);
+ if (mConfig.multiStackEnabled) {
+ mHeaderView.mMoveTaskButton.setOnClickListener(this);
+ }
mActionButtonView.setOnClickListener(this);
if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
if (mConfig.developerOptionsEnabled) {
@@ -685,6 +697,9 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
// Unbind any listeners
mHeaderView.mApplicationIcon.setOnClickListener(null);
mHeaderView.mDismissButton.setOnClickListener(null);
+ if (mConfig.multiStackEnabled) {
+ mHeaderView.mMoveTaskButton.setOnClickListener(null);
+ }
mActionButtonView.setOnClickListener(null);
if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
mHeaderView.mApplicationIcon.setOnLongClickListener(null);
@@ -693,9 +708,9 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
mTaskDataLoaded = false;
}
- /** Enables/disables handling touch on this task view. */
- void setTouchEnabled(boolean enabled) {
- setOnClickListener(enabled ? this : null);
+ @Override
+ public void onMultiStackDebugTaskStackIdChanged() {
+ mHeaderView.rebindToTask(mTask);
}
/**** View.OnClickListener Implementation ****/
@@ -715,6 +730,10 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
}
} else if (v == mHeaderView.mDismissButton) {
dismissTask();
+ } else if (v == mHeaderView.mMoveTaskButton) {
+ if (mCb != null) {
+ mCb.onTaskResize(tv);
+ }
}
}
}, 125);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index 05f6f40..60a91bf 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -24,20 +24,18 @@ import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.ColorStateList;
-import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.RippleDrawable;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.PorterDuffXfermode;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.RippleDrawable;
+import android.graphics.Rect;
import android.util.AttributeSet;
-import android.view.MotionEvent;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.widget.FrameLayout;
@@ -46,7 +44,9 @@ import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.recents.Constants;
import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
+import com.android.systemui.recents.model.RecentsTaskLoader;
import com.android.systemui.recents.model.Task;
@@ -54,8 +54,10 @@ import com.android.systemui.recents.model.Task;
public class TaskViewHeader extends FrameLayout {
RecentsConfiguration mConfig;
+ private SystemServicesProxy mSsp;
// Header views
+ ImageView mMoveTaskButton;
ImageView mDismissButton;
ImageView mApplicationIcon;
TextView mActivityDescription;
@@ -93,6 +95,7 @@ public class TaskViewHeader extends FrameLayout {
public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mConfig = RecentsConfiguration.getInstance();
+ mSsp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
setWillNotDraw(false);
setClipToOutline(true);
setOutlineProvider(new ViewOutlineProvider() {
@@ -103,11 +106,10 @@ public class TaskViewHeader extends FrameLayout {
});
// Load the dismiss resources
- Resources res = context.getResources();
- mLightDismissDrawable = res.getDrawable(R.drawable.recents_dismiss_light);
- mDarkDismissDrawable = res.getDrawable(R.drawable.recents_dismiss_dark);
+ mLightDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_light);
+ mDarkDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_dark);
mDismissContentDescription =
- res.getString(R.string.accessibility_recents_item_will_be_dismissed);
+ context.getString(R.string.accessibility_recents_item_will_be_dismissed);
// Configure the highlight paint
if (sHighlightPaint == null) {
@@ -121,19 +123,12 @@ public class TaskViewHeader extends FrameLayout {
}
@Override
- public boolean onTouchEvent(MotionEvent event) {
- // We ignore taps on the task bar except on the filter and dismiss buttons
- if (!Constants.DebugFlags.App.EnableTaskBarTouchEvents) return true;
-
- return super.onTouchEvent(event);
- }
-
- @Override
protected void onFinishInflate() {
// Initialize the icon and description views
mApplicationIcon = (ImageView) findViewById(R.id.application_icon);
mActivityDescription = (TextView) findViewById(R.id.activity_description);
mDismissButton = (ImageView) findViewById(R.id.dismiss_task);
+ mMoveTaskButton = (ImageView) findViewById(R.id.move_task);
// Hide the backgrounds if they are ripple drawables
if (!Constants.DebugFlags.App.EnableTaskFiltering) {
@@ -200,8 +195,7 @@ public class TaskViewHeader extends FrameLayout {
mActivityDescription.setText(t.activityLabel);
}
// Try and apply the system ui tint
- int existingBgColor = (getBackground() instanceof ColorDrawable) ?
- ((ColorDrawable) getBackground()).getColor() : 0;
+ int existingBgColor = getBackgroundColor();
if (existingBgColor != t.colorPrimary) {
mBackgroundColorDrawable.setColor(t.colorPrimary);
mBackgroundColor = t.colorPrimary;
@@ -214,6 +208,43 @@ public class TaskViewHeader extends FrameLayout {
mLightDismissDrawable : mDarkDismissDrawable);
mDismissButton.setContentDescription(String.format(mDismissContentDescription,
t.activityLabel));
+ mMoveTaskButton.setVisibility((mConfig.multiStackEnabled) ? View.VISIBLE : View.INVISIBLE);
+ if (mConfig.multiStackEnabled) {
+ updateResizeTaskBarIcon(t);
+ }
+ }
+
+ /** Updates the resize task bar button. */
+ void updateResizeTaskBarIcon(Task t) {
+ Rect display = mSsp.getWindowRect();
+ Rect taskRect = mSsp.getTaskBounds(t.key.stackId);
+ int resId = R.drawable.star;
+ if (display.equals(taskRect) || taskRect.isEmpty()) {
+ resId = R.drawable.vector_drawable_place_fullscreen;
+ } else {
+ boolean top = display.top == taskRect.top;
+ boolean bottom = display.bottom == taskRect.bottom;
+ boolean left = display.left == taskRect.left;
+ boolean right = display.right == taskRect.right;
+ if (top && bottom && left) {
+ resId = R.drawable.vector_drawable_place_left;
+ } else if (top && bottom && right) {
+ resId = R.drawable.vector_drawable_place_right;
+ } else if (top && left && right) {
+ resId = R.drawable.vector_drawable_place_top;
+ } else if (bottom && left && right) {
+ resId = R.drawable.vector_drawable_place_bottom;
+ } else if (top && right) {
+ resId = R.drawable.vector_drawable_place_top_right;
+ } else if (top && left) {
+ resId = R.drawable.vector_drawable_place_top_left;
+ } else if (bottom && right) {
+ resId = R.drawable.vector_drawable_place_bottom_right;
+ } else if (bottom && left) {
+ resId = R.drawable.vector_drawable_place_bottom_left;
+ }
+ }
+ mMoveTaskButton.setImageResource(resId);
}
/** Unbinds the bar view from the task */
@@ -284,23 +315,26 @@ public class TaskViewHeader extends FrameLayout {
}
if (focused) {
+ int currentColor = mBackgroundColor;
int secondaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
int[][] states = new int[][] {
+ new int[] {},
new int[] { android.R.attr.state_enabled },
new int[] { android.R.attr.state_pressed }
};
int[] newStates = new int[]{
+ 0,
android.R.attr.state_enabled,
android.R.attr.state_pressed
};
int[] colors = new int[] {
+ currentColor,
secondaryColor,
secondaryColor
};
mBackground.setColor(new ColorStateList(states, colors));
mBackground.setState(newStates);
// Pulse the background color
- int currentColor = mBackgroundColor;
int lightPrimaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(),
currentColor, lightPrimaryColor);
@@ -327,7 +361,7 @@ public class TaskViewHeader extends FrameLayout {
mFocusAnimator = new AnimatorSet();
mFocusAnimator.playTogether(backgroundColor, translation);
- mFocusAnimator.setStartDelay(750);
+ mFocusAnimator.setStartDelay(150);
mFocusAnimator.setDuration(750);
mFocusAnimator.start();
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
index 42c0f9f..a55e026 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
@@ -21,7 +21,6 @@ import android.graphics.Rect;
import android.view.View;
import android.view.ViewPropertyAnimator;
import android.view.animation.Interpolator;
-import com.android.systemui.recents.Constants;
/* The transform state for a task view */
@@ -133,6 +132,8 @@ public class TaskViewTransform {
/** Reset the transform on a view. */
public static void reset(View v) {
+ // Cancel any running animations
+ v.animate().cancel();
v.setTranslationX(0f);
v.setTranslationY(0f);
v.setTranslationZ(0f);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index d9fea47..105bf0f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -122,7 +122,7 @@ class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Voi
// Prepare all the output metadata
mImageTime = System.currentTimeMillis();
- String imageDate = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date(mImageTime));
+ String imageDate = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date(mImageTime));
mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, imageDate);
mScreenshotDir = new File(Environment.getExternalStoragePublicDirectory(
@@ -341,7 +341,6 @@ class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Voi
class GlobalScreenshot {
private static final String TAG = "GlobalScreenshot";
- private static final int SCREENSHOT_NOTIFICATION_ID = 789;
private static final int SCREENSHOT_FLASH_TO_PEAK_DURATION = 130;
private static final int SCREENSHOT_DROP_IN_DURATION = 430;
private static final int SCREENSHOT_DROP_OUT_DELAY = 500;
@@ -464,7 +463,7 @@ class GlobalScreenshot {
mSaveInBgTask.cancel(false);
}
mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data, mNotificationManager,
- SCREENSHOT_NOTIFICATION_ID).execute(data);
+ R.id.notification_screenshot).execute(data);
}
/**
@@ -725,12 +724,12 @@ class GlobalScreenshot {
.setVisibility(Notification.VISIBILITY_PUBLIC) // ok to show outside lockscreen
.setCategory(Notification.CATEGORY_ERROR)
.setAutoCancel(true)
- .setColor(context.getResources().getColor(
+ .setColor(context.getColor(
com.android.internal.R.color.system_notification_accent_color));
Notification n =
new Notification.BigTextStyle(b)
.bigText(r.getString(R.string.screenshot_failed_text))
.build();
- nManager.notify(SCREENSHOT_NOTIFICATION_ID, n);
+ nManager.notify(R.id.notification_screenshot, n);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java
index a1704ff..74267a5 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java
@@ -17,11 +17,7 @@
package com.android.systemui.settings;
import android.app.Activity;
-import android.app.Dialog;
-import android.content.Context;
-import android.content.res.Resources;
import android.os.Bundle;
-import android.os.Handler;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.Window;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index 465a141..c0b3a9b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -94,7 +94,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
= new PathInterpolator(0, 0, 0.5f, 1);
private final int mTintedRippleColor;
private final int mLowPriorityRippleColor;
- private final int mNormalRippleColor;
+ protected final int mNormalRippleColor;
private boolean mDimmed;
private boolean mDark;
@@ -115,7 +115,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
private OnActivatedListener mOnActivatedListener;
private final Interpolator mLinearOutSlowInInterpolator;
- private final Interpolator mFastOutSlowInInterpolator;
+ protected final Interpolator mFastOutSlowInInterpolator;
private final Interpolator mSlowOutFastInInterpolator;
private final Interpolator mSlowOutLinearInInterpolator;
private final Interpolator mLinearInterpolator;
@@ -154,15 +154,15 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
mAppearAnimationFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP);
mRoundedRectCornerRadius = getResources().getDimensionPixelSize(
R.dimen.notification_material_rounded_rect_radius);
- mLegacyColor = getResources().getColor(R.color.notification_legacy_background_color);
- mNormalColor = getResources().getColor(R.color.notification_material_background_color);
- mLowPriorityColor = getResources().getColor(
+ mLegacyColor = context.getColor(R.color.notification_legacy_background_color);
+ mNormalColor = context.getColor(R.color.notification_material_background_color);
+ mLowPriorityColor = context.getColor(
R.color.notification_material_background_low_priority_color);
- mTintedRippleColor = context.getResources().getColor(
+ mTintedRippleColor = context.getColor(
R.color.notification_ripple_tinted_color);
- mLowPriorityRippleColor = context.getResources().getColor(
+ mLowPriorityRippleColor = context.getColor(
R.color.notification_ripple_color_low_priority);
- mNormalRippleColor = context.getResources().getColor(
+ mNormalRippleColor = context.getColor(
R.color.notification_ripple_untinted_color);
}
@@ -388,7 +388,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
}
private void updateBackgroundTint() {
- int color = getBackgroundColor();
+ int color = getBgColor();
int rippleColor = getRippleColor();
if (color == mNormalColor) {
// We don't need to tint a normal notification
@@ -652,7 +652,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
}
private void updateAppearAnimationAlpha() {
- int backgroundColor = getBackgroundColor();
+ int backgroundColor = getBgColor();
if (backgroundColor != -1) {
float contentAlphaProgress = mAppearAnimationFraction;
contentAlphaProgress = contentAlphaProgress / (1.0f - ALPHA_ANIMATION_END);
@@ -666,7 +666,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
}
}
- private int getBackgroundColor() {
+ private int getBgColor() {
if (mBgTint != 0) {
return mBgTint;
} else if (mShowingLegacyBackground) {
@@ -678,7 +678,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
}
}
- private int getRippleColor() {
+ protected int getRippleColor() {
if (mBgTint != 0) {
return mTintedRippleColor;
} else if (mShowingLegacyBackground) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedButton.java
new file mode 100644
index 0000000..87c12c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedButton.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.Button;
+
+/**
+ * A Button which doesn't have overlapping drawing commands
+ */
+public class AlphaOptimizedButton extends Button {
+ public AlphaOptimizedButton(Context context) {
+ super(context);
+ }
+
+ public AlphaOptimizedButton(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public AlphaOptimizedButton(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public AlphaOptimizedButton(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java
index a835c0e..359272e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java
@@ -19,7 +19,6 @@ package com.android.systemui.statusbar;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.FrameLayout;
-import android.widget.LinearLayout;
/**
* A frame layout which does not have overlapping renderings commands and therefore does not need a
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedImageView.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedImageView.java
index 094161d..858c118 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedImageView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedImageView.java
@@ -18,7 +18,6 @@ package com.android.systemui.statusbar;
import android.content.Context;
import android.util.AttributeSet;
-import android.view.View;
import android.widget.ImageView;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 8a03a2b..f75dd73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -24,6 +24,7 @@ import android.app.ActivityManagerNative;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.app.RemoteInput;
import android.app.TaskStackBuilder;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
@@ -49,6 +50,7 @@ import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -69,16 +71,13 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
import android.view.ViewParent;
-import android.view.ViewStub;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.AnimationUtils;
import android.widget.DateTimeView;
import android.widget.ImageView;
-import android.widget.LinearLayout;
import android.widget.RemoteViews;
import android.widget.TextView;
@@ -89,14 +88,16 @@ import com.android.internal.util.NotificationColorUtil;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
-import com.android.systemui.SearchPanelView;
import com.android.systemui.SwipeHelper;
import com.android.systemui.SystemUI;
+import com.android.systemui.recents.Recents;
import com.android.systemui.statusbar.NotificationData.Entry;
import com.android.systemui.statusbar.phone.NavigationBarView;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.HeadsUpNotificationView;
import com.android.systemui.statusbar.policy.PreviewInflater;
+import com.android.systemui.statusbar.policy.RemoteInputView;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import java.util.ArrayList;
@@ -116,6 +117,11 @@ public abstract class BaseStatusBar extends SystemUI implements
// STOPSHIP disable once we resolve b/18102199
private static final boolean NOTIFICATION_CLICK_DEBUG = true;
+ public static final boolean ENABLE_REMOTE_INPUT =
+ Build.IS_DEBUGGABLE && SystemProperties.getBoolean("debug.enable_remote_input", false);
+ public static final boolean ENABLE_CHILD_NOTIFICATIONS = Build.IS_DEBUGGABLE
+ && SystemProperties.getBoolean("debug.child_notifs", false);
+
protected static final int MSG_SHOW_RECENT_APPS = 1019;
protected static final int MSG_HIDE_RECENT_APPS = 1020;
protected static final int MSG_TOGGLE_RECENTS_APPS = 1021;
@@ -123,11 +129,9 @@ public abstract class BaseStatusBar extends SystemUI implements
protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023;
protected static final int MSG_SHOW_NEXT_AFFILIATED_TASK = 1024;
protected static final int MSG_SHOW_PREV_AFFILIATED_TASK = 1025;
- protected static final int MSG_CLOSE_SEARCH_PANEL = 1027;
protected static final int MSG_SHOW_HEADS_UP = 1028;
protected static final int MSG_HIDE_HEADS_UP = 1029;
protected static final int MSG_ESCALATE_HEADS_UP = 1030;
- protected static final int MSG_DECAY_HEADS_UP = 1031;
protected static final boolean ENABLE_HEADS_UP = true;
// scores above this threshold should be displayed in heads up mode.
@@ -137,10 +141,6 @@ public abstract class BaseStatusBar extends SystemUI implements
// Should match the value in PhoneWindowManager
public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
- public static final int EXPANDED_LEAVE_ALONE = -10000;
- public static final int EXPANDED_FULL_OPEN = -10001;
-
- private static final int HIDDEN_NOTIFICATION_ID = 10000;
private static final String BANNER_ACTION_CANCEL =
"com.android.systemui.statusbar.banner_action_cancel";
private static final String BANNER_ACTION_SETUP =
@@ -154,13 +154,12 @@ public abstract class BaseStatusBar extends SystemUI implements
protected NotificationData mNotificationData;
protected NotificationStackScrollLayout mStackScroller;
+ protected NotificationGroupManager mGroupManager = new NotificationGroupManager();
+
// for heads up notifications
protected HeadsUpNotificationView mHeadsUpNotificationView;
protected int mHeadsUpNotificationDecay;
- // Search panel
- protected SearchPanelView mSearchPanelView;
-
protected int mCurrentUserId = 0;
final protected SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>();
@@ -387,7 +386,7 @@ public abstract class BaseStatusBar extends SystemUI implements
} else if (BANNER_ACTION_CANCEL.equals(action) || BANNER_ACTION_SETUP.equals(action)) {
NotificationManager noMan = (NotificationManager)
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
- noMan.cancel(HIDDEN_NOTIFICATION_ID);
+ noMan.cancel(R.id.notification_hidden);
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0);
@@ -414,7 +413,7 @@ public abstract class BaseStatusBar extends SystemUI implements
@Override
public void run() {
for (StatusBarNotification sbn : notifications) {
- addNotification(sbn, currentRanking);
+ addNotification(sbn, currentRanking, null /* oldEntry */);
}
}
});
@@ -424,61 +423,69 @@ public abstract class BaseStatusBar extends SystemUI implements
public void onNotificationPosted(final StatusBarNotification sbn,
final RankingMap rankingMap) {
if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn);
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- Notification n = sbn.getNotification();
- boolean isUpdate = mNotificationData.get(sbn.getKey()) != null
- || isHeadsUp(sbn.getKey());
-
- // Ignore children of notifications that have a summary, since we're not
- // going to show them anyway. This is true also when the summary is canceled,
- // because children are automatically canceled by NoMan in that case.
- if (n.isGroupChild() &&
- mNotificationData.isGroupWithSummary(sbn.getGroupKey())) {
- if (DEBUG) {
- Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
- }
+ if (sbn != null) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ processForRemoteInput(sbn.getNotification());
+ Notification n = sbn.getNotification();
+ boolean isUpdate = mNotificationData.get(sbn.getKey()) != null
+ || isHeadsUp(sbn.getKey());
+
+ // In case we don't allow child notifications, we ignore children of
+ // notifications that have a summary, since we're not going to show them
+ // anyway. This is true also when the summary is canceled,
+ // because children are automatically canceled by NoMan in that case.
+ if (!ENABLE_CHILD_NOTIFICATIONS
+ && mGroupManager.isChildInGroupWithSummary(sbn)) {
+ if (DEBUG) {
+ Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
+ }
- // Remove existing notification to avoid stale data.
+ // Remove existing notification to avoid stale data.
+ if (isUpdate) {
+ removeNotification(sbn.getKey(), rankingMap);
+ } else {
+ mNotificationData.updateRanking(rankingMap);
+ }
+ return;
+ }
if (isUpdate) {
- removeNotification(sbn.getKey(), rankingMap);
+ updateNotification(sbn, rankingMap);
} else {
- mNotificationData.updateRanking(rankingMap);
+ addNotification(sbn, rankingMap, null /* oldEntry */);
}
- return;
- }
- if (isUpdate) {
- updateNotification(sbn, rankingMap);
- } else {
- addNotification(sbn, rankingMap);
}
- }
- });
+ });
+ }
}
@Override
- public void onNotificationRemoved(final StatusBarNotification sbn,
+ public void onNotificationRemoved(StatusBarNotification sbn,
final RankingMap rankingMap) {
if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn);
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- removeNotification(sbn.getKey(), rankingMap);
- }
- });
+ if (sbn != null) {
+ final String key = sbn.getKey();
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ removeNotification(key, rankingMap);
+ }
+ });
+ }
}
@Override
public void onNotificationRankingUpdate(final RankingMap rankingMap) {
if (DEBUG) Log.d(TAG, "onRankingUpdate");
+ if (rankingMap != null) {
mHandler.post(new Runnable() {
@Override
public void run() {
updateNotificationRanking(rankingMap);
}
});
- }
+ } }
};
@@ -511,7 +518,6 @@ public abstract class BaseStatusBar extends SystemUI implements
ServiceManager.checkService(DreamService.DREAM_SERVICE));
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
- mSettingsObserver.onChange(false); // set up
mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), true,
mSettingsObserver);
@@ -532,7 +538,7 @@ public abstract class BaseStatusBar extends SystemUI implements
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
- mRecents = getComponent(RecentsComponent.class);
+ mRecents = getComponent(Recents.class);
mRecents.setCallback(this);
final Configuration currentConfig = mContext.getResources().getConfiguration();
@@ -561,6 +567,7 @@ public abstract class BaseStatusBar extends SystemUI implements
createAndAddWindows();
+ mSettingsObserver.onChange(false); // set up
disable(switches[0], false /* animate */);
setSystemUiVisibility(switches[1], 0xffffffff);
topAppWindowChanged(switches[2] != 0);
@@ -647,7 +654,7 @@ public abstract class BaseStatusBar extends SystemUI implements
.setContentText(mContext.getString(R.string.hidden_notifications_text))
.setPriority(Notification.PRIORITY_HIGH)
.setOngoing(true)
- .setColor(res.getColor(colorRes))
+ .setColor(mContext.getColor(colorRes))
.setContentIntent(setupIntent)
.addAction(R.drawable.ic_close,
mContext.getString(R.string.hidden_notifications_cancel),
@@ -658,7 +665,7 @@ public abstract class BaseStatusBar extends SystemUI implements
NotificationManager noMan =
(NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
- noMan.notify(HIDDEN_NOTIFICATION_ID, note.build());
+ noMan.notify(R.id.notification_hidden, note.build());
}
}
@@ -698,6 +705,11 @@ public abstract class BaseStatusBar extends SystemUI implements
return null;
}
+ @Override
+ public NotificationGroupManager getGroupManager() {
+ return mGroupManager;
+ }
+
/**
* Takes the necessary steps to prepare the status bar for starting an activity, then starts it.
* @param action A dismiss action that is called if it's safe to start the activity.
@@ -770,18 +782,14 @@ public abstract class BaseStatusBar extends SystemUI implements
final int color = sbn.getNotification().color;
if (isMediaNotification(entry)) {
entry.row.setTintColor(color == Notification.COLOR_DEFAULT
- ? mContext.getResources().getColor(
+ ? mContext.getColor(
R.color.notification_material_background_media_default_color)
: color);
}
}
if (entry.icon != null) {
- if (entry.targetSdk >= Build.VERSION_CODES.LOLLIPOP) {
- entry.icon.setColorFilter(mContext.getResources().getColor(android.R.color.white));
- } else {
- entry.icon.setColorFilter(null);
- }
+ entry.icon.setTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
}
}
@@ -834,16 +842,13 @@ public abstract class BaseStatusBar extends SystemUI implements
}, false /* afterKeyguardGone */);
}
- private void inflateGuts(ExpandableNotificationRow row) {
- ViewStub stub = (ViewStub) row.findViewById(R.id.notification_guts_stub);
- if (stub != null) {
- stub.inflate();
- }
+ private void bindGuts(ExpandableNotificationRow row) {
+ row.inflateGuts();
final StatusBarNotification sbn = row.getStatusBarNotification();
PackageManager pmUser = getPackageManagerForUser(
sbn.getUser().getIdentifier());
row.setTag(sbn.getPackageName());
- final View guts = row.findViewById(R.id.notification_guts);
+ final View guts = row.getGuts();
final String pkg = sbn.getPackageName();
String appname = pkg;
Drawable pkgicon = null;
@@ -921,11 +926,11 @@ public abstract class BaseStatusBar extends SystemUI implements
return false;
}
- inflateGuts((ExpandableNotificationRow) v);
+ ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+ bindGuts(row);
// Assume we are a status_bar_notification_row
- final NotificationGuts guts = (NotificationGuts) v.findViewById(
- R.id.notification_guts);
+ final NotificationGuts guts = row.getGuts();
if (guts == null) {
// This view has no guts. Examples are the more card or the dismiss all view
return false;
@@ -1031,50 +1036,6 @@ public abstract class BaseStatusBar extends SystemUI implements
mHandler.sendEmptyMessage(msg);
}
- @Override
- public void showSearchPanel() {
- if (mSearchPanelView != null && mSearchPanelView.isAssistantAvailable()) {
- mSearchPanelView.show(true, true);
- }
- }
-
- @Override
- public void hideSearchPanel() {
- int msg = MSG_CLOSE_SEARCH_PANEL;
- mHandler.removeMessages(msg);
- mHandler.sendEmptyMessage(msg);
- }
-
- protected abstract WindowManager.LayoutParams getSearchLayoutParams(
- LayoutParams layoutParams);
-
- protected void updateSearchPanel() {
- // Search Panel
- boolean visible = false;
- if (mSearchPanelView != null) {
- visible = mSearchPanelView.isShowing();
- mWindowManager.removeView(mSearchPanelView);
- }
-
- // Provide SearchPanel with a temporary parent to allow layout params to work.
- LinearLayout tmpRoot = new LinearLayout(mContext);
- mSearchPanelView = (SearchPanelView) LayoutInflater.from(mContext).inflate(
- R.layout.status_bar_search_panel, tmpRoot, false);
- mSearchPanelView.setOnTouchListener(
- new TouchOutsideListener(MSG_CLOSE_SEARCH_PANEL, mSearchPanelView));
- mSearchPanelView.setVisibility(View.GONE);
- boolean vertical = mNavigationBarView != null && mNavigationBarView.isVertical();
- mSearchPanelView.setHorizontal(vertical);
-
- WindowManager.LayoutParams lp = getSearchLayoutParams(mSearchPanelView.getLayoutParams());
-
- mWindowManager.addView(mSearchPanelView, lp);
- mSearchPanelView.setBar(this);
- if (visible) {
- mSearchPanelView.show(true, false);
- }
- }
-
protected H createHandler() {
return new H();
}
@@ -1161,7 +1122,7 @@ public abstract class BaseStatusBar extends SystemUI implements
// Do nothing
}
- public abstract void resetHeadsUpDecayTimer();
+ public abstract void scheduleHeadsUpDecay(long delay);
public abstract void scheduleHeadsUpOpen();
@@ -1190,14 +1151,15 @@ public abstract class BaseStatusBar extends SystemUI implements
}
if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
- final boolean allowed = 0 != Settings.Secure.getIntForUser(
+ final boolean allowedByUser = 0 != Settings.Secure.getIntForUser(
mContext.getContentResolver(),
Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle);
final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(null /* admin */,
userHandle);
final boolean allowedByDpm = (dpmFlags
& DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS) == 0;
- mUsersAllowingPrivateNotifications.append(userHandle, allowed && allowedByDpm);
+ final boolean allowed = allowedByUser && allowedByDpm;
+ mUsersAllowingPrivateNotifications.append(userHandle, allowed);
return allowed;
}
@@ -1250,47 +1212,19 @@ public abstract class BaseStatusBar extends SystemUI implements
case MSG_SHOW_PREV_AFFILIATED_TASK:
showRecentsPreviousAffiliatedTask();
break;
- case MSG_CLOSE_SEARCH_PANEL:
- if (DEBUG) Log.d(TAG, "closing search panel");
- if (mSearchPanelView != null && mSearchPanelView.isShowing()) {
- mSearchPanelView.show(false, true);
- }
- break;
}
}
}
- public class TouchOutsideListener implements View.OnTouchListener {
- private int mMsg;
- private StatusBarPanel mPanel;
-
- public TouchOutsideListener(int msg, StatusBarPanel panel) {
- mMsg = msg;
- mPanel = panel;
- }
-
- public boolean onTouch(View v, MotionEvent ev) {
- final int action = ev.getAction();
- if (action == MotionEvent.ACTION_OUTSIDE
- || (action == MotionEvent.ACTION_DOWN
- && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) {
- mHandler.removeMessages(mMsg);
- mHandler.sendEmptyMessage(mMsg);
- return true;
- }
- return false;
- }
- }
-
protected void workAroundBadLayerDrawableOpacity(View v) {
}
- private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) {
+ protected boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) {
return inflateViews(entry, parent, false);
}
protected boolean inflateViewsForHeadsUp(NotificationData.Entry entry, ViewGroup parent) {
- return inflateViews(entry, parent, true);
+ return inflateViews(entry, parent, true);
}
private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent, boolean isHeadsUp) {
@@ -1331,6 +1265,7 @@ public abstract class BaseStatusBar extends SystemUI implements
hasUserChangedExpansion = row.hasUserChangedExpansion();
userExpanded = row.isUserExpanded();
userLocked = row.isUserLocked();
+ entry.row.setHeadsUp(isHeadsUp);
entry.reset();
if (hasUserChangedExpansion) {
row.setUserExpanded(userExpanded);
@@ -1342,6 +1277,7 @@ public abstract class BaseStatusBar extends SystemUI implements
row = (ExpandableNotificationRow) inflater.inflate(R.layout.status_bar_notification_row,
parent, false);
row.setExpansionLogger(this, entry.notification.getKey());
+ row.setGroupManager(mGroupManager);
}
workAroundBadLayerDrawableOpacity(row);
@@ -1358,11 +1294,13 @@ public abstract class BaseStatusBar extends SystemUI implements
(NotificationContentView) row.findViewById(R.id.expandedPublic);
row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
+ if (ENABLE_REMOTE_INPUT) {
+ row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
+ }
PendingIntent contentIntent = sbn.getNotification().contentIntent;
if (contentIntent != null) {
- final View.OnClickListener listener = makeClicker(contentIntent, sbn.getKey(),
- isHeadsUp);
+ final View.OnClickListener listener = makeClicker(contentIntent, sbn.getKey());
row.setOnClickListener(listener);
} else {
row.setOnClickListener(null);
@@ -1520,23 +1458,114 @@ public abstract class BaseStatusBar extends SystemUI implements
}
row.setUserLocked(userLocked);
row.setStatusBarNotification(entry.notification);
+ applyRemoteInput(entry);
return true;
}
- public NotificationClicker makeClicker(PendingIntent intent, String notificationKey,
- boolean forHun) {
- return new NotificationClicker(intent, notificationKey, forHun);
+ /**
+ * Adds RemoteInput actions from the WearableExtender; to be removed once more apps support this
+ * via first-class API.
+ *
+ * TODO: Remove once enough apps specify remote inputs on their own.
+ */
+ private void processForRemoteInput(Notification n) {
+ if (!ENABLE_REMOTE_INPUT) return;
+
+ if (n.extras != null && n.extras.containsKey("android.wearable.EXTENSIONS") &&
+ (n.actions == null || n.actions.length == 0)) {
+ Notification.Action viableAction = null;
+ Notification.WearableExtender we = new Notification.WearableExtender(n);
+
+ List<Notification.Action> actions = we.getActions();
+ final int numActions = actions.size();
+
+ for (int i = 0; i < numActions; i++) {
+ Notification.Action action = actions.get(i);
+ RemoteInput[] remoteInputs = action.getRemoteInputs();
+ for (RemoteInput ri : action.getRemoteInputs()) {
+ if (ri.getAllowFreeFormInput()) {
+ viableAction = action;
+ break;
+ }
+ }
+ if (viableAction != null) {
+ break;
+ }
+ }
+
+ if (viableAction != null) {
+ Notification stripped = n.clone();
+ Notification.Builder.stripForDelivery(stripped);
+ stripped.actions = new Notification.Action[] { viableAction };
+ stripped.extras.putBoolean("android.rebuild.contentView", true);
+ stripped.contentView = null;
+ stripped.extras.putBoolean("android.rebuild.bigView", true);
+ stripped.bigContentView = null;
+
+ // Don't create the HUN input view for now because input doesn't work there yet.
+ // TODO: Enable once HUNs can take remote input correctly.
+ if (false) {
+ stripped.extras.putBoolean("android.rebuild.hudView", true);
+ stripped.headsUpContentView = null;
+ }
+
+ Notification rebuilt = Notification.Builder.rebuild(mContext, stripped);
+
+ n.actions = rebuilt.actions;
+ n.bigContentView = rebuilt.bigContentView;
+ n.headsUpContentView = rebuilt.headsUpContentView;
+ n.publicVersion = rebuilt.publicVersion;
+ }
+ }
+ }
+
+ private void applyRemoteInput(final Entry entry) {
+ if (!ENABLE_REMOTE_INPUT) return;
+
+ RemoteInput remoteInput = null;
+
+ // See if the notification has exactly one action and this action allows free-form input
+ // TODO: relax restrictions once we support more than one remote input action.
+ Notification.Action[] actions = entry.notification.getNotification().actions;
+ if (actions != null && actions.length == 1) {
+ if (actions[0].getRemoteInputs() != null) {
+ for (RemoteInput ri : actions[0].getRemoteInputs()) {
+ if (ri.getAllowFreeFormInput()) {
+ remoteInput = ri;
+ break;
+ }
+ }
+ }
+ }
+
+ // See if we have somewhere to put that remote input
+ ViewGroup actionContainer = null;
+ if (remoteInput != null && entry.expandedBig != null) {
+ View actionContainerCandidate = entry.expandedBig
+ .findViewById(com.android.internal.R.id.actions);
+ if (actionContainerCandidate instanceof ViewGroup) {
+ actionContainer = (ViewGroup) actionContainerCandidate;
+ }
+ }
+
+ if (actionContainer != null) {
+ actionContainer.removeAllViews();
+ actionContainer.addView(
+ RemoteInputView.inflate(mContext, actionContainer, actions[0], remoteInput));
+ }
+ }
+
+ public NotificationClicker makeClicker(PendingIntent intent, String notificationKey) {
+ return new NotificationClicker(intent, notificationKey);
}
protected class NotificationClicker implements View.OnClickListener {
private PendingIntent mIntent;
private final String mNotificationKey;
- private boolean mIsHeadsUp;
- public NotificationClicker(PendingIntent intent, String notificationKey, boolean forHun) {
+ public NotificationClicker(PendingIntent intent, String notificationKey) {
mIntent = intent;
mNotificationKey = notificationKey;
- mIsHeadsUp = forHun;
}
public void onClick(final View v) {
@@ -1549,12 +1578,12 @@ public abstract class BaseStatusBar extends SystemUI implements
mCurrentUserId);
dismissKeyguardThenExecute(new OnDismissAction() {
public boolean onDismiss() {
- if (mIsHeadsUp) {
+ if (mNotificationKey.equals(mHeadsUpNotificationView.getKey())) {
// 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();
+ mHeadsUpNotificationView.releaseImmediately();
}
new Thread() {
@Override
@@ -1693,6 +1722,21 @@ public abstract class BaseStatusBar extends SystemUI implements
if (DEBUG) {
Log.d(TAG, "createNotificationViews(notification=" + sbn);
}
+ final StatusBarIconView iconView = createIcon(sbn);
+ if (iconView == null) {
+ return null;
+ }
+
+ // Construct the expanded view.
+ NotificationData.Entry entry = new NotificationData.Entry(sbn, iconView);
+ if (!inflateViews(entry, mStackScroller)) {
+ handleNotificationError(sbn, "Couldn't expand RemoteViews for: " + sbn);
+ return null;
+ }
+ return entry;
+ }
+
+ protected StatusBarIconView createIcon(StatusBarNotification sbn) {
// Construct the icon.
Notification n = sbn.getNotification();
final StatusBarIconView iconView = new StatusBarIconView(mContext,
@@ -1709,13 +1753,7 @@ public abstract class BaseStatusBar extends SystemUI implements
handleNotificationError(sbn, "Couldn't create icon: " + ic);
return null;
}
- // Construct the expanded view.
- NotificationData.Entry entry = new NotificationData.Entry(sbn, iconView);
- if (!inflateViews(entry, mStackScroller)) {
- handleNotificationError(sbn, "Couldn't expand RemoteViews for: " + sbn);
- return null;
- }
- return entry;
+ return iconView;
}
protected void addNotificationViews(Entry entry, RankingMap ranking) {
@@ -1755,22 +1793,25 @@ public abstract class BaseStatusBar extends SystemUI implements
entry.row.setSystemExpanded(top);
}
}
+ boolean isInvisibleChild = !mGroupManager.isVisible(entry.notification);
boolean showOnKeyguard = shouldShowOnKeyguard(entry.notification);
if ((isLockscreenPublicMode() && !mShowLockscreenNotifications) ||
(onKeyguard && (visibleNotifications >= maxKeyguardNotifications
- || !showOnKeyguard))) {
+ || !showOnKeyguard || isInvisibleChild))) {
entry.row.setVisibility(View.GONE);
- if (onKeyguard && showOnKeyguard) {
+ if (onKeyguard && showOnKeyguard && !isInvisibleChild) {
mKeyguardIconOverflowContainer.getIconsView().addNotification(entry);
}
} else {
boolean wasGone = entry.row.getVisibility() == View.GONE;
entry.row.setVisibility(View.VISIBLE);
- if (wasGone) {
- // notify the scroller of a child addition
- mStackScroller.generateAddAnimation(entry.row, true /* fromMoreCard */);
+ if (!isInvisibleChild) {
+ if (wasGone) {
+ // notify the scroller of a child addition
+ mStackScroller.generateAddAnimation(entry.row, true /* fromMoreCard */);
+ }
+ visibleNotifications++;
}
- visibleNotifications++;
}
}
@@ -1813,15 +1854,12 @@ public abstract class BaseStatusBar extends SystemUI implements
setShowLockscreenNotifications(show && allowedByDpm);
}
- protected abstract void haltTicker();
protected abstract void setAreThereNotifications();
protected abstract void updateNotifications();
- protected abstract void tick(StatusBarNotification n, boolean firstTime);
- protected abstract void updateExpandedViewPos(int expandedPosition);
- protected abstract boolean shouldDisableNavbarGestures();
+ public abstract boolean shouldDisableNavbarGestures();
public abstract void addNotification(StatusBarNotification notification,
- RankingMap ranking);
+ RankingMap ranking, Entry oldEntry);
protected abstract void updateNotificationRanking(RankingMap ranking);
public abstract void removeNotification(String key, RankingMap ranking);
@@ -1902,17 +1940,15 @@ public abstract class BaseStatusBar extends SystemUI implements
&& oldPublicContentView.getPackage() != null
&& oldPublicContentView.getPackage().equals(publicContentView.getPackage())
&& oldPublicContentView.getLayoutId() == publicContentView.getLayoutId());
- boolean updateTicker = n.tickerText != null
- && !TextUtils.equals(n.tickerText,
- oldEntry.notification.getNotification().tickerText);
final boolean shouldInterrupt = shouldInterrupt(notification);
- final boolean alertAgain = alertAgain(oldEntry, n);
+ final boolean alertAgain = shouldInterrupt && alertAgain(oldEntry, n);
boolean updateSuccessful = false;
if (contentsUnchanged && bigContentsUnchanged && headsUpContentsUnchanged
&& publicUnchanged) {
if (DEBUG) Log.d(TAG, "reusing notification for key: " + key);
oldEntry.notification = notification;
+ mGroupManager.onEntryUpdated(oldEntry, oldNotification);
try {
if (oldEntry.icon != null) {
// Update the icon
@@ -1930,20 +1966,20 @@ public abstract class BaseStatusBar extends SystemUI implements
}
if (wasHeadsUp) {
- if (shouldInterrupt) {
- updateHeadsUpViews(oldEntry, notification);
- if (alertAgain) {
- resetHeadsUpDecayTimer();
- }
- } else {
+ // Release may hang on to the views for a bit, so we should always update them.
+ updateHeadsUpViews(oldEntry, notification);
+ mHeadsUpNotificationView.updateNotification(oldEntry, alertAgain);
+ if (!shouldInterrupt) {
// we updated the notification above, so release to build a new shade entry
- mHeadsUpNotificationView.releaseAndClose();
+ mHeadsUpNotificationView.release();
return;
}
} else {
if (shouldInterrupt && alertAgain) {
+ mStackScroller.setRemoveAnimationEnabled(false);
removeNotificationViews(key, ranking);
- addNotification(notification, ranking); //this will pop the headsup
+ mStackScroller.setRemoveAnimationEnabled(true);
+ addNotification(notification, ranking, oldEntry); //this will pop the headsup
} else {
updateNotificationViews(oldEntry, notification);
}
@@ -1960,33 +1996,32 @@ public abstract class BaseStatusBar extends SystemUI implements
if (!updateSuccessful) {
if (DEBUG) Log.d(TAG, "not reusing notification for key: " + key);
if (wasHeadsUp) {
- if (shouldInterrupt) {
- if (DEBUG) Log.d(TAG, "rebuilding heads up for key: " + key);
- Entry newEntry = new Entry(notification, null);
- ViewGroup holder = mHeadsUpNotificationView.getHolder();
- if (inflateViewsForHeadsUp(newEntry, holder)) {
- mHeadsUpNotificationView.showNotification(newEntry);
- if (alertAgain) {
- resetHeadsUpDecayTimer();
- }
- } else {
- Log.w(TAG, "Couldn't create new updated headsup for package "
- + contentView.getPackage());
- }
+ if (DEBUG) Log.d(TAG, "rebuilding heads up for key: " + key);
+ ViewGroup holder = mHeadsUpNotificationView.getHolder();
+ if (inflateViewsForHeadsUp(oldEntry, holder)) {
+ mHeadsUpNotificationView.updateNotification(oldEntry, alertAgain);
} else {
+ Log.w(TAG, "Couldn't create new updated headsup for package "
+ + contentView.getPackage());
+ }
+ if (!shouldInterrupt) {
if (DEBUG) Log.d(TAG, "releasing heads up for key: " + key);
oldEntry.notification = notification;
- mHeadsUpNotificationView.releaseAndClose();
+ mGroupManager.onEntryUpdated(oldEntry, oldNotification);
+ mHeadsUpNotificationView.release();
return;
}
} else {
if (shouldInterrupt && alertAgain) {
if (DEBUG) Log.d(TAG, "reposting to invoke heads up for key: " + key);
+ mStackScroller.setRemoveAnimationEnabled(false);
removeNotificationViews(key, ranking);
- addNotification(notification, ranking); //this will pop the headsup
+ mStackScroller.setRemoveAnimationEnabled(true);
+ addNotification(notification, ranking, oldEntry); //this will pop the headsup
} else {
if (DEBUG) Log.d(TAG, "rebuilding update in place for key: " + key);
oldEntry.notification = notification;
+ mGroupManager.onEntryUpdated(oldEntry, oldNotification);
final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(),
notification.getUser(),
n.icon,
@@ -2010,15 +2045,8 @@ public abstract class BaseStatusBar extends SystemUI implements
boolean isForCurrentUser = isNotificationForCurrentProfiles(notification);
if (DEBUG) Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
- // Restart the ticker if it's still running
- if (updateTicker && isForCurrentUser) {
- haltTicker();
- tick(notification, false);
- }
-
// Recalculate the position of the sliding windows and the titles.
setAreThereNotifications();
- updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
}
private void updateNotificationViews(NotificationData.Entry entry,
@@ -2053,8 +2081,7 @@ public abstract class BaseStatusBar extends SystemUI implements
// update the contentIntent
final PendingIntent contentIntent = notification.getNotification().contentIntent;
if (contentIntent != null) {
- final View.OnClickListener listener = makeClicker(contentIntent, notification.getKey(),
- isHeadsUp);
+ final View.OnClickListener listener = makeClicker(contentIntent, notification.getKey());
entry.row.setOnClickListener(listener);
} else {
entry.row.setOnClickListener(null);
@@ -2062,6 +2089,8 @@ public abstract class BaseStatusBar extends SystemUI implements
entry.row.setStatusBarNotification(notification);
entry.row.notifyContentUpdated();
entry.row.resetHeight();
+
+ applyRemoteInput(entry);
}
protected void notifyHeadsUpScreenOn(boolean screenOn) {
@@ -2133,9 +2162,6 @@ public abstract class BaseStatusBar extends SystemUI implements
}
public void destroy() {
- if (mSearchPanelView != null) {
- mWindowManager.removeViewImmediate(mSearchPanelView);
- }
mContext.unregisterReceiver(mBroadcastReceiver);
try {
mNotificationListener.unregisterAsSystemService();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 0b1b883..7aa9a90 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
+import android.util.Pair;
import com.android.internal.statusbar.IStatusBar;
import com.android.internal.statusbar.StatusBarIcon;
@@ -57,6 +58,9 @@ public class CommandQueue extends IStatusBar.Stub {
private static final int MSG_NOTIFICATION_LIGHT_OFF = 16 << MSG_SHIFT;
private static final int MSG_NOTIFICATION_LIGHT_PULSE = 17 << MSG_SHIFT;
private static final int MSG_SHOW_SCREEN_PIN_REQUEST = 18 << MSG_SHIFT;
+ private static final int MSG_APP_TRANSITION_PENDING = 19 << MSG_SHIFT;
+ private static final int MSG_APP_TRANSITION_CANCELLED = 20 << MSG_SHIFT;
+ private static final int MSG_APP_TRANSITION_STARTING = 21 << MSG_SHIFT;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -92,13 +96,14 @@ public class CommandQueue extends IStatusBar.Stub {
public void toggleRecentApps();
public void preloadRecentApps();
public void cancelPreloadRecentApps();
- public void showSearchPanel();
- public void hideSearchPanel();
public void setWindowState(int window, int state);
public void buzzBeepBlinked();
public void notificationLightOff();
public void notificationLightPulse(int argb, int onMillis, int offMillis);
public void showScreenPinningRequest();
+ public void appTransitionPending();
+ public void appTransitionCancelled();
+ public void appTransitionStarting(long startTime, long duration);
}
public CommandQueue(Callbacks callbacks, StatusBarIconList list) {
@@ -246,6 +251,28 @@ public class CommandQueue extends IStatusBar.Stub {
}
}
+ public void appTransitionPending() {
+ synchronized (mList) {
+ mHandler.removeMessages(MSG_APP_TRANSITION_PENDING);
+ mHandler.sendEmptyMessage(MSG_APP_TRANSITION_PENDING);
+ }
+ }
+
+ public void appTransitionCancelled() {
+ synchronized (mList) {
+ mHandler.removeMessages(MSG_APP_TRANSITION_PENDING);
+ mHandler.sendEmptyMessage(MSG_APP_TRANSITION_PENDING);
+ }
+ }
+
+ public void appTransitionStarting(long startTime, long duration) {
+ synchronized (mList) {
+ mHandler.removeMessages(MSG_APP_TRANSITION_STARTING);
+ mHandler.obtainMessage(MSG_APP_TRANSITION_STARTING, Pair.create(startTime, duration))
+ .sendToTarget();
+ }
+ }
+
private final class H extends Handler {
public void handleMessage(Message msg) {
final int what = msg.what & MSG_MASK;
@@ -328,6 +355,16 @@ public class CommandQueue extends IStatusBar.Stub {
case MSG_SHOW_SCREEN_PIN_REQUEST:
mCallbacks.showScreenPinningRequest();
break;
+ case MSG_APP_TRANSITION_PENDING:
+ mCallbacks.appTransitionPending();
+ break;
+ case MSG_APP_TRANSITION_CANCELLED:
+ mCallbacks.appTransitionCancelled();
+ break;
+ case MSG_APP_TRANSITION_STARTING:
+ Pair<Long, Long> data = (Pair<Long, Long>) msg.obj;
+ mCallbacks.appTransitionStarting(data.first, data.second);
+ break;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DelegateViewHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/DelegateViewHelper.java
index 7ae6764..9e2207e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/DelegateViewHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DelegateViewHelper.java
@@ -22,11 +22,12 @@ import android.graphics.RectF;
import android.view.MotionEvent;
import android.view.View;
import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.PhoneStatusBar;
public class DelegateViewHelper {
private View mDelegateView;
private View mSourceView;
- private BaseStatusBar mBar;
+ private PhoneStatusBar mBar;
private int[] mTempPoint = new int[2];
private float[] mDownPoint = new float[2];
private float mTriggerThreshhold;
@@ -45,7 +46,7 @@ public class DelegateViewHelper {
mDelegateView = view;
}
- public void setBar(BaseStatusBar phoneStatusBar) {
+ public void setBar(PhoneStatusBar phoneStatusBar) {
mBar = phoneStatusBar;
}
@@ -79,7 +80,7 @@ public class DelegateViewHelper {
float y = k < historySize ? event.getHistoricalY(k) : event.getY();
final float distance = mSwapXY ? (mDownPoint[0] - x) : (mDownPoint[1] - y);
if (distance > mTriggerThreshhold) {
- mBar.showSearchPanel();
+ mBar.invokeAssistGesture(false /* vibrate */);
mPanelShowing = true;
break;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DismissViewButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/DismissViewButton.java
index f2a5673..00665f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/DismissViewButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DismissViewButton.java
@@ -21,12 +21,9 @@ import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
-import android.os.SystemClock;
import android.util.AttributeSet;
-import android.view.Choreographer;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewRootImpl;
import android.widget.Button;
import com.android.systemui.R;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
index c9f0260..15a092c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
@@ -162,20 +162,20 @@ public class DragDownHelper implements Gefingerpoken {
? RUBBERBAND_FACTOR_EXPANDABLE
: RUBBERBAND_FACTOR_STATIC;
float rubberband = heightDelta * rubberbandFactor;
- if (expandable && (rubberband + child.getMinHeight()) > child.getMaxHeight()) {
- float overshoot = (rubberband + child.getMinHeight()) - child.getMaxHeight();
+ if (expandable && (rubberband + child.getMinHeight()) > child.getMaxContentHeight()) {
+ float overshoot = (rubberband + child.getMinHeight()) - child.getMaxContentHeight();
overshoot *= (1 - RUBBERBAND_FACTOR_STATIC);
rubberband -= overshoot;
}
- child.setActualHeight((int) (child.getMinHeight() + rubberband));
+ child.setContentHeight((int) (child.getMinHeight() + rubberband));
}
private void cancelExpansion(final ExpandableView child) {
- if (child.getActualHeight() == child.getMinHeight()) {
+ if (child.getContentHeight() == child.getMinHeight()) {
return;
}
- ObjectAnimator anim = ObjectAnimator.ofInt(child, "actualHeight",
- child.getActualHeight(), child.getMinHeight());
+ ObjectAnimator anim = ObjectAnimator.ofInt(child, "contentHeight",
+ child.getContentHeight(), child.getMinHeight());
anim.setInterpolator(mInterpolator);
anim.setDuration(SPRING_BACK_ANIMATION_LENGTH_MS);
anim.addListener(new AnimatorListenerAdapter() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
index 0825aa3..5db0699 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
@@ -20,11 +20,9 @@ import android.content.Context;
import android.content.res.Configuration;
import android.util.AttributeSet;
import android.view.View;
-import android.view.animation.Interpolator;
import android.widget.TextView;
import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
public class EmptyShadeView extends StackScrollerDecorView {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 8ad8406..06a174e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -16,9 +16,13 @@
package com.android.systemui.statusbar;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.AnimationDrawable;
+import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
@@ -26,12 +30,25 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewStub;
import android.view.accessibility.AccessibilityEvent;
+import android.view.animation.LinearInterpolator;
import android.widget.ImageView;
+
import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.statusbar.stack.NotificationChildrenContainer;
+import com.android.systemui.statusbar.stack.StackScrollState;
+import com.android.systemui.statusbar.stack.StackStateAnimator;
+import com.android.systemui.statusbar.stack.StackViewState;
+
+import java.util.List;
public class ExpandableNotificationRow extends ActivatableNotificationView {
+
+ private static final int DEFAULT_DIVIDER_ALPHA = 0x29;
+ private static final int COLORED_DIVIDER_ALPHA = 0x7B;
+ private final LinearInterpolator mLinearInterpolator = new LinearInterpolator();
private int mRowMinHeight;
- private int mRowMaxHeight;
/** Does this row contain layouts that can adapt to row expansion */
private boolean mExpandable;
@@ -70,6 +87,27 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
private StatusBarNotification mStatusBarNotification;
private boolean mIsHeadsUp;
+ private View mExpandButton;
+ private View mExpandButtonDivider;
+ private ViewStub mExpandButtonStub;
+ private ViewStub mChildrenContainerStub;
+ private NotificationGroupManager mGroupManager;
+ private View mExpandButtonContainer;
+ private boolean mChildrenExpanded;
+ private NotificationChildrenContainer mChildrenContainer;
+ private ValueAnimator mChildExpandAnimator;
+ private float mChildrenExpandProgress;
+ private float mExpandButtonStart;
+ private ViewStub mGutsStub;
+ private boolean mHasExpandAction;
+ private boolean mIsSystemChildExpanded;
+ private OnClickListener mExpandClickListener = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mGroupManager.setGroupExpanded(mStatusBarNotification,
+ !mChildrenExpanded);
+ }
+ };
public void setIconAnimationRunning(boolean running) {
setIconAnimationRunning(running, mPublicLayout);
@@ -119,6 +157,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
public void setStatusBarNotification(StatusBarNotification statusBarNotification) {
mStatusBarNotification = statusBarNotification;
updateVetoButton();
+ updateExpandButton();
}
public StatusBarNotification getStatusBarNotification() {
@@ -129,6 +168,101 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
mIsHeadsUp = isHeadsUp;
}
+ public void setGroupManager(NotificationGroupManager groupManager) {
+ mGroupManager = groupManager;
+ }
+
+ public void addChildNotification(ExpandableNotificationRow row) {
+ addChildNotification(row, -1);
+ }
+
+ /**
+ * Add a child notification to this view.
+ *
+ * @param row the row to add
+ * @param childIndex the index to add it at, if -1 it will be added at the end
+ */
+ public void addChildNotification(ExpandableNotificationRow row, int childIndex) {
+ if (mChildrenContainer == null) {
+ mChildrenContainerStub.inflate();
+ }
+ mChildrenContainer.addNotification(row, childIndex);
+ }
+
+ public void removeChildNotification(ExpandableNotificationRow row) {
+ if (mChildrenContainer != null) {
+ mChildrenContainer.removeNotification(row);
+ }
+ }
+
+ @Override
+ public boolean areChildrenExpanded() {
+ return mChildrenExpanded;
+ }
+
+ public List<ExpandableNotificationRow> getNotificationChildren() {
+ return mChildrenContainer == null ? null : mChildrenContainer.getNotificationChildren();
+ }
+
+ /**
+ * Apply the order given in the list to the children.
+ *
+ * @param childOrder the new list order
+ * @return whether the list order has changed
+ */
+ public boolean applyChildOrder(List<ExpandableNotificationRow> childOrder) {
+ return mChildrenContainer != null && mChildrenContainer.applyChildOrder(childOrder);
+ }
+
+ public void getChildrenStates(StackScrollState resultState) {
+ if (mChildrenExpanded) {
+ StackViewState parentState = resultState.getViewStateForView(this);
+ mChildrenContainer.getState(resultState, parentState);
+ }
+ }
+
+ public void applyChildrenState(StackScrollState state) {
+ if (mChildrenExpanded) {
+ mChildrenContainer.applyState(state);
+ }
+ }
+
+ public void prepareExpansionChanged(StackScrollState state) {
+ if (mChildrenExpanded) {
+ mChildrenContainer.prepareExpansionChanged(state);
+ }
+ }
+
+ public void startChildAnimation(StackScrollState finalState,
+ StackStateAnimator stateAnimator, boolean withDelays, long delay, long duration) {
+ if (mChildrenExpanded) {
+ mChildrenContainer.startAnimationToState(finalState, stateAnimator, withDelays, delay,
+ duration);
+ }
+ }
+
+ public ExpandableNotificationRow getViewAtPosition(float y) {
+ if (!mChildrenExpanded) {
+ return this;
+ } else {
+ ExpandableNotificationRow view = mChildrenContainer.getViewAtPosition(y);
+ return view == null ? this : view;
+ }
+ }
+
+ public NotificationGuts getGuts() {
+ return mGuts;
+ }
+
+ protected int calculateContentHeightFromActualHeight(int actualHeight) {
+ int realActualHeight = actualHeight;
+ if (hasBottomDecor()) {
+ realActualHeight -= getBottomDecorHeight();
+ }
+ realActualHeight = Math.max(getMinHeight(), realActualHeight);
+ return realActualHeight;
+ }
+
public interface ExpansionLogger {
public void logNotificationExpansion(String key, boolean userAction, boolean expanded);
}
@@ -145,7 +279,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
super.reset();
mRowMinHeight = 0;
final boolean wasExpanded = isExpanded();
- mRowMaxHeight = 0;
+ mMaxViewHeight = 0;
mExpandable = false;
mHasUserChangedExpansion = false;
mUserLocked = false;
@@ -180,21 +314,97 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
super.onFinishInflate();
mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic);
mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded);
- ViewStub gutsStub = (ViewStub) findViewById(R.id.notification_guts_stub);
- gutsStub.setOnInflateListener(new ViewStub.OnInflateListener() {
+ mGutsStub = (ViewStub) findViewById(R.id.notification_guts_stub);
+ mGutsStub.setOnInflateListener(new ViewStub.OnInflateListener() {
@Override
public void onInflate(ViewStub stub, View inflated) {
mGuts = (NotificationGuts) inflated;
mGuts.setClipTopAmount(getClipTopAmount());
mGuts.setActualHeight(getActualHeight());
+ mGutsStub = null;
+ }
+ });
+ mExpandButtonStub = (ViewStub) findViewById(R.id.more_button_stub);
+ mExpandButtonStub.setOnInflateListener(new ViewStub.OnInflateListener() {
+
+ @Override
+ public void onInflate(ViewStub stub, View inflated) {
+ mExpandButtonContainer = inflated;
+ mExpandButton = inflated.findViewById(R.id.notification_expand_button);
+ mExpandButtonDivider = inflated.findViewById(R.id.notification_expand_divider);
+ mExpandButtonContainer.setOnClickListener(mExpandClickListener);
+ }
+ });
+ mChildrenContainerStub = (ViewStub) findViewById(R.id.child_container_stub);
+ mChildrenContainerStub.setOnInflateListener(new ViewStub.OnInflateListener() {
+
+ @Override
+ public void onInflate(ViewStub stub, View inflated) {
+ mChildrenContainer = (NotificationChildrenContainer) inflated;
+ mChildrenContainer.setCollapseClickListener(mExpandClickListener);
+ updateChildrenVisibility(false);
}
});
mVetoButton = findViewById(R.id.veto);
}
+ public void inflateGuts() {
+ if (mGuts == null) {
+ mGutsStub.inflate();
+ }
+ }
+
+ private void updateChildrenVisibility(boolean animated) {
+ if (mChildrenContainer == null) {
+ return;
+ }
+ if (mChildExpandAnimator != null) {
+ mChildExpandAnimator.cancel();
+ }
+ float targetProgress = mChildrenExpanded ? 1.0f : 0.0f;
+ if (animated) {
+ if (mChildrenExpanded) {
+ mChildrenContainer.setVisibility(VISIBLE);
+ }
+ mExpandButtonStart = mExpandButtonContainer.getTranslationY();
+ mChildExpandAnimator = ValueAnimator.ofFloat(mChildrenExpandProgress, targetProgress);
+ mChildExpandAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ setChildrenExpandProgress((float) animation.getAnimatedValue());
+ }
+ });
+ mChildExpandAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mChildExpandAnimator = null;
+ if (!mChildrenExpanded) {
+ mChildrenContainer.setVisibility(INVISIBLE);
+ }
+ }
+ });
+ mChildExpandAnimator.setInterpolator(mLinearInterpolator);
+ mChildExpandAnimator.setDuration(
+ StackStateAnimator.ANIMATION_DURATION_EXPAND_CLICKED);
+ mChildExpandAnimator.start();
+ } else {
+ setChildrenExpandProgress(targetProgress);
+ mChildrenContainer.setVisibility(mChildrenExpanded ? VISIBLE : INVISIBLE);
+ }
+ }
+
+ private void setChildrenExpandProgress(float progress) {
+ mChildrenExpandProgress = progress;
+ updateExpandButtonAppearance();
+ NotificationContentView showingLayout = getShowingLayout();
+ float alpha = 1.0f - mChildrenExpandProgress;
+ alpha = PhoneStatusBar.ALPHA_OUT.getInterpolation(alpha);
+ showingLayout.setAlpha(alpha);
+ }
+
@Override
- public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
- if (super.onRequestSendAccessibilityEvent(child, event)) {
+ public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) {
+ if (super.onRequestSendAccessibilityEventInternal(child, event)) {
// Add a record for the entire layout since its content is somehow small.
// The event comes from a leaf view that is interacted with.
AccessibilityEvent record = AccessibilityEvent.obtain();
@@ -217,7 +427,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
public void setHeightRange(int rowMinHeight, int rowMaxHeight) {
mRowMinHeight = rowMinHeight;
- mRowMaxHeight = rowMaxHeight;
+ mMaxViewHeight = rowMaxHeight;
}
public boolean isExpandable() {
@@ -281,7 +491,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
if (expand != mIsSystemExpanded) {
final boolean wasExpanded = isExpanded();
mIsSystemExpanded = expand;
- notifyHeightChanged();
+ notifyHeightChanged(false /* needsAnimation */);
logExpansionEvent(false, wasExpanded);
}
}
@@ -295,7 +505,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
mExpansionDisabled = expansionDisabled;
logExpansionEvent(false, wasExpanded);
if (wasExpanded != isExpanded()) {
- notifyHeightChanged();
+ notifyHeightChanged(false /* needsAnimation */);
}
}
}
@@ -313,9 +523,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
public void applyExpansionToLayout() {
boolean expand = isExpanded();
if (expand && mExpandable) {
- setActualHeight(mMaxExpandHeight);
+ setContentHeight(mMaxExpandHeight);
} else {
- setActualHeight(mRowMinHeight);
+ setContentHeight(mRowMinHeight);
}
}
@@ -325,12 +535,26 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
return getActualHeight();
}
boolean inExpansionState = isExpanded();
- if (!inExpansionState) {
- // not expanded, so we return the collapsed size
- return mRowMinHeight;
+ int maxContentHeight;
+ if ((!inExpansionState && !mChildrenExpanded) || mShowingPublicForIntrinsicHeight) {
+ maxContentHeight = mRowMinHeight;
+ } else if (mChildrenExpanded) {
+ maxContentHeight = mChildrenContainer.getIntrinsicHeight();
+ } else {
+ maxContentHeight = getMaxExpandHeight();
}
+ return maxContentHeight + getBottomDecorHeight();
+ }
+
+ @Override
+ protected boolean hasBottomDecor() {
+ return BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS
+ && !mIsHeadsUp && mGroupManager.hasGroupChildren(mStatusBarNotification);
+ }
- return mShowingPublicForIntrinsicHeight ? mRowMinHeight : getMaxExpandHeight();
+ @Override
+ protected boolean canHaveBottomDecor() {
+ return BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS && !mIsHeadsUp;
}
/**
@@ -343,7 +567,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
*/
private boolean isExpanded() {
return !mExpansionDisabled
- && (!hasUserChangedExpansion() && isSystemExpanded() || isUserExpanded());
+ && (!hasUserChangedExpansion() && (isSystemExpanded() || isSystemChildExpanded())
+ || isUserExpanded());
+ }
+
+ private boolean isSystemChildExpanded() {
+ return mIsSystemChildExpanded;
+ }
+
+ public void setSystemChildExpanded(boolean expanded) {
+ mIsSystemChildExpanded = expanded;
}
@Override
@@ -357,11 +590,20 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
mWasReset = false;
}
+ @Override
+ protected boolean isChildInvisible(View child) {
+
+ // We don't want to layout the ChildrenContainer if this is a heads-up view, otherwise the
+ // view will get too high and the shadows will be off.
+ boolean isInvisibleChildContainer = child == mChildrenContainer && mIsHeadsUp;
+ return super.isChildInvisible(child) || isInvisibleChildContainer;
+ }
+
private void updateMaxExpandHeight() {
int intrinsicBefore = getIntrinsicHeight();
mMaxExpandHeight = mPrivateLayout.getMaxHeight();
if (intrinsicBefore != getIntrinsicHeight()) {
- notifyHeightChanged();
+ notifyHeightChanged(false /* needsAnimation */);
}
}
@@ -428,8 +670,127 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
mVetoButton.setVisibility(isClearable() && !mShowingPublic ? View.VISIBLE : View.GONE);
}
+ public void setChildrenExpanded(boolean expanded, boolean animate) {
+ mChildrenExpanded = expanded;
+ updateChildrenVisibility(animate);
+ }
+
+ public void updateExpandButton() {
+ boolean hasExpand = hasBottomDecor();
+ if (hasExpand != mHasExpandAction) {
+ if (hasExpand) {
+ if (mExpandButtonContainer == null) {
+ mExpandButtonStub.inflate();
+ }
+ mExpandButtonContainer.setVisibility(View.VISIBLE);
+ updateExpandButtonAppearance();
+ updateExpandButtonColor();
+ } else if (mExpandButtonContainer != null) {
+ mExpandButtonContainer.setVisibility(View.GONE);
+ }
+ notifyHeightChanged(true /* needsAnimation */);
+ }
+ mHasExpandAction = hasExpand;
+ }
+
+ private void updateExpandButtonAppearance() {
+ if (mExpandButtonContainer == null) {
+ return;
+ }
+ float expandButtonAlpha = 0.0f;
+ float expandButtonTranslation = 0.0f;
+ float containerTranslation = 0.0f;
+ int minHeight = getMinHeight();
+ if (!mChildrenExpanded || mChildExpandAnimator != null) {
+ int expandActionHeight = getBottomDecorHeight();
+ int translationY = getActualHeight() - expandActionHeight;
+ if (translationY > minHeight) {
+ containerTranslation = translationY;
+ expandButtonAlpha = 1.0f;
+ expandButtonTranslation = 0.0f;
+ } else {
+ containerTranslation = minHeight;
+ float progress = expandActionHeight != 0
+ ? (minHeight - translationY) / (float) expandActionHeight
+ : 1.0f;
+ expandButtonTranslation = -progress * expandActionHeight * 0.7f;
+ float alphaProgress = Math.min(progress / 0.7f, 1.0f);
+ alphaProgress = PhoneStatusBar.ALPHA_OUT.getInterpolation(alphaProgress);
+ expandButtonAlpha = 1.0f - alphaProgress;
+ }
+ }
+ if (mChildExpandAnimator != null || mChildrenExpanded) {
+ expandButtonAlpha = (1.0f - mChildrenExpandProgress)
+ * expandButtonAlpha;
+ expandButtonTranslation = (1.0f - mChildrenExpandProgress)
+ * expandButtonTranslation;
+ float newTranslation = -getBottomDecorHeight();
+
+ // We don't want to take the actual height of the view as this is already
+ // interpolated by a custom interpolator leading to a confusing animation. We want
+ // to have a stable end value to interpolate in between
+ float collapsedHeight = !mChildrenExpanded
+ ? Math.max(StackStateAnimator.getFinalActualHeight(this)
+ - getBottomDecorHeight(), minHeight)
+ : mExpandButtonStart;
+ float translationProgress = mFastOutSlowInInterpolator.getInterpolation(
+ mChildrenExpandProgress);
+ containerTranslation = (1.0f - translationProgress) * collapsedHeight
+ + translationProgress * newTranslation;
+ }
+ mExpandButton.setAlpha(expandButtonAlpha);
+ mExpandButtonDivider.setAlpha(expandButtonAlpha);
+ mExpandButton.setTranslationY(expandButtonTranslation);
+ mExpandButtonContainer.setTranslationY(containerTranslation);
+ NotificationContentView showingLayout = getShowingLayout();
+ float layoutTranslation =
+ mExpandButtonContainer.getTranslationY() - showingLayout.getContentHeight();
+ layoutTranslation = Math.min(layoutTranslation, 0);
+ if (!mChildrenExpanded && mChildExpandAnimator == null) {
+ // Needed for the DragDownHelper in order not to jump there, as the position
+ // can be negative for a short time.
+ layoutTranslation = 0;
+ }
+ showingLayout.setTranslationY(layoutTranslation);
+ if (mChildrenContainer != null) {
+ mChildrenContainer.setTranslationY(
+ mExpandButtonContainer.getTranslationY() + getBottomDecorHeight());
+ }
+ }
+
+ private void updateExpandButtonColor() {
+ // TODO: This needs some more baking, currently only the divider is colored according to
+ // the tint, but legacy black doesn't work yet perfectly for the button etc.
+ int color = getRippleColor();
+ if (color == mNormalRippleColor) {
+ color = 0;
+ }
+ if (mExpandButtonDivider != null) {
+ applyTint(mExpandButtonDivider, color);
+ }
+ if (mChildrenContainer != null) {
+ mChildrenContainer.setTintColor(color);
+ }
+ }
+
+ public static void applyTint(View v, int color) {
+ int alpha;
+ if (color != 0) {
+ alpha = COLORED_DIVIDER_ALPHA;
+ } else {
+ color = 0xff000000;
+ alpha = DEFAULT_DIVIDER_ALPHA;
+ }
+ if (v.getBackground() instanceof ColorDrawable) {
+ ColorDrawable background = (ColorDrawable) v.getBackground();
+ background.mutate();
+ background.setColor(color);
+ background.setAlpha(alpha);
+ }
+ }
+
public int getMaxExpandHeight() {
- return mShowingPublicForIntrinsicHeight ? mRowMinHeight : mMaxExpandHeight;
+ return mMaxExpandHeight;
}
@Override
@@ -440,17 +801,19 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
@Override
public void setActualHeight(int height, boolean notifyListeners) {
- mPrivateLayout.setActualHeight(height);
- mPublicLayout.setActualHeight(height);
+ super.setActualHeight(height, notifyListeners);
+ int contentHeight = calculateContentHeightFromActualHeight(height);
+ mPrivateLayout.setContentHeight(contentHeight);
+ mPublicLayout.setContentHeight(contentHeight);
if (mGuts != null) {
mGuts.setActualHeight(height);
}
invalidate();
- super.setActualHeight(height, notifyListeners);
+ updateExpandButtonAppearance();
}
@Override
- public int getMaxHeight() {
+ public int getMaxContentHeight() {
NotificationContentView showingLayout = getShowingLayout();
return showingLayout.getMaxHeight();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index ebc663c..7ae0d6d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -33,24 +33,32 @@ import java.util.ArrayList;
*/
public abstract class ExpandableView extends FrameLayout {
- private final int mMaxNotificationHeight;
-
- private OnHeightChangedListener mOnHeightChangedListener;
+ private final int mBottomDecorHeight;
+ protected OnHeightChangedListener mOnHeightChangedListener;
+ protected int mMaxViewHeight;
private int mActualHeight;
protected int mClipTopAmount;
private boolean mActualHeightInitialized;
private boolean mDark;
private ArrayList<View> mMatchParentViews = new ArrayList<View>();
+ private int mClipTopOptimization;
+ private static Rect mClipRect = new Rect();
public ExpandableView(Context context, AttributeSet attrs) {
super(context, attrs);
- mMaxNotificationHeight = getResources().getDimensionPixelSize(
+ mMaxViewHeight = getResources().getDimensionPixelSize(
R.dimen.notification_max_height);
+ mBottomDecorHeight = resolveBottomDecorHeight();
+ }
+
+ protected int resolveBottomDecorHeight() {
+ return getResources().getDimensionPixelSize(
+ R.dimen.notification_bottom_decor_height);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int ownMaxHeight = mMaxNotificationHeight;
+ int ownMaxHeight = mMaxViewHeight;
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY;
boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST;
@@ -63,6 +71,9 @@ public abstract class ExpandableView extends FrameLayout {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
+ if (child.getVisibility() == GONE || isChildInvisible(child)) {
+ continue;
+ }
int childHeightSpec = newHeightSpec;
ViewGroup.LayoutParams layoutParams = child.getLayoutParams();
if (layoutParams.height != ViewGroup.LayoutParams.MATCH_PARENT) {
@@ -81,7 +92,8 @@ public abstract class ExpandableView extends FrameLayout {
mMatchParentViews.add(child);
}
}
- int ownHeight = hasFixedHeight ? ownMaxHeight : maxChildHeight;
+ int ownHeight = hasFixedHeight ? ownMaxHeight :
+ isHeightLimited ? Math.min(ownMaxHeight, maxChildHeight) : maxChildHeight;
newHeightSpec = MeasureSpec.makeMeasureSpec(ownHeight, MeasureSpec.EXACTLY);
for (View child : mMatchParentViews) {
child.measure(getChildMeasureSpec(
@@ -90,6 +102,10 @@ public abstract class ExpandableView extends FrameLayout {
}
mMatchParentViews.clear();
int width = MeasureSpec.getSize(widthMeasureSpec);
+ if (canHaveBottomDecor()) {
+ // We always account for the expandAction as well.
+ ownHeight += mBottomDecorHeight;
+ }
setMeasuredDimension(width, ownHeight);
}
@@ -99,7 +115,7 @@ public abstract class ExpandableView extends FrameLayout {
if (!mActualHeightInitialized && mActualHeight == 0) {
int initialHeight = getInitialHeight();
if (initialHeight != 0) {
- setActualHeight(initialHeight);
+ setContentHeight(initialHeight);
}
}
}
@@ -140,13 +156,14 @@ public abstract class ExpandableView extends FrameLayout {
public void setActualHeight(int actualHeight, boolean notifyListeners) {
mActualHeightInitialized = true;
mActualHeight = actualHeight;
+ updateClipping();
if (notifyListeners) {
- notifyHeightChanged();
+ notifyHeightChanged(false /* needsAnimation */);
}
}
- public void setActualHeight(int actualHeight) {
- setActualHeight(actualHeight, true);
+ public void setContentHeight(int contentHeight) {
+ setActualHeight(contentHeight + getBottomDecorHeight(), true);
}
/**
@@ -159,14 +176,39 @@ public abstract class ExpandableView extends FrameLayout {
}
/**
+ * This view may have a bottom decor which will be placed below the content. If it has one, this
+ * view will be layouted higher than just the content by {@link #mBottomDecorHeight}.
+ * @return the height of the decor if it currently has one
+ */
+ public int getBottomDecorHeight() {
+ return hasBottomDecor() ? mBottomDecorHeight : 0;
+ }
+
+ /**
+ * @return whether this view may have a bottom decor at all. This will force the view to layout
+ * itself higher than just it's content
+ */
+ protected boolean canHaveBottomDecor() {
+ return false;
+ }
+
+ /**
+ * @return whether this view has a decor view below it's content. This will make the intrinsic
+ * height from {@link #getIntrinsicHeight()} higher as well
+ */
+ protected boolean hasBottomDecor() {
+ return false;
+ }
+
+ /**
* @return The maximum height of this notification.
*/
- public int getMaxHeight() {
+ public int getMaxContentHeight() {
return getHeight();
}
/**
- * @return The minimum height of this notification.
+ * @return The minimum content height of this notification.
*/
public int getMinHeight() {
return getHeight();
@@ -245,9 +287,9 @@ public abstract class ExpandableView extends FrameLayout {
return false;
}
- public void notifyHeightChanged() {
+ public void notifyHeightChanged(boolean needsAnimation) {
if (mOnHeightChangedListener != null) {
- mOnHeightChangedListener.onHeightChanged(this);
+ mOnHeightChangedListener.onHeightChanged(this, needsAnimation);
}
}
@@ -298,6 +340,41 @@ public abstract class ExpandableView extends FrameLayout {
outRect.top += getTranslationY() + getClipTopAmount();
}
+ public int getContentHeight() {
+ return mActualHeight - getBottomDecorHeight();
+ }
+
+ /**
+ * @return whether the given child can be ignored for layouting and measuring purposes
+ */
+ protected boolean isChildInvisible(View child) {
+ return false;
+ }
+
+ public boolean areChildrenExpanded() {
+ return false;
+ }
+
+ private void updateClipping() {
+ mClipRect.set(0, mClipTopOptimization, getWidth(), getActualHeight());
+ setClipBounds(mClipRect);
+ }
+
+ public int getClipTopOptimization() {
+ return mClipTopOptimization;
+ }
+
+ /**
+ * Set that the view will be clipped by a given amount from the top. Contrary to
+ * {@link #setClipTopAmount} this amount doesn't effect shadows and the background.
+ *
+ * @param clipTopOptimization the amount to clip from the top
+ */
+ public void setClipTopOptimization(int clipTopOptimization) {
+ mClipTopOptimization = clipTopOptimization;
+ updateClipping();
+ }
+
/**
* A listener notifying when {@link #getActualHeight} changes.
*/
@@ -306,8 +383,9 @@ public abstract class ExpandableView extends FrameLayout {
/**
* @param view the view for which the height changed, or {@code null} if just the top
* padding or the padding between the elements changed
+ * @param needsAnimation whether the view height needs to be animated
*/
- void onHeightChanged(ExpandableView view);
+ void onHeightChanged(ExpandableView view, boolean needsAnimation);
/**
* Called when the view is reset and therefore the height will change abruptly
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java
index 9f0f84e..0fa088b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar;
import android.animation.Animator;
-import android.animation.ValueAnimator;
import android.content.Context;
import android.view.ViewPropertyAnimator;
import android.view.animation.AnimationUtils;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
index 0fc46e9..01aa8d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
@@ -44,7 +44,7 @@ public class NotificationBackgroundView extends View {
}
private void draw(Canvas canvas, Drawable drawable) {
- if (drawable != null) {
+ if (drawable != null && mActualHeight > mClipTopAmount) {
drawable.setBounds(0, mClipTopAmount, getWidth(), mActualHeight);
drawable.draw(canvas);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index 914b3d8..745e75d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -17,21 +17,16 @@
package com.android.systemui.statusbar;
import android.content.Context;
-import android.graphics.ColorFilter;
-import android.graphics.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.widget.FrameLayout;
-import android.widget.ImageView;
import com.android.systemui.R;
/**
@@ -52,7 +47,7 @@ public class NotificationContentView extends FrameLayout {
private int mSmallHeight;
private int mClipTopAmount;
- private int mActualHeight;
+ private int mContentHeight;
private final Interpolator mLinearInterpolator = new LinearInterpolator();
@@ -102,7 +97,7 @@ public class NotificationContentView extends FrameLayout {
mSmallHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
mContractedVisible = true;
if (resetActualHeight) {
- mActualHeight = mSmallHeight;
+ mContentHeight = mSmallHeight;
}
}
@@ -159,12 +154,17 @@ public class NotificationContentView extends FrameLayout {
}
}
- public void setActualHeight(int actualHeight) {
- mActualHeight = actualHeight;
+ public void setContentHeight(int contentHeight) {
+ contentHeight = Math.max(Math.min(contentHeight, getHeight()), getMinHeight());
+ mContentHeight = contentHeight;
selectLayout(mAnimate /* animate */, false /* force */);
updateClipping();
}
+ public int getContentHeight() {
+ return mContentHeight;
+ }
+
public int getMaxHeight() {
// The maximum height is just the laid out height.
@@ -181,7 +181,7 @@ public class NotificationContentView extends FrameLayout {
}
private void updateClipping() {
- mClipBounds.set(0, mClipTopAmount, getWidth(), mActualHeight);
+ mClipBounds.set(0, mClipTopAmount, getWidth(), mContentHeight);
setClipBounds(mClipBounds);
}
@@ -240,7 +240,7 @@ public class NotificationContentView extends FrameLayout {
}
private boolean showContractedChild() {
- return mActualHeight <= mSmallHeight || mExpandedChild == null;
+ return mContentHeight <= mSmallHeight || mExpandedChild == null;
}
public void notifyContentUpdated() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index 34c458a..912f414 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -22,9 +22,10 @@ import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
-import android.util.ArraySet;
import android.view.View;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
@@ -91,10 +92,12 @@ public class NotificationData {
private final ArrayMap<String, Entry> mEntries = new ArrayMap<>();
private final ArrayList<Entry> mSortedAndFiltered = new ArrayList<>();
- private ArraySet<String> mGroupsWithSummaries = new ArraySet<>();
+
+ private NotificationGroupManager mGroupManager;
private RankingMap mRankingMap;
private final Ranking mTmpRanking = new Ranking();
+
private final Comparator<Entry> mRankingComparator = new Comparator<Entry>() {
private final Ranking mRankingA = new Ranking();
private final Ranking mRankingB = new Ranking();
@@ -141,6 +144,7 @@ public class NotificationData {
public NotificationData(Environment environment) {
mEnvironment = environment;
+ mGroupManager = environment.getGroupManager();
}
/**
@@ -163,12 +167,14 @@ public class NotificationData {
public void add(Entry entry, RankingMap ranking) {
mEntries.put(entry.notification.getKey(), entry);
updateRankingAndSort(ranking);
+ mGroupManager.onEntryAdded(entry);
}
public Entry remove(String key, RankingMap ranking) {
Entry removed = mEntries.remove(key);
if (removed == null) return null;
updateRankingAndSort(ranking);
+ mGroupManager.onEntryRemoved(removed);
return removed;
}
@@ -203,7 +209,6 @@ public class NotificationData {
// anything changed, and this class should call back the UI so it updates itself.
public void filterAndSort() {
mSortedAndFiltered.clear();
- mGroupsWithSummaries.clear();
final int N = mEntries.size();
for (int i = 0; i < N; i++) {
@@ -214,32 +219,12 @@ public class NotificationData {
continue;
}
- if (sbn.getNotification().isGroupSummary()) {
- mGroupsWithSummaries.add(sbn.getGroupKey());
- }
mSortedAndFiltered.add(entry);
}
- // Second pass: Filter out group children with summary.
- if (!mGroupsWithSummaries.isEmpty()) {
- final int M = mSortedAndFiltered.size();
- for (int i = M - 1; i >= 0; i--) {
- Entry ent = mSortedAndFiltered.get(i);
- StatusBarNotification sbn = ent.notification;
- if (sbn.getNotification().isGroupChild() &&
- mGroupsWithSummaries.contains(sbn.getGroupKey())) {
- mSortedAndFiltered.remove(i);
- }
- }
- }
-
Collections.sort(mSortedAndFiltered, mRankingComparator);
}
- public boolean isGroupWithSummary(String groupKey) {
- return mGroupsWithSummaries.contains(groupKey);
- }
-
boolean shouldFilterOut(StatusBarNotification sbn) {
if (!(mEnvironment.isDeviceProvisioned() ||
showNotificationEvenIfUnprovisioned(sbn))) {
@@ -254,6 +239,11 @@ public class NotificationData {
mEnvironment.shouldHideSensitiveContents(sbn.getUserId())) {
return true;
}
+
+ if (!BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS
+ && mGroupManager.isChildInGroupWithSummary(sbn)) {
+ return true;
+ }
return false;
}
@@ -328,5 +318,6 @@ public class NotificationData {
public boolean isDeviceProvisioned();
public boolean isNotificationForCurrentProfiles(StatusBarNotification sbn);
public String getCurrentMediaNotificationKey();
+ public NotificationGroupManager getGroupManager();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java
index bfa3aa5..5fa7070 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java
@@ -62,4 +62,13 @@ public class NotificationOverflowContainer extends ActivatableNotificationView {
public NotificationOverflowIconsView getIconsView() {
return mIconsView;
}
+
+ protected int getContentHeightFromActualHeight(int actualHeight) {
+ int realActualHeight = actualHeight;
+ if (hasBottomDecor()) {
+ realActualHeight -= getBottomDecorHeight();
+ }
+ realActualHeight = Math.max(getMinHeight(), realActualHeight);
+ return realActualHeight;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowIconsView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowIconsView.java
index c4c9dac..88bb714 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowIconsView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowIconsView.java
@@ -18,7 +18,6 @@ package com.android.systemui.statusbar;
import android.app.Notification;
import android.content.Context;
-import android.content.res.Configuration;
import android.graphics.PorterDuff;
import android.util.AttributeSet;
import android.widget.ImageView;
@@ -46,7 +45,7 @@ public class NotificationOverflowIconsView extends IconMerger {
protected void onFinishInflate() {
super.onFinishInflate();
mNotificationColorUtil = NotificationColorUtil.getInstance(getContext());
- mTintColor = getResources().getColor(R.color.keyguard_overflow_content_color);
+ mTintColor = getContext().getColor(R.color.keyguard_overflow_content_color);
mIconSize = getResources().getDimensionPixelSize(
com.android.internal.R.dimen.status_bar_icon_size);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java
index 57d162b..958b8b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java
@@ -60,7 +60,7 @@ public class NotificationTemplateViewWrapper extends NotificationViewWrapper {
super(view);
mIconDarkAlpha = ctx.getResources().getInteger(R.integer.doze_small_icon_alpha);
mIconBackgroundDarkColor =
- ctx.getResources().getColor(R.color.doze_small_icon_background_color);
+ ctx.getColor(R.color.doze_small_icon_background_color);
mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(ctx,
android.R.interpolator.linear_out_slow_in);
resolveViews();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java
index 78b9739..44e8b85 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java
@@ -19,8 +19,6 @@ 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.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ServiceMonitor.java b/packages/SystemUI/src/com/android/systemui/statusbar/ServiceMonitor.java
index aea9ec6..602989a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ServiceMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ServiceMonitor.java
@@ -177,6 +177,7 @@ public class ServiceMonitor {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addDataScheme("package");
mContext.registerReceiver(mBroadcastReceiver, filter);
@@ -196,13 +197,14 @@ public class ServiceMonitor {
+ " extras=" + bundleToString(intent.getExtras()));
if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())) {
mHandler.sendEmptyMessage(MSG_START_SERVICE);
- } else if (Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction())) {
- PackageManager pm = mContext.getPackageManager();
- boolean serviceEnabled =
- pm.getApplicationEnabledSetting(mServiceName.getPackageName())
- != PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+ } else if (Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction())
+ || Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
+ final PackageManager pm = mContext.getPackageManager();
+ final boolean serviceEnabled = isPackageAvailable()
+ && pm.getApplicationEnabledSetting(mServiceName.getPackageName())
+ != PackageManager.COMPONENT_ENABLED_STATE_DISABLED
&& pm.getComponentEnabledSetting(mServiceName)
- != PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+ != PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
if (mBound && !serviceEnabled) {
stopService();
scheduleCheckBound();
@@ -279,4 +281,25 @@ public class ServiceMonitor {
}
return sb.append('}').toString();
}
+
+ public ComponentName getComponent() {
+ return getComponentNameFromSetting();
+ }
+
+ public void setComponent(ComponentName component) {
+ final String setting = component == null ? null : component.flattenToShortString();
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ mSettingKey, setting, UserHandle.USER_CURRENT);
+ }
+
+ public boolean isPackageAvailable() {
+ final ComponentName component = getComponent();
+ if (component == null) return false;
+ try {
+ return mContext.getPackageManager().isPackageAvailable(component.getPackageName());
+ } catch (RuntimeException e) {
+ Log.w(mTag, "Error checking package availability", e);
+ return false;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
index 8e35ee9..a82afcf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
@@ -17,6 +17,9 @@
package com.android.systemui.statusbar;
import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
import android.telephony.SubscriptionInfo;
import android.util.AttributeSet;
import android.util.Log;
@@ -55,9 +58,11 @@ public class SignalClusterView
private int mAirplaneContentDescription;
private String mWifiDescription;
private ArrayList<PhoneState> mPhoneStates = new ArrayList<PhoneState>();
+ private int mIconTint = Color.WHITE;
+ private float mDarkIntensity;
ViewGroup mWifiGroup;
- ImageView mVpn, mWifi, mAirplane, mNoSims;
+ ImageView mVpn, mWifi, mAirplane, mNoSims, mWifiDark, mNoSimsDark;
View mWifiAirplaneSpacer;
View mWifiSignalSpacer;
LinearLayout mMobileSignalGroup;
@@ -111,8 +116,10 @@ public class SignalClusterView
mVpn = (ImageView) findViewById(R.id.vpn);
mWifiGroup = (ViewGroup) findViewById(R.id.wifi_combo);
mWifi = (ImageView) findViewById(R.id.wifi_signal);
+ mWifiDark = (ImageView) findViewById(R.id.wifi_signal_dark);
mAirplane = (ImageView) findViewById(R.id.airplane);
mNoSims = (ImageView) findViewById(R.id.no_sims);
+ mNoSimsDark = (ImageView) findViewById(R.id.no_sims_dark);
mWifiAirplaneSpacer = findViewById(R.id.wifi_airplane_spacer);
mWifiSignalSpacer = findViewById(R.id.wifi_signal_spacer);
mMobileSignalGroup = (LinearLayout) findViewById(R.id.mobile_signal_group);
@@ -121,6 +128,7 @@ public class SignalClusterView
}
apply();
+ applyIconTint();
}
@Override
@@ -187,6 +195,9 @@ public class SignalClusterView
for (int i = 0; i < n; i++) {
inflatePhoneState(subs.get(i).getSubscriptionId());
}
+ if (isAttachedToWindow()) {
+ applyIconTint();
+ }
}
private PhoneState getOrInflateState(int subId) {
@@ -217,7 +228,7 @@ public class SignalClusterView
}
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
// Standard group layout onPopulateAccessibilityEvent() implementations
// ignore content description, so populate manually
if (mWifiVisible && mWifiGroup != null && mWifiGroup.getContentDescription() != null)
@@ -225,7 +236,7 @@ public class SignalClusterView
for (PhoneState state : mPhoneStates) {
state.populateAccessibilityEvent(event);
}
- return super.dispatchPopulateAccessibilityEvent(event);
+ return super.dispatchPopulateAccessibilityEventInternal(event);
}
@Override
@@ -265,6 +276,7 @@ public class SignalClusterView
if (DEBUG) Log.d(TAG, String.format("vpn: %s", mVpnVisible ? "VISIBLE" : "GONE"));
if (mWifiVisible) {
mWifi.setImageResource(mWifiStrengthId);
+ mWifiDark.setImageResource(mWifiStrengthId);
mWifiGroup.setContentDescription(mWifiDescription);
mWifiGroup.setVisibility(View.VISIBLE);
} else {
@@ -309,12 +321,42 @@ public class SignalClusterView
}
mNoSims.setVisibility(mNoSimsVisible ? View.VISIBLE : View.GONE);
+ mNoSimsDark.setVisibility(mNoSimsVisible ? View.VISIBLE : View.GONE);
boolean anythingVisible = mNoSimsVisible || mWifiVisible || mIsAirplaneMode
|| anyMobileVisible || mVpnVisible;
setPaddingRelative(0, 0, anythingVisible ? mEndPadding : mEndPaddingNothingVisible, 0);
}
+ public void setIconTint(int tint, float darkIntensity) {
+ boolean changed = tint != mIconTint || darkIntensity != mDarkIntensity;
+ mIconTint = tint;
+ mDarkIntensity = darkIntensity;
+ if (changed && isAttachedToWindow()) {
+ applyIconTint();
+ }
+ }
+
+ private void applyIconTint() {
+ setTint(mVpn, mIconTint);
+ setTint(mAirplane, mIconTint);
+ applyDarkIntensity(mDarkIntensity, mNoSims, mNoSimsDark);
+ applyDarkIntensity(mDarkIntensity, mWifi, mWifiDark);
+ for (int i = 0; i < mPhoneStates.size(); i++) {
+ mPhoneStates.get(i).setIconTint(mIconTint, mDarkIntensity);
+ }
+ }
+
+ private void applyDarkIntensity(float darkIntensity, View lightIcon, View darkIcon) {
+ lightIcon.setAlpha(1 - darkIntensity);
+ darkIcon.setAlpha(darkIntensity);
+ }
+
+ private void setTint(ImageView v, int tint) {
+ v.setImageTintMode(PorterDuff.Mode.SRC_ATOP);
+ v.setImageTintList(ColorStateList.valueOf(tint));
+ }
+
private class PhoneState {
private final int mSubId;
private boolean mMobileVisible = false;
@@ -323,7 +365,7 @@ public class SignalClusterView
private String mMobileDescription, mMobileTypeDescription;
private ViewGroup mMobileGroup;
- private ImageView mMobile, mMobileType;
+ private ImageView mMobile, mMobileDark, mMobileType;
public PhoneState(int subId, Context context) {
ViewGroup root = (ViewGroup) LayoutInflater.from(context)
@@ -335,12 +377,14 @@ public class SignalClusterView
public void setViews(ViewGroup root) {
mMobileGroup = root;
mMobile = (ImageView) root.findViewById(R.id.mobile_signal);
+ mMobileDark = (ImageView) root.findViewById(R.id.mobile_signal_dark);
mMobileType = (ImageView) root.findViewById(R.id.mobile_type);
}
public boolean apply(boolean isSecondaryIcon) {
if (mMobileVisible && !mIsAirplaneMode) {
mMobile.setImageResource(mMobileStrengthId);
+ mMobileDark.setImageResource(mMobileStrengthId);
mMobileType.setImageResource(mMobileTypeId);
mMobileGroup.setContentDescription(mMobileTypeDescription
+ " " + mMobileDescription);
@@ -354,6 +398,8 @@ public class SignalClusterView
0, 0, 0);
mMobile.setPaddingRelative(mIsMobileTypeIconWide ? mWideTypeIconStartPadding : 0,
0, 0, 0);
+ mMobileDark.setPaddingRelative(mIsMobileTypeIconWide ? mWideTypeIconStartPadding : 0,
+ 0, 0, 0);
if (DEBUG) Log.d(TAG, String.format("mobile: %s sig=%d typ=%d",
(mMobileVisible ? "VISIBLE" : "GONE"), mMobileStrengthId, mMobileTypeId));
@@ -369,6 +415,11 @@ public class SignalClusterView
event.getText().add(mMobileGroup.getContentDescription());
}
}
+
+ public void setIconTint(int tint, float darkIntensity) {
+ applyDarkIntensity(darkIntensity, mMobile, mMobileDark);
+ setTint(mMobileType, tint);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 20dd3e7..e6847d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -55,7 +55,7 @@ public class StatusBarIconView extends AnimatedImageView {
mSlot = slot;
mNumberPain = new Paint();
mNumberPain.setTextAlign(Paint.Align.CENTER);
- mNumberPain.setColor(res.getColor(R.drawable.notification_number_text_color));
+ mNumberPain.setColor(context.getColor(R.drawable.notification_number_text_color));
mNumberPain.setAntiAlias(true);
setNotification(notification);
@@ -147,6 +147,9 @@ public class StatusBarIconView extends AnimatedImageView {
}
private boolean updateDrawable(boolean withClear) {
+ if (mIcon == null) {
+ return false;
+ }
Drawable drawable = getIcon(mIcon);
if (drawable == null) {
Log.w(TAG, "No icon for slot " + mSlot);
@@ -226,6 +229,12 @@ public class StatusBarIconView extends AnimatedImageView {
}
@Override
+ public void onRtlPropertiesChanged(int layoutDirection) {
+ super.onRtlPropertiesChanged(layoutDirection);
+ updateDrawable();
+ }
+
+ @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
index e89e15d..1601b83 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
@@ -145,12 +145,12 @@ public class BarTransitions {
mTransparent = 0x2f0000ff;
mWarning = 0xffff0000;
} else {
- mOpaque = res.getColor(R.color.system_bar_background_opaque);
- mSemiTransparent = res.getColor(R.color.system_bar_background_semi_transparent);
- mTransparent = res.getColor(R.color.system_bar_background_transparent);
- mWarning = res.getColor(com.android.internal.R.color.battery_saver_mode_color);
+ mOpaque = context.getColor(R.color.system_bar_background_opaque);
+ mSemiTransparent = context.getColor(R.color.system_bar_background_semi_transparent);
+ mTransparent = context.getColor(R.color.system_bar_background_transparent);
+ mWarning = context.getColor(com.android.internal.R.color.battery_saver_mode_color);
}
- mGradient = res.getDrawable(gradientResourceId);
+ mGradient = context.getDrawable(gradientResourceId);
mInterpolator = new LinearInterpolator();
}
@@ -160,7 +160,7 @@ public class BarTransitions {
}
@Override
- public void setColorFilter(ColorFilter cf) {
+ public void setColorFilter(ColorFilter colorFilter) {
// noop
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
index 6cb5bcc..44168bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
@@ -90,24 +90,12 @@ public class DemoStatusIcons extends LinearLayout implements DemoMode {
: 0;
updateSlot("alarm_clock", null, iconId);
}
- String sync = args.getString("sync");
- if (sync != null) {
- int iconId = sync.equals("show") ? R.drawable.stat_sys_sync
- : 0;
- updateSlot("sync_active", null, iconId);
- }
String tty = args.getString("tty");
if (tty != null) {
int iconId = tty.equals("show") ? R.drawable.stat_sys_tty_mode
: 0;
updateSlot("tty", null, iconId);
}
- String eri = args.getString("eri");
- if (eri != null) {
- int iconId = eri.equals("show") ? R.drawable.stat_sys_roaming_cdma_0
- : 0;
- updateSlot("cdma_eri", null, iconId);
- }
String mute = args.getString("mute");
if (mute != null) {
int iconId = mute.equals("show") ? android.R.drawable.stat_notify_call_mute
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 0c21b20..a247c8e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -336,7 +336,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
}
public void launchCamera() {
- mFlashlightController.killFlashlight();
Intent intent = getCameraIntent();
boolean wouldLaunchResolverActivity = PreviewInflater.wouldLaunchResolverActivity(
mContext, intent, mLockPatternUtils.getCurrentUser());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index d0fe32e..262d955 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -25,10 +25,9 @@ import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import com.android.internal.widget.LockPatternUtils;
-import com.android.keyguard.KeyguardViewBase;
+import com.android.keyguard.KeyguardHostView;
import com.android.keyguard.R;
import com.android.keyguard.ViewMediatorCallback;
-import com.android.systemui.keyguard.KeyguardViewMediator;
import static com.android.keyguard.KeyguardHostView.OnDismissAction;
import static com.android.keyguard.KeyguardSecurityModel.SecurityMode;
@@ -43,7 +42,7 @@ public class KeyguardBouncer {
private LockPatternUtils mLockPatternUtils;
private ViewGroup mContainer;
private StatusBarWindowManager mWindowManager;
- private KeyguardViewBase mKeyguardView;
+ private KeyguardHostView mKeyguardView;
private ViewGroup mRoot;
private boolean mShowingSoon;
private Choreographer mChoreographer = Choreographer.getInstance();
@@ -140,16 +139,6 @@ public class KeyguardBouncer {
}
}
- public long getUserActivityTimeout() {
- if (mKeyguardView != null) {
- long timeout = mKeyguardView.getUserActivityTimeout();
- if (timeout >= 0) {
- return timeout;
- }
- }
- return KeyguardViewMediator.AWAKE_INTERVAL_DEFAULT_MS;
- }
-
public boolean isShowing() {
return mShowingSoon || (mRoot != null && mRoot.getVisibility() == View.VISIBLE);
}
@@ -171,7 +160,7 @@ public class KeyguardBouncer {
private void inflateView() {
removeView();
mRoot = (ViewGroup) LayoutInflater.from(mContext).inflate(R.layout.keyguard_bouncer, null);
- mKeyguardView = (KeyguardViewBase) mRoot.findViewById(R.id.keyguard_host_view);
+ mKeyguardView = (KeyguardHostView) mRoot.findViewById(R.id.keyguard_host_view);
mKeyguardView.setLockPatternUtils(mLockPatternUtils);
mKeyguardView.setViewMediatorCallback(mCallback);
mContainer.addView(mRoot, mContainer.getChildCount());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java
index 7579039..076e5f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java
@@ -20,7 +20,6 @@ import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
-import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.WindowInsets;
@@ -47,7 +46,7 @@ public class KeyguardPreviewContainer extends FrameLayout {
}
@Override
- public void setColorFilter(ColorFilter cf) {
+ public void setColorFilter(ColorFilter colorFilter) {
// noop
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 40c9134..13b3898 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.phone;
-import android.animation.LayoutTransition;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.drawable.Drawable;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
index 7ec84da..a712d29 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
@@ -26,12 +26,9 @@ import android.view.animation.AccelerateInterpolator;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.R;
-import com.android.systemui.statusbar.policy.KeyButtonView;
public final class NavigationBarTransitions extends BarTransitions {
- private static final int CONTENT_FADE_DURATION = 200;
-
private final NavigationBarView mView;
private final IStatusBarService mBarService;
@@ -78,48 +75,11 @@ public final class NavigationBarTransitions extends BarTransitions {
}
private void applyMode(int mode, boolean animate, boolean force) {
- // apply to key buttons
- final float alpha = alphaForMode(mode);
- setKeyButtonViewQuiescentAlpha(mView.getHomeButton(), alpha, animate);
- setKeyButtonViewQuiescentAlpha(mView.getRecentsButton(), alpha, animate);
- setKeyButtonViewQuiescentAlpha(mView.getMenuButton(), alpha, animate);
- setKeyButtonViewQuiescentAlpha(mView.getImeSwitchButton(), alpha, animate);
-
- applyBackButtonQuiescentAlpha(mode, animate);
// apply to lights out
applyLightsOut(isLightsOut(mode), animate, force);
}
- private float alphaForMode(int mode) {
- final boolean isOpaque = mode == MODE_OPAQUE || mode == MODE_LIGHTS_OUT;
- return isOpaque ? KeyButtonView.DEFAULT_QUIESCENT_ALPHA : 1f;
- }
-
- public void applyBackButtonQuiescentAlpha(int mode, boolean animate) {
- float backAlpha = 0;
- backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getHomeButton());
- backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getRecentsButton());
- backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getMenuButton());
- backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getImeSwitchButton());
- if (backAlpha > 0) {
- setKeyButtonViewQuiescentAlpha(mView.getBackButton(), backAlpha, animate);
- }
- }
-
- private static float maxVisibleQuiescentAlpha(float max, View v) {
- if ((v instanceof KeyButtonView) && v.isShown()) {
- return Math.max(max, ((KeyButtonView)v).getQuiescentAlpha());
- }
- return max;
- }
-
- private void setKeyButtonViewQuiescentAlpha(View button, float alpha, boolean animate) {
- if (button instanceof KeyButtonView) {
- ((KeyButtonView) button).setQuiescentAlpha(alpha, animate);
- }
- }
-
private void applyLightsOut(boolean lightsOut, boolean animate, boolean force) {
if (!force && lightsOut == mLightsOut) return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 1e4dfb4..c62ad66 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -197,7 +197,7 @@ public class NavigationBarView extends LinearLayout {
mDelegateHelper.setDelegateView(view);
}
- public void setBar(BaseStatusBar phoneStatusBar) {
+ public void setBar(PhoneStatusBar phoneStatusBar) {
mTaskSwitchHelper.setBar(phoneStatusBar);
mDelegateHelper.setBar(phoneStatusBar);
}
@@ -263,8 +263,8 @@ public class NavigationBarView extends LinearLayout {
return mCurrentView.findViewById(R.id.back);
}
- public View getHomeButton() {
- return mCurrentView.findViewById(R.id.home);
+ public KeyButtonView getHomeButton() {
+ return (KeyButtonView) mCurrentView.findViewById(R.id.home);
}
public View getImeSwitchButton() {
@@ -369,8 +369,6 @@ public class NavigationBarView extends LinearLayout {
getBackButton() .setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE);
getHomeButton() .setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE);
getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
-
- mBarTransitions.applyBackButtonQuiescentAlpha(mBarTransitions.getMode(), true /*animate*/);
}
private boolean inLockTask() {
@@ -662,10 +660,6 @@ public class NavigationBarView extends LinearLayout {
+ " " + visibilityToString(button.getVisibility())
+ " alpha=" + button.getAlpha()
);
- if (button instanceof KeyButtonView) {
- pw.print(" drawingAlpha=" + ((KeyButtonView)button).getDrawingAlpha());
- pw.print(" quiescentAlpha=" + ((KeyButtonView)button).getQuiescentAlpha());
- }
}
pw.println();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
new file mode 100644
index 0000000..7072dcb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.app.Notification;
+import android.service.notification.StatusBarNotification;
+
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.StatusBarState;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A class to handle notifications and their corresponding groups.
+ */
+public class NotificationGroupManager {
+
+ private final HashMap<String, NotificationGroup> mGroupMap = new HashMap<>();
+ private OnGroupChangeListener mListener;
+ private int mBarState = -1;
+
+ public void setOnGroupChangeListener(OnGroupChangeListener listener) {
+ mListener = listener;
+ }
+
+ public boolean isGroupExpanded(StatusBarNotification sbn) {
+ NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
+ if (group == null) {
+ return false;
+ }
+ return group.expanded;
+ }
+
+ public void setGroupExpanded(StatusBarNotification sbn, boolean expanded) {
+ NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
+ if (group == null) {
+ return;
+ }
+ setGroupExpanded(group, expanded);
+ }
+
+ private void setGroupExpanded(NotificationGroup group, boolean expanded) {
+ group.expanded = expanded;
+ if (group.summary != null) {
+ mListener.onGroupExpansionChanged(group.summary.row, expanded);
+ }
+ }
+
+ public void onEntryRemoved(NotificationData.Entry removed) {
+ onEntryRemovedInternal(removed, removed.notification);
+ }
+
+ /**
+ * An entry was removed.
+ *
+ * @param removed the removed entry
+ * @param sbn the notification the entry has, which doesn't need to be the same as it's internal
+ * notification
+ */
+ private void onEntryRemovedInternal(NotificationData.Entry removed,
+ final StatusBarNotification sbn) {
+ Notification notif = sbn.getNotification();
+ String groupKey = sbn.getGroupKey();
+ final NotificationGroup group = mGroupMap.get(groupKey);
+ if (notif.isGroupSummary()) {
+ group.summary = null;
+ } else {
+ group.children.remove(removed);
+ }
+ if (group.children.isEmpty()) {
+ if (group.summary == null) {
+ mGroupMap.remove(groupKey);
+ } else {
+ if (group.expanded) {
+ // only the summary is left. Change it to unexpanded in a few ms. We do this to
+ // avoid raceconditions
+ removed.row.post(new Runnable() {
+ @Override
+ public void run() {
+ if (group.children.isEmpty()) {
+ setGroupExpanded(sbn, false);
+ }
+ }
+ });
+ } else {
+ group.summary.row.updateExpandButton();
+ }
+ }
+ }
+ }
+
+ public void onEntryAdded(NotificationData.Entry added) {
+ StatusBarNotification sbn = added.notification;
+ Notification notif = sbn.getNotification();
+ String groupKey = sbn.getGroupKey();
+ NotificationGroup group = mGroupMap.get(groupKey);
+ if (group == null) {
+ group = new NotificationGroup();
+ mGroupMap.put(groupKey, group);
+ }
+ if (notif.isGroupSummary()) {
+ group.summary = added;
+ group.expanded = added.row.areChildrenExpanded();
+ if (!group.children.isEmpty()) {
+ mListener.onGroupCreatedFromChildren(group);
+ }
+ } else {
+ group.children.add(added);
+ if (group.summary != null && group.children.size() == 1 && !group.expanded) {
+ group.summary.row.updateExpandButton();
+ }
+ }
+ }
+
+ public void onEntryUpdated(NotificationData.Entry entry,
+ StatusBarNotification oldNotification) {
+ if (mGroupMap.get(oldNotification.getGroupKey()) != null) {
+ onEntryRemovedInternal(entry, oldNotification);
+ }
+ onEntryAdded(entry);
+ }
+
+ public boolean isVisible(StatusBarNotification sbn) {
+ if (!sbn.getNotification().isGroupChild()) {
+ return true;
+ }
+ NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
+ if (group != null && group.expanded) {
+ return true;
+ }
+ return false;
+ }
+
+ public boolean hasGroupChildren(StatusBarNotification sbn) {
+ if (areGroupsProhibited()) {
+ return false;
+ }
+ if (!sbn.getNotification().isGroupSummary()) {
+ return false;
+ }
+ NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
+ if (group == null) {
+ return false;
+ }
+ return !group.children.isEmpty();
+ }
+
+ public void setStatusBarState(int newState) {
+ if (mBarState == newState) {
+ return;
+ }
+ boolean prohibitedBefore = areGroupsProhibited();
+ mBarState = newState;
+ boolean nowProhibited = areGroupsProhibited();
+ if (nowProhibited != prohibitedBefore) {
+ if (nowProhibited) {
+ for (NotificationGroup group : mGroupMap.values()) {
+ if (group.expanded) {
+ setGroupExpanded(group, false);
+ }
+ }
+ }
+ mListener.onGroupsProhibitedChanged();
+ }
+ }
+
+ private boolean areGroupsProhibited() {
+ return mBarState == StatusBarState.KEYGUARD;
+ }
+
+ /**
+ * @return whether a given notification is a child in a group which has a summary
+ */
+ public boolean isChildInGroupWithSummary(StatusBarNotification sbn) {
+ if (!sbn.getNotification().isGroupChild()) {
+ return false;
+ }
+ NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
+ if (group == null || group.summary == null) {
+ return false;
+ }
+ return true;
+ }
+
+ public ExpandableNotificationRow getGroupSummary(StatusBarNotification sbn) {
+ NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
+ return group == null ? null
+ : group.summary == null ? null
+ : group.summary.row;
+ }
+
+ public static class NotificationGroup {
+ public final HashSet<NotificationData.Entry> children = new HashSet<>();
+ public NotificationData.Entry summary;
+ public boolean expanded;
+ }
+
+ public interface OnGroupChangeListener {
+ /**
+ * The expansion of a group has changed.
+ *
+ * @param changedRow the row for which the expansion has changed, which is also the summary
+ * @param expanded a boolean indicating the new expanded state
+ */
+ void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded);
+
+ /**
+ * Children group policy has changed and children may no be prohibited or allowed.
+ */
+ void onGroupsProhibitedChanged();
+
+ /**
+ * A group of children just received a summary notification and should therefore become
+ * children of it.
+ *
+ * @param group the group created
+ */
+ void onGroupCreatedFromChildren(NotificationGroup group);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 0ae34bb..195da46 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -112,6 +112,7 @@ public class NotificationPanelView extends PanelView implements
private boolean mQsFullyExpanded;
private boolean mKeyguardShowing;
private boolean mDozing;
+ private boolean mDozingOnDown;
private int mStatusBarState;
private float mInitialHeightOnTouch;
private float mInitialTouchX;
@@ -163,7 +164,7 @@ public class NotificationPanelView extends PanelView implements
private Runnable mLaunchAnimationEndRunnable;
private boolean mOnlyAffordanceInThisMotion;
private boolean mKeyguardStatusViewAnimating;
- private boolean mHeaderAnimatingIn;
+ private boolean mHeaderAnimating;
private ObjectAnimator mQsContainerAnimator;
private ValueAnimator mQsSizeChangeAnimator;
@@ -222,9 +223,8 @@ public class NotificationPanelView extends PanelView implements
// recompute internal state when qspanel height changes
mQsContainer.addOnLayoutChangeListener(new OnLayoutChangeListener() {
@Override
- public void onLayoutChange(View v, int left, int top, int right,
- int bottom, int oldLeft, int oldTop, int oldRight,
- int oldBottom) {
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
final int height = bottom - top;
final int oldHeight = oldBottom - oldTop;
if (height != oldHeight) {
@@ -493,14 +493,14 @@ public class NotificationPanelView extends PanelView implements
}
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
event.getText().add(getKeyguardOrLockScreenString());
mLastAnnouncementWasQuickSettings = false;
return true;
}
- return super.dispatchPopulateAccessibilityEvent(event);
+ return super.dispatchPopulateAccessibilityEventInternal(event);
}
@Override
@@ -508,7 +508,7 @@ public class NotificationPanelView extends PanelView implements
if (mBlockTouches) {
return false;
}
- resetDownStates(event);
+ initDownStates(event);
int pointerIndex = event.findPointerIndex(mTrackingPointer);
if (pointerIndex < 0) {
pointerIndex = 0;
@@ -594,10 +594,11 @@ public class NotificationPanelView extends PanelView implements
&& x < stackScrollerX + mNotificationStackScroller.getWidth();
}
- private void resetDownStates(MotionEvent event) {
+ private void initDownStates(MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
mOnlyAffordanceInThisMotion = false;
mQsTouchAboveFalsingThreshold = mQsFullyExpanded;
+ mDozingOnDown = isDozing();
}
}
@@ -642,7 +643,7 @@ public class NotificationPanelView extends PanelView implements
if (mBlockTouches) {
return false;
}
- resetDownStates(event);
+ initDownStates(event);
if ((!mIsExpanding || mHintAnimationRunning)
&& !mQsExpanded
&& mStatusBar.getBarState() != StatusBarState.SHADE) {
@@ -873,26 +874,28 @@ public class NotificationPanelView extends PanelView implements
public void setBarState(int statusBarState, boolean keyguardFadingAway,
boolean goingToFullShade) {
- boolean keyguardShowing = statusBarState == StatusBarState.KEYGUARD
- || statusBarState == StatusBarState.SHADE_LOCKED;
- if (!mKeyguardShowing && keyguardShowing) {
- setQsTranslation(mQsExpansionHeight);
- mHeader.setTranslationY(0f);
- }
+ int oldState = mStatusBarState;
+ boolean keyguardShowing = statusBarState == StatusBarState.KEYGUARD;
setKeyguardStatusViewVisibility(statusBarState, keyguardFadingAway, goingToFullShade);
setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
- if (goingToFullShade) {
+
+ mStatusBarState = statusBarState;
+ mKeyguardShowing = keyguardShowing;
+
+ if (goingToFullShade || (oldState == StatusBarState.KEYGUARD
+ && statusBarState == StatusBarState.SHADE_LOCKED)) {
animateKeyguardStatusBarOut();
+ animateHeaderSlidingIn();
+ } else if (oldState == StatusBarState.SHADE_LOCKED
+ && statusBarState == StatusBarState.KEYGUARD) {
+ animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ animateHeaderSlidingOut();
} else {
mKeyguardStatusBar.setAlpha(1f);
mKeyguardStatusBar.setVisibility(keyguardShowing ? View.VISIBLE : View.INVISIBLE);
}
- mStatusBarState = statusBarState;
- mKeyguardShowing = keyguardShowing;
+
updateQsState();
- if (goingToFullShade) {
- animateHeaderSlidingIn();
- }
}
private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = new Runnable() {
@@ -914,7 +917,7 @@ public class NotificationPanelView extends PanelView implements
= new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- mHeaderAnimatingIn = false;
+ mHeaderAnimating = false;
mQsContainerAnimator = null;
mQsContainer.removeOnLayoutChangeListener(mQsContainerAnimatorUpdater);
}
@@ -942,10 +945,13 @@ public class NotificationPanelView extends PanelView implements
@Override
public boolean onPreDraw() {
getViewTreeObserver().removeOnPreDrawListener(this);
+ long delay = mStatusBarState == StatusBarState.SHADE_LOCKED
+ ? 0
+ : mStatusBar.calculateGoingToFullShadeDelay();
mHeader.setTranslationY(-mHeader.getCollapsedHeight() - mQsPeekHeight);
mHeader.animate()
.translationY(0f)
- .setStartDelay(mStatusBar.calculateGoingToFullShadeDelay())
+ .setStartDelay(delay)
.setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE)
.setInterpolator(mFastOutSlowInInterpolator)
.start();
@@ -954,7 +960,7 @@ public class NotificationPanelView extends PanelView implements
mQsContainer.getTranslationY(),
mHeader.getCollapsedHeight() + mQsPeekHeight - mQsContainer.getHeight()
- mQsContainer.getTop());
- mQsContainerAnimator.setStartDelay(mStatusBar.calculateGoingToFullShadeDelay());
+ mQsContainerAnimator.setStartDelay(delay);
mQsContainerAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE);
mQsContainerAnimator.setInterpolator(mFastOutSlowInInterpolator);
mQsContainerAnimator.addListener(mAnimateHeaderSlidingInListener);
@@ -963,11 +969,33 @@ public class NotificationPanelView extends PanelView implements
return true;
}
};
-
+
private void animateHeaderSlidingIn() {
- mHeaderAnimatingIn = true;
+ mHeaderAnimating = true;
getViewTreeObserver().addOnPreDrawListener(mStartHeaderSlidingIn);
+ }
+ private void animateHeaderSlidingOut() {
+ mHeaderAnimating = true;
+ mHeader.animate().y(-mHeader.getHeight())
+ .setStartDelay(0)
+ .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD)
+ .setInterpolator(mFastOutSlowInInterpolator)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mHeader.animate().setListener(null);
+ mHeaderAnimating = false;
+ updateQsState();
+ }
+ })
+ .start();
+ mQsContainer.animate()
+ .y(-mQsContainer.getHeight())
+ .setStartDelay(0)
+ .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD)
+ .setInterpolator(mFastOutSlowInInterpolator)
+ .start();
}
private final Runnable mAnimateKeyguardStatusBarInvisibleEndRunnable = new Runnable() {
@@ -982,8 +1010,12 @@ public class NotificationPanelView extends PanelView implements
private void animateKeyguardStatusBarOut() {
mKeyguardStatusBar.animate()
.alpha(0f)
- .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay())
- .setDuration(mStatusBar.getKeyguardFadingAwayDuration()/2)
+ .setStartDelay(mStatusBar.isKeyguardFadingAway()
+ ? mStatusBar.getKeyguardFadingAwayDelay()
+ : 0)
+ .setDuration(mStatusBar.isKeyguardFadingAway()
+ ? mStatusBar.getKeyguardFadingAwayDuration() / 2
+ : StackStateAnimator.ANIMATION_DURATION_STANDARD)
.setInterpolator(PhoneStatusBar.ALPHA_OUT)
.setUpdateListener(mStatusBarAnimateAlphaListener)
.withEndAction(mAnimateKeyguardStatusBarInvisibleEndRunnable)
@@ -998,13 +1030,13 @@ public class NotificationPanelView extends PanelView implements
}
};
- private void animateKeyguardStatusBarIn() {
+ private void animateKeyguardStatusBarIn(long duration) {
mKeyguardStatusBar.setVisibility(View.VISIBLE);
mKeyguardStatusBar.setAlpha(0f);
mKeyguardStatusBar.animate()
.alpha(1f)
.setStartDelay(0)
- .setDuration(DOZE_ANIMATION_DURATION)
+ .setDuration(duration)
.setInterpolator(mDozeAnimationInterpolator)
.setUpdateListener(mStatusBarAnimateAlphaListener)
.start();
@@ -1084,9 +1116,12 @@ public class NotificationPanelView extends PanelView implements
}
private void updateQsState() {
- boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling;
- mHeader.setVisibility((mQsExpanded || !mKeyguardShowing) ? View.VISIBLE : View.INVISIBLE);
- mHeader.setExpanded(mKeyguardShowing || (mQsExpanded && !mStackScrollerOverscrolling));
+ boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling || mHeaderAnimating;
+ mHeader.setVisibility((mQsExpanded || !mKeyguardShowing || mHeaderAnimating)
+ ? View.VISIBLE
+ : View.INVISIBLE);
+ mHeader.setExpanded((mKeyguardShowing && !mHeaderAnimating)
+ || (mQsExpanded && !mStackScrollerOverscrolling));
mNotificationStackScroller.setScrollingEnabled(
mStatusBarState != StatusBarState.KEYGUARD && (!mQsExpanded
|| mQsExpansionFromOverscroll));
@@ -1124,6 +1159,10 @@ public class NotificationPanelView extends PanelView implements
if (mKeyguardShowing) {
updateHeaderKeyguard();
}
+ if (mStatusBarState == StatusBarState.SHADE_LOCKED
+ || mStatusBarState == StatusBarState.KEYGUARD) {
+ updateKeyguardBottomAreaAlpha();
+ }
if (mStatusBarState == StatusBarState.SHADE && mQsExpanded
&& !mStackScrollerOverscrolling && mQsScrimEnabled) {
mQsNavbarScrim.setAlpha(getQsExpansionFraction());
@@ -1164,10 +1203,10 @@ public class NotificationPanelView extends PanelView implements
}
private void setQsTranslation(float height) {
- if (!mHeaderAnimatingIn) {
+ if (!mHeaderAnimating) {
mQsContainer.setY(height - mQsContainer.getDesiredHeight() + getHeaderTranslation());
}
- if (mKeyguardShowing) {
+ if (mKeyguardShowing && !mHeaderAnimating) {
mHeader.setY(interpolate(getQsExpansionFraction(), -mHeader.getHeight(), 0));
}
}
@@ -1479,8 +1518,7 @@ public class NotificationPanelView extends PanelView implements
* Hides the header when notifications are colliding with it.
*/
private void updateHeader() {
- if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
- || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
+ if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
updateHeaderKeyguard();
} else {
updateHeaderShade();
@@ -1489,15 +1527,14 @@ public class NotificationPanelView extends PanelView implements
}
private void updateHeaderShade() {
- if (!mHeaderAnimatingIn) {
+ if (!mHeaderAnimating) {
mHeader.setTranslationY(getHeaderTranslation());
}
setQsTranslation(mQsExpansionHeight);
}
private float getHeaderTranslation() {
- if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
- || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
+ if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
return 0;
}
if (mNotificationStackScroller.getNotGoneChildCount() == 0) {
@@ -1510,30 +1547,42 @@ public class NotificationPanelView extends PanelView implements
return Math.min(0, mNotificationStackScroller.getTranslationY()) / HEADER_RUBBERBAND_FACTOR;
}
- private void updateHeaderKeyguard() {
- float alphaNotifications;
+ /**
+ * @return the alpha to be used to fade out the contents on Keyguard (status bar, bottom area)
+ * during swiping up
+ */
+ private float getKeyguardContentsAlpha() {
+ float alpha;
if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
// When on Keyguard, we hide the header as soon as the top card of the notification
// stack scroller is close enough (collision distance) to the bottom of the header.
- alphaNotifications = getNotificationsTopY()
+ alpha = getNotificationsTopY()
/
(mKeyguardStatusBar.getHeight() + mNotificationsHeaderCollideDistance);
} else {
// In SHADE_LOCKED, the top card is already really close to the header. Hide it as
// soon as we start translating the stack.
- alphaNotifications = getNotificationsTopY() / mKeyguardStatusBar.getHeight();
+ alpha = getNotificationsTopY() / mKeyguardStatusBar.getHeight();
}
- alphaNotifications = MathUtils.constrain(alphaNotifications, 0, 1);
- alphaNotifications = (float) Math.pow(alphaNotifications, 0.75);
+ alpha = MathUtils.constrain(alpha, 0, 1);
+ alpha = (float) Math.pow(alpha, 0.75);
+ return alpha;
+ }
+
+ private void updateHeaderKeyguard() {
float alphaQsExpansion = 1 - Math.min(1, getQsExpansionFraction() * 2);
- mKeyguardStatusBar.setAlpha(Math.min(alphaNotifications, alphaQsExpansion)
+ mKeyguardStatusBar.setAlpha(Math.min(getKeyguardContentsAlpha(), alphaQsExpansion)
* mKeyguardStatusBarAnimateAlpha);
- mKeyguardBottomArea.setAlpha(Math.min(1 - getQsExpansionFraction(), alphaNotifications));
setQsTranslation(mQsExpansionHeight);
}
+ private void updateKeyguardBottomAreaAlpha() {
+ mKeyguardBottomArea.setAlpha(
+ Math.min(getKeyguardContentsAlpha(), 1 - getQsExpansionFraction()));
+ }
+
private float getNotificationsTopY() {
if (mNotificationStackScroller.getNotGoneChildCount() == 0) {
return getExpandedHeight();
@@ -1631,7 +1680,7 @@ public class NotificationPanelView extends PanelView implements
}
@Override
- public void onHeightChanged(ExpandableView view) {
+ public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
// Block update if we are in quick settings and just the top padding changed
// (i.e. view == null).
@@ -1764,6 +1813,7 @@ public class NotificationPanelView extends PanelView implements
mSecureCameraLaunchManager.onSwipingStarted();
requestDisallowInterceptTouchEvent(true);
mOnlyAffordanceInThisMotion = true;
+ mQsTracking = false;
}
@Override
@@ -1905,7 +1955,7 @@ public class NotificationPanelView extends PanelView implements
mKeyguardBottomArea.setVisibility(View.VISIBLE);
mKeyguardStatusBar.setVisibility(View.VISIBLE);
if (animate) {
- animateKeyguardStatusBarIn();
+ animateKeyguardStatusBarIn(DOZE_ANIMATION_DURATION);
mKeyguardBottomArea.startFinishDozeAnimation();
}
}
@@ -1955,6 +2005,32 @@ public class NotificationPanelView extends PanelView implements
onEmptySpaceClick(x);
}
+ protected boolean onMiddleClicked() {
+ switch (mStatusBar.getBarState()) {
+ case StatusBarState.KEYGUARD:
+ if (!mDozingOnDown) {
+ EventLogTags.writeSysuiLockscreenGesture(
+ EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_TAP_UNLOCK_HINT,
+ 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
+ startUnlockHintAnimation();
+ }
+ return true;
+ case StatusBarState.SHADE_LOCKED:
+ if (!mQsExpanded) {
+ mStatusBar.goToKeyguard();
+ }
+ return true;
+ case StatusBarState.SHADE:
+
+ // This gets called in the middle of the touch handling, where the state is still
+ // that we are tracking the panel. Collapse the panel after this is done.
+ post(mPostCollapseRunnable);
+ return false;
+ default:
+ return true;
+ }
+ }
+
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
index e7b0c4c..a03c297 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
@@ -18,7 +18,6 @@ package com.android.systemui.statusbar.phone;
import android.content.Context;
import android.graphics.Canvas;
-import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewStub;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index d86ccee..4bbf690 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -73,6 +73,7 @@ public abstract class PanelView extends FrameLayout {
private boolean mTouchAboveFalsingThreshold;
private int mUnlockFalsingThreshold;
private boolean mTouchStartedInEmptyArea;
+ private boolean mMotionAborted;
private ValueAnimator mHeightAnimator;
private ObjectAnimator mPeekAnimator;
@@ -100,7 +101,6 @@ 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() {
@@ -209,7 +209,8 @@ public abstract class PanelView extends FrameLayout {
@Override
public boolean onTouchEvent(MotionEvent event) {
- if (mInstantExpanding || mTouchDisabled) {
+ if (mInstantExpanding || mTouchDisabled
+ || (mMotionAborted && event.getActionMasked() != MotionEvent.ACTION_DOWN)) {
return false;
}
@@ -227,8 +228,8 @@ public abstract class PanelView extends FrameLayout {
pointerIndex = 0;
mTrackingPointer = event.getPointerId(pointerIndex);
}
- final float y = event.getY(pointerIndex);
final float x = event.getX(pointerIndex);
+ final float y = event.getY(pointerIndex);
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
mGestureWaitForTouchSlop = mExpandedHeight == 0f;
@@ -245,9 +246,9 @@ public abstract class PanelView extends FrameLayout {
mPanelClosedOnDown = mExpandedHeight == 0.0f;
mHasLayoutedSinceDown = false;
mUpdateFlingOnLayout = false;
+ mMotionAborted = false;
mPeekTouching = mPanelClosedOnDown;
mTouchAboveFalsingThreshold = false;
- mDozingOnDown = isDozing();
if (mVelocityTracker == null) {
initVelocityTracker();
}
@@ -278,7 +279,13 @@ public abstract class PanelView extends FrameLayout {
mInitialTouchX = newX;
}
break;
-
+ case MotionEvent.ACTION_POINTER_DOWN:
+ if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
+ mMotionAborted = true;
+ endMotionEvent(event, x, y, true /* forceCancel */);
+ return false;
+ }
+ break;
case MotionEvent.ACTION_MOVE:
float h = y - mInitialTouchY;
@@ -320,26 +327,35 @@ public abstract class PanelView extends FrameLayout {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
- mTrackingPointer = -1;
trackMovement(event);
- if ((mTracking && mTouchSlopExceeded)
- || Math.abs(x - mInitialTouchX) > mTouchSlop
- || Math.abs(y - mInitialTouchY) > mTouchSlop
- || event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
- float vel = 0f;
- float vectorVel = 0f;
- if (mVelocityTracker != null) {
- mVelocityTracker.computeCurrentVelocity(1000);
- vel = mVelocityTracker.getYVelocity();
- vectorVel = (float) Math.hypot(
- mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
- }
- boolean expand = flingExpands(vel, vectorVel)
- || event.getActionMasked() == MotionEvent.ACTION_CANCEL;
- onTrackingStopped(expand);
- DozeLog.traceFling(expand, mTouchAboveFalsingThreshold,
- mStatusBar.isFalsingThresholdNeeded(),
- mStatusBar.isScreenOnComingFromTouch());
+ endMotionEvent(event, x, y, false /* forceCancel */);
+ break;
+ }
+ return !waitForTouchSlop || mTracking;
+ }
+
+ private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) {
+ mTrackingPointer = -1;
+ if ((mTracking && mTouchSlopExceeded)
+ || Math.abs(x - mInitialTouchX) > mTouchSlop
+ || Math.abs(y - mInitialTouchY) > mTouchSlop
+ || event.getActionMasked() == MotionEvent.ACTION_CANCEL
+ || forceCancel) {
+ float vel = 0f;
+ float vectorVel = 0f;
+ if (mVelocityTracker != null) {
+ mVelocityTracker.computeCurrentVelocity(1000);
+ vel = mVelocityTracker.getYVelocity();
+ vectorVel = (float) Math.hypot(
+ mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
+ }
+ boolean expand = flingExpands(vel, vectorVel)
+ || event.getActionMasked() == MotionEvent.ACTION_CANCEL
+ || forceCancel;
+ 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();
@@ -349,24 +365,21 @@ public abstract class PanelView extends FrameLayout {
EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_UP_UNLOCK,
heightDp, velocityDp);
}
- fling(vel, expand);
- mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown;
- if (mUpdateFlingOnLayout) {
- mUpdateFlingVelocity = vel;
- }
- } else {
- boolean expands = onEmptySpaceClick(mInitialTouchX);
- onTrackingStopped(expands);
- }
+ fling(vel, expand);
+ mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown;
+ if (mUpdateFlingOnLayout) {
+ mUpdateFlingVelocity = vel;
+ }
+ } else {
+ boolean expands = onEmptySpaceClick(mInitialTouchX);
+ onTrackingStopped(expands);
+ }
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }
- mPeekTouching = false;
- break;
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
}
- return !waitForTouchSlop || mTracking;
+ mPeekTouching = false;
}
private int getFalsingThreshold() {
@@ -391,7 +404,8 @@ public abstract class PanelView extends FrameLayout {
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
- if (mInstantExpanding) {
+ if (mInstantExpanding
+ || (mMotionAborted && event.getActionMasked() != MotionEvent.ACTION_DOWN)) {
return false;
}
@@ -427,11 +441,11 @@ public abstract class PanelView extends FrameLayout {
mTouchStartedInEmptyArea = !isInContentBounds(x, y);
mTouchSlopExceeded = false;
mJustPeeked = false;
+ mMotionAborted = false;
mPanelClosedOnDown = mExpandedHeight == 0.0f;
mHasLayoutedSinceDown = false;
mUpdateFlingOnLayout = false;
mTouchAboveFalsingThreshold = false;
- mDozingOnDown = isDozing();
initVelocityTracker();
trackMovement(event);
break;
@@ -445,7 +459,15 @@ public abstract class PanelView extends FrameLayout {
mInitialTouchY = event.getY(newIndex);
}
break;
-
+ case MotionEvent.ACTION_POINTER_DOWN:
+ if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
+ mMotionAborted = true;
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+ break;
case MotionEvent.ACTION_MOVE:
final float h = y - mInitialTouchY;
trackMovement(event);
@@ -464,6 +486,10 @@ public abstract class PanelView extends FrameLayout {
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
break;
}
return false;
@@ -942,35 +968,14 @@ public abstract class PanelView extends FrameLayout {
}
}
- private final Runnable mPostCollapseRunnable = new Runnable() {
+ protected final Runnable mPostCollapseRunnable = new Runnable() {
@Override
public void run() {
collapse(false /* delayed */);
}
};
- private boolean onMiddleClicked() {
- switch (mStatusBar.getBarState()) {
- case StatusBarState.KEYGUARD:
- if (!mDozingOnDown) {
- EventLogTags.writeSysuiLockscreenGesture(
- EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_TAP_UNLOCK_HINT,
- 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
- startUnlockHintAnimation();
- }
- return true;
- case StatusBarState.SHADE_LOCKED:
- mStatusBar.goToKeyguard();
- return true;
- case StatusBarState.SHADE:
- // This gets called in the middle of the touch handling, where the state is still
- // that we are tracking the panel. Collapse the panel after this is done.
- post(mPostCollapseRunnable);
- return false;
- default:
- return true;
- }
- }
+ protected abstract boolean onMiddleClicked();
protected abstract void onEdgeClicked(boolean right);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index f227107..ad78f6a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -32,7 +32,6 @@ import static com.android.systemui.statusbar.phone.BarTransitions.MODE_WARNING;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.TimeInterpolator;
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
@@ -82,14 +81,12 @@ import android.provider.Settings;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.StatusBarNotification;
-import android.text.TextUtils;
import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
import android.view.Display;
import android.view.Gravity;
-import android.view.HardwareCanvas;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
@@ -97,23 +94,16 @@ import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
-import android.view.ViewPropertyAnimator;
import android.view.ViewStub;
-import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
-import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
-import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.PathInterpolator;
-import android.widget.FrameLayout;
import android.widget.ImageView;
-import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.internal.statusbar.StatusBarIcon;
@@ -123,14 +113,14 @@ 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;
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.qs.QSPanel;
-import com.android.systemui.recent.ScreenPinningRequest;
+import com.android.systemui.recents.ScreenPinningRequest;
import com.android.systemui.statusbar.ActivatableNotificationView;
+import com.android.systemui.assist.AssistGestureManager;
import com.android.systemui.statusbar.BackDropView;
import com.android.systemui.statusbar.BaseStatusBar;
import com.android.systemui.statusbar.CommandQueue;
@@ -172,8 +162,7 @@ import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout.OnChildLocationsChangedListener;
-import com.android.systemui.statusbar.stack.StackScrollAlgorithm;
-import com.android.systemui.statusbar.stack.StackScrollState.ViewState;
+import com.android.systemui.statusbar.stack.StackViewState;
import com.android.systemui.volume.VolumeComponent;
import java.io.FileDescriptor;
@@ -181,6 +170,7 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -199,9 +189,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
// additional instrumentation for testing purposes; intended to be left on during development
public static final boolean CHATTY = DEBUG;
- public static final String ACTION_STATUSBAR_START
- = "com.android.internal.policy.statusbar.START";
-
public static final boolean SHOW_LOCKSCREEN_MEDIA_ARTWORK = true;
private static final int MSG_OPEN_NOTIFICATION_PANEL = 1000;
@@ -215,9 +202,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
private static final boolean CLOSE_PANEL_WHEN_EMPTIED = true;
- private static final int NOTIFICATION_PRIORITY_MULTIPLIER = 10; // see NotificationManagerService
- private static final int HIDE_ICONS_BELOW_SCORE = Notification.PRIORITY_LOW * NOTIFICATION_PRIORITY_MULTIPLIER;
-
private static final int STATUS_OR_NAV_TRANSIENT =
View.STATUS_BAR_TRANSIENT | View.NAVIGATION_BAR_TRANSIENT;
private static final long AUTOHIDE_TIMEOUT_MS = 3000;
@@ -264,8 +248,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
AccessibilityController mAccessibilityController;
int mNaturalBarHeight = -1;
- int mIconSize = -1;
- int mIconHPadding = -1;
+
Display mDisplay;
Point mCurrentDisplaySize = new Point();
@@ -281,34 +264,14 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
int mPixelFormat;
Object mQueueLock = new Object();
- // viewgroup containing the normal contents of the statusbar
- LinearLayout mStatusBarContents;
-
- // right-hand icons
- LinearLayout mSystemIconArea;
- LinearLayout mSystemIcons;
-
- // left-hand icons
- LinearLayout mStatusIcons;
- LinearLayout mStatusIconsKeyguard;
-
- // the icons themselves
- IconMerger mNotificationIcons;
- View mNotificationIconArea;
-
- // [+>
- View mMoreIcon;
+ StatusBarIconController mIconController;
// expanded notifications
NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window
View mExpandedContents;
- int mNotificationPanelGravity;
- int mNotificationPanelMarginBottomPx;
- float mNotificationPanelMinHeightFrac;
TextView mNotificationPanelDebugText;
// settings
- View mFlipSettingsView;
private QSPanel mQSPanel;
// top bar
@@ -325,16 +288,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
int mKeyguardMaxNotificationCount;
- // carrier/wifi label
- private TextView mCarrierLabel;
- private boolean mCarrierLabelVisible = false;
- private int mCarrierLabelHeight;
- private int mStatusBarHeaderHeight;
-
- private boolean mShowCarrierInPanel = false;
-
- // position
- int[] mPositionTmp = new int[2];
boolean mExpandedVisible;
private int mNavigationBarWindowState = WINDOW_STATE_SHOWING;
@@ -342,12 +295,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
// the tracker view
int mTrackingPosition; // the position of the top of the tracking view.
- // ticker
- private boolean mTickerEnabled;
- private Ticker mTicker;
- private View mTickerView;
- private boolean mTicking;
-
// Tracking finger for opening/closing.
int mEdgeBorder; // corresponds to R.dimen.status_bar_edge_ignore
boolean mTracking;
@@ -374,6 +321,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
private int mNavigationIconHints = 0;
private HandlerThread mHandlerThread;
+ private AssistGestureManager mAssistGestureManager;
+
// ensure quick settings is disabled until the current user makes it through the setup wizard
private boolean mUserSetup = false;
private ContentObserver mUserSetupObserver = new ContentObserver(new Handler()) {
@@ -411,7 +360,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
if (!mUseHeadsUp) {
Log.d(TAG, "dismissing any existing heads up notification on disable event");
setHeadsUpVisibility(false);
- mHeadsUpNotificationView.release();
+ mHeadsUpNotificationView.releaseImmediately();
removeHeadsUpView();
} else {
addHeadsUpView();
@@ -442,7 +391,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
private boolean mDozing;
private boolean mScrimSrcModeEnabled;
- private Interpolator mLinearOutSlowIn;
private Interpolator mLinearInterpolator = new LinearInterpolator();
private Interpolator mBackdropInterpolator = new AccelerateDecelerateInterpolator();
public static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
@@ -498,10 +446,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
// Fingerprint (as computed by getLoggingFingerprint() of the last logged state.
private int mLastLoggedStateFingerprint;
- private static final int VISIBLE_LOCATIONS = ViewState.LOCATION_FIRST_CARD
- | ViewState.LOCATION_TOP_STACK_PEEKING
- | ViewState.LOCATION_MAIN_AREA
- | ViewState.LOCATION_BOTTOM_STACK_PEEKING;
+ private static final int VISIBLE_LOCATIONS = StackViewState.LOCATION_FIRST_CARD
+ | StackViewState.LOCATION_TOP_STACK_PEEKING
+ | StackViewState.LOCATION_MAIN_AREA
+ | StackViewState.LOCATION_BOTTOM_STACK_PEEKING;
private final OnChildLocationsChangedListener mNotificationLocationsChangedListener =
new OnChildLocationsChangedListener() {
@@ -577,6 +525,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
goToLockedShade(null);
}
};
+ private HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>> mTmpChildOrderMap
+ = new HashMap<>();
@Override
public void start() {
@@ -585,6 +535,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
updateDisplaySize();
mScrimSrcModeEnabled = mContext.getResources().getBoolean(
R.bool.config_status_bar_scrim_behind_use_src);
+
super.start(); // calls createAndAddWindows()
mMediaSessionManager
@@ -633,8 +584,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
updateDisplaySize(); // populates mDisplayMetrics
updateResources();
- mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size);
-
mStatusBarWindow = (StatusBarWindowView) View.inflate(context,
R.layout.super_status_bar, null);
mStatusBarWindow.mService = this;
@@ -662,7 +611,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
if (!ActivityManager.isHighEndGfx()) {
mStatusBarWindow.setBackground(null);
- mNotificationPanel.setBackground(new FastColorDrawable(context.getResources().getColor(
+ mNotificationPanel.setBackground(new FastColorDrawable(context.getColor(
R.color.notification_panel_solid_background)));
}
if (ENABLE_HEADS_UP) {
@@ -692,8 +641,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
new NavigationBarView.OnVerticalChangedListener() {
@Override
public void onVerticalChanged(boolean isVertical) {
- if (mSearchPanelView != null) {
- mSearchPanelView.setHorizontal(isVertical);
+ if (mAssistGestureManager != null) {
+ mAssistGestureManager.onConfigurationChanged();
}
mNotificationPanel.setQsScrimEnabled(!isVertical);
}
@@ -712,19 +661,12 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
// figure out which pixel-format to use for the status bar.
mPixelFormat = PixelFormat.OPAQUE;
- mSystemIconArea = (LinearLayout) mStatusBarView.findViewById(R.id.system_icon_area);
- mSystemIcons = (LinearLayout) mStatusBarView.findViewById(R.id.system_icons);
- mStatusIcons = (LinearLayout)mStatusBarView.findViewById(R.id.statusIcons);
- mNotificationIconArea = mStatusBarView.findViewById(R.id.notification_icon_area_inner);
- mNotificationIcons = (IconMerger)mStatusBarView.findViewById(R.id.notificationIcons);
- mMoreIcon = mStatusBarView.findViewById(R.id.moreIcon);
- mNotificationIcons.setOverflowIndicator(mMoreIcon);
- mStatusBarContents = (LinearLayout)mStatusBarView.findViewById(R.id.status_bar_contents);
-
mStackScroller = (NotificationStackScrollLayout) mStatusBarWindow.findViewById(
R.id.notification_stack_scroller);
mStackScroller.setLongPressListener(getNotificationLongClicker());
mStackScroller.setPhoneStatusBar(this);
+ mStackScroller.setGroupManager(mGroupManager);
+ mGroupManager.setOnGroupChangeListener(mStackScroller);
mKeyguardIconOverflowContainer =
(NotificationOverflowContainer) LayoutInflater.from(mContext).inflate(
@@ -764,7 +706,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mHeader = (StatusBarHeaderView) mStatusBarWindow.findViewById(R.id.header);
mHeader.setActivityStarter(this);
mKeyguardStatusBar = (KeyguardStatusBarView) mStatusBarWindow.findViewById(R.id.keyguard_header);
- mStatusIconsKeyguard = (LinearLayout) mKeyguardStatusBar.findViewById(R.id.statusIcons);
mKeyguardStatusView = mStatusBarWindow.findViewById(R.id.keyguard_status_view);
mKeyguardBottomArea =
(KeyguardBottomAreaView) mStatusBarWindow.findViewById(R.id.keyguard_bottom_area);
@@ -774,23 +715,14 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
R.id.keyguard_indication_text));
mKeyguardBottomArea.setKeyguardIndicationController(mKeyguardIndicationController);
- mTickerEnabled = res.getBoolean(R.bool.enable_ticker);
- if (mTickerEnabled) {
- final ViewStub tickerStub = (ViewStub) mStatusBarView.findViewById(R.id.ticker_stub);
- if (tickerStub != null) {
- mTickerView = tickerStub.inflate();
- mTicker = new MyTicker(context, mStatusBarView);
-
- TickerView tickerView = (TickerView) mStatusBarView.findViewById(R.id.tickerText);
- tickerView.mTicker = mTicker;
- }
- }
-
mEdgeBorder = res.getDimensionPixelSize(R.dimen.status_bar_edge_ignore);
// set the inital view visibility
setAreThereNotifications();
+ mIconController = new StatusBarIconController(
+ mContext, mStatusBarView, mKeyguardStatusBar, this);
+
// Background thread for any controllers that need it.
mHandlerThread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
mHandlerThread.start();
@@ -849,27 +781,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
});
}
- mCarrierLabel = (TextView)mStatusBarWindow.findViewById(R.id.carrier_label);
- mShowCarrierInPanel = (mCarrierLabel != null);
- if (DEBUG) Log.v(TAG, "carrierlabel=" + mCarrierLabel + " show=" + mShowCarrierInPanel);
- if (mShowCarrierInPanel) {
- mCarrierLabel.setVisibility(mCarrierLabelVisible ? View.VISIBLE : View.INVISIBLE);
-
- mNetworkController.addCarrierLabel(new NetworkControllerImpl.CarrierLabelListener() {
- @Override
- public void setCarrierLabel(String label) {
- mCarrierLabel.setText(label);
- if (mNetworkController.hasMobileDataFeature()) {
- if (TextUtils.isEmpty(label)) {
- mCarrierLabel.setVisibility(View.GONE);
- } else {
- mCarrierLabel.setVisibility(View.VISIBLE);
- }
- }
- }
- });
- }
-
mFlashlightController = new FlashlightController(mContext);
mKeyguardBottomArea.setFlashlightController(mFlashlightController);
mKeyguardBottomArea.setPhoneStatusBar(this);
@@ -922,6 +833,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mBroadcastReceiver.onReceive(mContext,
new Intent(pm.isScreenOn() ? Intent.ACTION_SCREEN_ON : Intent.ACTION_SCREEN_OFF));
+ mAssistGestureManager = new AssistGestureManager(this, context);
+
// receive broadcasts
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
@@ -936,7 +849,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
// listen for USER_SETUP_COMPLETE setting (per-user)
resetUserSetupObserver();
- startGlyphRasterizeHack();
return mStatusBarView;
}
@@ -948,9 +860,20 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
final ArrayList<View> viewsToHide = new ArrayList<View>(numChildren);
for (int i = 0; i < numChildren; i++) {
final View child = mStackScroller.getChildAt(i);
- if (mStackScroller.canChildBeDismissed(child)) {
- if (child.getVisibility() == View.VISIBLE) {
- viewsToHide.add(child);
+ if (child instanceof ExpandableNotificationRow) {
+ if (mStackScroller.canChildBeDismissed(child)) {
+ if (child.getVisibility() == View.VISIBLE) {
+ viewsToHide.add(child);
+ }
+ }
+ ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ List<ExpandableNotificationRow> children = row.getNotificationChildren();
+ if (row.areChildrenExpanded() && children != null) {
+ for (ExpandableNotificationRow childRow : children) {
+ if (childRow.getVisibility() == View.VISIBLE) {
+ viewsToHide.add(childRow);
+ }
+ }
}
}
}
@@ -1007,30 +930,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
}
- /**
- * Hack to improve glyph rasterization for scaled text views.
- */
- private void startGlyphRasterizeHack() {
- mStatusBarView.getViewTreeObserver().addOnPreDrawListener(
- new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- if (mDrawCount == 1) {
- mStatusBarView.getViewTreeObserver().removeOnPreDrawListener(this);
- HardwareCanvas.setProperty("extraRasterBucket",
- Float.toString(StackScrollAlgorithm.DIMMED_SCALE));
- HardwareCanvas.setProperty("extraRasterBucket", Float.toString(
- mContext.getResources().getDimensionPixelSize(
- R.dimen.qs_time_collapsed_size)
- / mContext.getResources().getDimensionPixelSize(
- R.dimen.qs_time_expanded_size)));
- }
- mDrawCount++;
- return true;
- }
- });
- }
-
@Override
protected void setZenMode(int mode) {
super.setZenMode(mode);
@@ -1055,60 +954,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
return mStatusBarWindow;
}
- @Override
- protected WindowManager.LayoutParams getSearchLayoutParams(LayoutParams layoutParams) {
- boolean opaque = false;
- WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
- LayoutParams.MATCH_PARENT,
- LayoutParams.MATCH_PARENT,
- WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
- WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
- | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
- (opaque ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT));
- if (ActivityManager.isHighEndGfx()) {
- lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
- }
- lp.gravity = Gravity.BOTTOM | Gravity.START;
- lp.setTitle("SearchPanel");
- lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
- | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
- return lp;
- }
-
- @Override
- protected void updateSearchPanel() {
- super.updateSearchPanel();
- if (mNavigationBarView != null) {
- mNavigationBarView.setDelegateView(mSearchPanelView);
- }
- }
-
- @Override
- public void showSearchPanel() {
- super.showSearchPanel();
- mHandler.removeCallbacks(mShowSearchPanel);
-
- // we want to freeze the sysui state wherever it is
- mSearchPanelView.setSystemUiVisibility(mSystemUiVisibility);
-
- if (mNavigationBarView != null) {
- WindowManager.LayoutParams lp =
- (android.view.WindowManager.LayoutParams) mNavigationBarView.getLayoutParams();
- lp.flags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
- mWindowManager.updateViewLayout(mNavigationBarView, lp);
- }
- }
-
- @Override
- public void hideSearchPanel() {
- super.hideSearchPanel();
- if (mNavigationBarView != null) {
- WindowManager.LayoutParams lp =
- (android.view.WindowManager.LayoutParams) mNavigationBarView.getLayoutParams();
- lp.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
- mWindowManager.updateViewLayout(mNavigationBarView, lp);
- }
+ public void invokeAssistGesture(boolean vibrate) {
+ mHandler.removeCallbacks(mInvokeAssist);
+ mAssistGestureManager.onGestureInvoked(vibrate);
}
public int getStatusBarHeight() {
@@ -1138,30 +986,33 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
};
private int mShowSearchHoldoff = 0;
- private Runnable mShowSearchPanel = new Runnable() {
+ private Runnable mInvokeAssist = new Runnable() {
public void run() {
- showSearchPanel();
+ invokeAssistGesture(true /* vibrate */);
awakenDreams();
+ if (mNavigationBarView != null) {
+ mNavigationBarView.getHomeButton().abortCurrentGesture();
+ }
}
};
View.OnTouchListener mHomeActionListener = new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
- switch(event.getAction()) {
+ switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
- if (!shouldDisableNavbarGestures()) {
- mHandler.removeCallbacks(mShowSearchPanel);
- mHandler.postDelayed(mShowSearchPanel, mShowSearchHoldoff);
- }
- break;
+ if (!shouldDisableNavbarGestures()) {
+ mHandler.removeCallbacks(mInvokeAssist);
+ mHandler.postDelayed(mInvokeAssist, mShowSearchHoldoff);
+ }
+ break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- mHandler.removeCallbacks(mShowSearchPanel);
- awakenDreams();
- break;
- }
- return false;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mHandler.removeCallbacks(mInvokeAssist);
+ awakenDreams();
+ break;
+ }
+ return false;
}
};
@@ -1185,7 +1036,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mNavigationBarView.getBackButton().setLongClickable(true);
mNavigationBarView.getBackButton().setOnLongClickListener(mLongPressBackRecentsListener);
mNavigationBarView.getHomeButton().setOnTouchListener(mHomeActionListener);
- updateSearchPanel();
+ mAssistGestureManager.onConfigurationChanged();
}
// For small-screen devices (read: phones) that lack hardware navigation buttons
@@ -1258,49 +1109,17 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mWindowManager.removeView(mHeadsUpNotificationView);
}
- public void refreshAllStatusBarIcons() {
- refreshAllIconsForLayout(mStatusIcons);
- refreshAllIconsForLayout(mStatusIconsKeyguard);
- refreshAllIconsForLayout(mNotificationIcons);
- }
-
- private void refreshAllIconsForLayout(LinearLayout ll) {
- final int count = ll.getChildCount();
- for (int n = 0; n < count; n++) {
- View child = ll.getChildAt(n);
- if (child instanceof StatusBarIconView) {
- ((StatusBarIconView) child).updateDrawable();
- }
- }
- }
-
public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) {
- if (SPEW) Log.d(TAG, "addIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex
- + " icon=" + icon);
- StatusBarIconView view = new StatusBarIconView(mContext, slot, null);
- view.set(icon);
- mStatusIcons.addView(view, viewIndex, new LinearLayout.LayoutParams(
- LayoutParams.WRAP_CONTENT, mIconSize));
- view = new StatusBarIconView(mContext, slot, null);
- view.set(icon);
- mStatusIconsKeyguard.addView(view, viewIndex, new LinearLayout.LayoutParams(
- LayoutParams.WRAP_CONTENT, mIconSize));
+ mIconController.addSystemIcon(slot, index, viewIndex, icon);
}
public void updateIcon(String slot, int index, int viewIndex,
StatusBarIcon old, StatusBarIcon icon) {
- if (SPEW) Log.d(TAG, "updateIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex
- + " old=" + old + " icon=" + icon);
- StatusBarIconView view = (StatusBarIconView) mStatusIcons.getChildAt(viewIndex);
- view.set(icon);
- view = (StatusBarIconView) mStatusIconsKeyguard.getChildAt(viewIndex);
- view.set(icon);
+ mIconController.updateSystemIcon(slot, index, viewIndex, old, icon);
}
public void removeIcon(String slot, int index, int viewIndex) {
- if (SPEW) Log.d(TAG, "removeIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex);
- mStatusIcons.removeViewAt(viewIndex);
- mStatusIconsKeyguard.removeViewAt(viewIndex);
+ mIconController.removeSystemIcon(slot, index, viewIndex);
}
public UserHandle getCurrentUserHandle() {
@@ -1308,11 +1127,19 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
@Override
- public void addNotification(StatusBarNotification notification, RankingMap ranking) {
+ public void addNotification(StatusBarNotification notification, RankingMap ranking,
+ Entry oldEntry) {
if (DEBUG) Log.d(TAG, "addNotification key=" + notification.getKey());
if (mUseHeadsUp && shouldInterrupt(notification)) {
if (DEBUG) Log.d(TAG, "launching notification in heads up mode");
- Entry interruptionCandidate = new Entry(notification, null);
+ Entry interruptionCandidate = oldEntry;
+ if (interruptionCandidate == null) {
+ final StatusBarIconView iconView = createIcon(notification);
+ if (iconView == null) {
+ return;
+ }
+ interruptionCandidate = new Entry(notification, iconView);
+ }
ViewGroup holder = mHeadsUpNotificationView.getHolder();
if (inflateViewsForHeadsUp(interruptionCandidate, holder)) {
// 1. Populate mHeadsUpNotificationView
@@ -1341,58 +1168,22 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
notification.getNotification().fullScreenIntent.send();
} catch (PendingIntent.CanceledException e) {
}
- } else {
- // usual case: status bar visible & not immersive
-
- // show the ticker if there isn't already a heads up
- if (mHeadsUpNotificationView.getEntry() == null) {
- tick(notification, true);
- }
}
addNotificationViews(shadeEntry, ranking);
// Recalculate the position of the sliding windows and the titles.
setAreThereNotifications();
- updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
}
- public void displayNotificationFromHeadsUp(StatusBarNotification notification) {
- NotificationData.Entry shadeEntry = createNotificationViews(notification);
- if (shadeEntry == null) {
- return;
- }
+ public void displayNotificationFromHeadsUp(Entry shadeEntry) {
+
+ // The notification comes from the headsup, let's inflate the normal layout again
+ inflateViews(shadeEntry, mStackScroller);
shadeEntry.setInterruption();
+ shadeEntry.row.setHeadsUp(false);
addNotificationViews(shadeEntry, null);
// Recalculate the position of the sliding windows and the titles.
setAreThereNotifications();
- updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
- }
-
- @Override
- public void resetHeadsUpDecayTimer() {
- mHandler.removeMessages(MSG_DECAY_HEADS_UP);
- if (mUseHeadsUp && mHeadsUpNotificationDecay > 0
- && mHeadsUpNotificationView.isClearable()) {
- mHandler.sendEmptyMessageDelayed(MSG_DECAY_HEADS_UP, mHeadsUpNotificationDecay);
- }
- }
-
- @Override
- public void scheduleHeadsUpOpen() {
- mHandler.removeMessages(MSG_SHOW_HEADS_UP);
- mHandler.sendEmptyMessage(MSG_SHOW_HEADS_UP);
- }
-
- @Override
- public void scheduleHeadsUpClose() {
- mHandler.removeMessages(MSG_HIDE_HEADS_UP);
- mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP);
- }
-
- @Override
- public void scheduleHeadsUpEscalation() {
- mHandler.removeMessages(MSG_ESCALATE_HEADS_UP);
- mHandler.sendEmptyMessage(MSG_ESCALATE_HEADS_UP);
}
@Override
@@ -1403,23 +1194,14 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
@Override
public void removeNotification(String key, RankingMap ranking) {
- if (ENABLE_HEADS_UP && mHeadsUpNotificationView.getEntry() != null
- && key.equals(mHeadsUpNotificationView.getEntry().notification.getKey())) {
- mHeadsUpNotificationView.clear();
+ if (ENABLE_HEADS_UP) {
+ mHeadsUpNotificationView.removeNotification(key);
}
StatusBarNotification old = removeNotificationViews(key, ranking);
if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
if (old != null) {
- // Cancel the ticker if it's still running
- if (mTickerEnabled) {
- mTicker.removeEntry(old);
- }
-
- // Recalculate the position of the sliding windows and the titles.
- updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
-
if (CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications()
&& !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()) {
if (mState == StatusBarState.SHADE) {
@@ -1437,7 +1219,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
if (mNavigationBarView != null) {
mNavigationBarView.setLayoutDirection(layoutDirection);
}
- refreshAllStatusBarIcons();
}
private void updateShowSearchHoldoff() {
@@ -1483,10 +1264,23 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
ent.row.setShowingLegacyBackground(true);
}
}
- toShow.add(ent.row);
+ if (mGroupManager.isChildInGroupWithSummary(ent.row.getStatusBarNotification())) {
+ ExpandableNotificationRow summary = mGroupManager.getGroupSummary(
+ ent.row.getStatusBarNotification());
+ List<ExpandableNotificationRow> orderedChildren =
+ mTmpChildOrderMap.get(summary);
+ if (orderedChildren == null) {
+ orderedChildren = new ArrayList<>();
+ mTmpChildOrderMap.put(summary, orderedChildren);
+ }
+ orderedChildren.add(ent.row);
+ } else {
+ toShow.add(ent.row);
+ }
+
}
- ArrayList<View> toRemove = new ArrayList<View>();
+ ArrayList<View> toRemove = new ArrayList<>();
for (int i=0; i< mStackScroller.getChildCount(); i++) {
View child = mStackScroller.getChildAt(i);
if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) {
@@ -1515,17 +1309,22 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
continue;
}
- if (child == toShow.get(j)) {
- // Everything is well, advance both lists.
- j++;
- continue;
+ ExpandableNotificationRow targetChild = toShow.get(j);
+ if (child != targetChild) {
+ // Oops, wrong notification at this position. Put the right one
+ // here and advance both lists.
+ mStackScroller.changeViewPosition(targetChild, i);
}
-
- // Oops, wrong notification at this position. Put the right one
- // here and advance both lists.
- mStackScroller.changeViewPosition(toShow.get(j), i);
j++;
+
}
+
+ // lets handle the child notifications now
+ updateNotificationShadeForChildren();
+
+ // clear the map again for the next usage
+ mTmpChildOrderMap.clear();
+
updateRowStates();
updateSpeedbump();
updateClearAll();
@@ -1540,6 +1339,52 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mShadeUpdates.check();
}
+ private void updateNotificationShadeForChildren() {
+ ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
+ boolean orderChanged = false;
+ for (int i = 0; i < mStackScroller.getChildCount(); i++) {
+ View view = mStackScroller.getChildAt(i);
+ if (!(view instanceof ExpandableNotificationRow)) {
+ // We don't care about non-notification views.
+ continue;
+ }
+
+ ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
+ List<ExpandableNotificationRow> children = parent.getNotificationChildren();
+ List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent);
+
+ // lets first remove all undesired children
+ if (children != null) {
+ toRemove.clear();
+ for (ExpandableNotificationRow childRow : children) {
+ if (orderedChildren == null || !orderedChildren.contains(childRow)) {
+ toRemove.add(childRow);
+ }
+ }
+ for (ExpandableNotificationRow remove : toRemove) {
+ parent.removeChildNotification(remove);
+ mStackScroller.notifyGroupChildRemoved(remove);
+ }
+ }
+
+ // We now add all the children which are not in there already
+ for (int childIndex = 0; orderedChildren != null && childIndex < orderedChildren.size();
+ childIndex++) {
+ ExpandableNotificationRow childView = orderedChildren.get(childIndex);
+ if (children == null || !children.contains(childView)) {
+ parent.addChildNotification(childView, childIndex);
+ mStackScroller.notifyGroupChildAdded(childView);
+ }
+ }
+
+ // Finally after removing and adding has been beformed we can apply the order.
+ orderChanged |= parent.applyChildOrder(orderedChildren);
+ }
+ if (orderChanged) {
+ mStackScroller.generateChildOrderChangedEvent();
+ }
+ }
+
private boolean packageHasVisibilityOverride(String key) {
return mNotificationData.getVisibilityOverride(key)
!= NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE;
@@ -1566,6 +1411,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
final int N = activeNotifications.size();
for (int i = 0; i < N; i++) {
Entry entry = activeNotifications.get(i);
+ boolean isChild = !isTopLevelChild(entry);
+ if (isChild) {
+ continue;
+ }
if (entry.row.getVisibility() != View.GONE &&
mNotificationData.isAmbient(entry.key)) {
speedbumpIndex = currentIndex;
@@ -1576,71 +1425,16 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mStackScroller.updateSpeedBumpIndex(speedbumpIndex);
}
+ public static boolean isTopLevelChild(Entry entry) {
+ return entry.row.getParent() instanceof NotificationStackScrollLayout;
+ }
+
@Override
protected void updateNotifications() {
- // TODO: Move this into updateNotificationIcons()?
- if (mNotificationIcons == null) return;
-
mNotificationData.filterAndSort();
updateNotificationShade();
- updateNotificationIcons();
- }
-
- private void updateNotificationIcons() {
- final LinearLayout.LayoutParams params
- = new LinearLayout.LayoutParams(mIconSize + 2*mIconHPadding, mNaturalBarHeight);
-
- ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
- final int N = activeNotifications.size();
- ArrayList<StatusBarIconView> toShow = new ArrayList<>(N);
-
- // Filter out notifications with low scores.
- for (int i = 0; i < N; i++) {
- Entry ent = activeNotifications.get(i);
- if (ent.notification.getScore() < HIDE_ICONS_BELOW_SCORE &&
- !NotificationData.showNotificationEvenIfUnprovisioned(ent.notification)) {
- continue;
- }
- toShow.add(ent.icon);
- }
-
- if (DEBUG) {
- Log.d(TAG, "refreshing icons: " + toShow.size() +
- " notifications, mNotificationIcons=" + mNotificationIcons);
- }
-
- ArrayList<View> toRemove = new ArrayList<View>();
- for (int i=0; i<mNotificationIcons.getChildCount(); i++) {
- View child = mNotificationIcons.getChildAt(i);
- if (!toShow.contains(child)) {
- toRemove.add(child);
- }
- }
-
- final int toRemoveCount = toRemove.size();
- for (int i = 0; i < toRemoveCount; i++) {
- mNotificationIcons.removeView(toRemove.get(i));
- }
-
- for (int i=0; i<toShow.size(); i++) {
- View v = toShow.get(i);
- if (v.getParent() == null) {
- mNotificationIcons.addView(v, i, params);
- }
- }
-
- // Resort notification icons
- final int childCount = mNotificationIcons.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View actual = mNotificationIcons.getChildAt(i);
- StatusBarIconView expected = toShow.get(i);
- if (actual == expected) {
- continue;
- }
- mNotificationIcons.removeView(expected);
- mNotificationIcons.addView(expected, i);
- }
+ mIconController.updateNotificationIcons(mNotificationData);
}
@Override
@@ -1649,53 +1443,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mNotificationPanel.notifyVisibleChildrenChanged();
}
- protected void updateCarrierLabelVisibility(boolean force) {
- // TODO: Handle this for the notification stack scroller as well
- if (!mShowCarrierInPanel) return;
- // The idea here is to only show the carrier label when there is enough room to see it,
- // i.e. when there aren't enough notifications to fill the panel.
- if (SPEW) {
- Log.d(TAG, String.format("stackScrollerh=%d scrollh=%d carrierh=%d",
- mStackScroller.getHeight(), mStackScroller.getHeight(),
- mCarrierLabelHeight));
- }
-
- // Emergency calls only is shown in the expanded header now.
- final boolean emergencyCallsShownElsewhere = true;
- final boolean makeVisible =
- !(emergencyCallsShownElsewhere && mNetworkController.isEmergencyOnly())
- && mStackScroller.getHeight() < (mNotificationPanel.getHeight()
- - mCarrierLabelHeight - mStatusBarHeaderHeight)
- && mStackScroller.getVisibility() == View.VISIBLE
- && mState != StatusBarState.KEYGUARD;
-
- if (force || mCarrierLabelVisible != makeVisible) {
- mCarrierLabelVisible = makeVisible;
- if (DEBUG) {
- Log.d(TAG, "making carrier label " + (makeVisible?"visible":"invisible"));
- }
- mCarrierLabel.animate().cancel();
- if (makeVisible) {
- mCarrierLabel.setVisibility(View.VISIBLE);
- }
- mCarrierLabel.animate()
- .alpha(makeVisible ? 1f : 0f)
- //.setStartDelay(makeVisible ? 500 : 0)
- //.setDuration(makeVisible ? 750 : 100)
- .setDuration(150)
- .setListener(makeVisible ? null : new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (!mCarrierLabelVisible) { // race
- mCarrierLabel.setVisibility(View.INVISIBLE);
- mCarrierLabel.setAlpha(0f);
- }
- }
- })
- .start();
- }
- }
-
@Override
protected void setAreThereNotifications() {
@@ -1728,8 +1475,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
findAndUpdateMediaNotifications();
-
- updateCarrierLabelVisibility(false);
}
public void findAndUpdateMediaNotifications() {
@@ -1973,14 +1718,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
}
- public void showClock(boolean show) {
- if (mStatusBarView == null) return;
- View clock = mStatusBarView.findViewById(R.id.clock);
- if (clock != null) {
- clock.setVisibility(show ? View.VISIBLE : View.GONE);
- }
- }
-
private int adjustDisableFlags(int state) {
if (!mLaunchTransitionFadingAway
&& (mExpandedVisible || mBouncerShowing || mWaitingForKeyguardExit)) {
@@ -2029,17 +1766,16 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
Log.d(TAG, flagdbg.toString());
if ((diff & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) {
- mSystemIconArea.animate().cancel();
if ((state & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) {
- animateStatusBarHide(mSystemIconArea, animate);
+ mIconController.hideSystemIconArea(animate);
} else {
- animateStatusBarShow(mSystemIconArea, animate);
+ mIconController.showSystemIconArea(animate);
}
}
if ((diff & StatusBarManager.DISABLE_CLOCK) != 0) {
- boolean show = (state & StatusBarManager.DISABLE_CLOCK) == 0;
- showClock(show);
+ boolean visible = (state & StatusBarManager.DISABLE_CLOCK) == 0;
+ mIconController.setClockVisibility(visible);
}
if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) {
if ((state & StatusBarManager.DISABLE_EXPAND) != 0) {
@@ -2063,12 +1799,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
if ((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
- if (mTicking) {
- haltTicker();
- }
- animateStatusBarHide(mNotificationIconArea, animate);
+ mIconController.hideNotificationIconArea(animate);
} else {
- animateStatusBarShow(mNotificationIconArea, animate);
+ mIconController.showNotificationIconArea(animate);
}
}
@@ -2079,60 +1812,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
}
- /**
- * Animates {@code v}, a view that is part of the status bar, out.
- */
- private void animateStatusBarHide(final View v, boolean animate) {
- v.animate().cancel();
- if (!animate) {
- v.setAlpha(0f);
- v.setVisibility(View.INVISIBLE);
- return;
- }
- v.animate()
- .alpha(0f)
- .setDuration(160)
- .setStartDelay(0)
- .setInterpolator(ALPHA_OUT)
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- v.setVisibility(View.INVISIBLE);
- }
- });
- }
-
- /**
- * Animates {@code v}, a view that is part of the status bar, in.
- */
- private void animateStatusBarShow(View v, boolean animate) {
- v.animate().cancel();
- v.setVisibility(View.VISIBLE);
- if (!animate) {
- v.setAlpha(1f);
- return;
- }
- v.animate()
- .alpha(1f)
- .setDuration(320)
- .setInterpolator(ALPHA_IN)
- .setStartDelay(50)
-
- // We need to clean up any pending end action from animateStatusBarHide if we call
- // both hide and show in the same frame before the animation actually gets started.
- // cancel() doesn't really remove the end action.
- .withEndAction(null);
-
- // Synchronize the motion with the Keyguard fading if necessary.
- if (mKeyguardFadingAway) {
- v.animate()
- .setDuration(mKeyguardFadingAwayDuration)
- .setInterpolator(mLinearOutSlowIn)
- .setStartDelay(mKeyguardFadingAwayDelay)
- .start();
- }
- }
-
@Override
protected BaseStatusBar.H createHandler() {
return new PhoneStatusBar.H();
@@ -2143,10 +1822,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
startActivityDismissingKeyguard(intent, false, dismissShade);
}
- public ScrimController getScrimController() {
- return mScrimController;
- }
-
public void setQsExpanded(boolean expanded) {
mStatusBarWindowManager.setQsExpanded(expanded);
}
@@ -2213,16 +1888,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
case MSG_SHOW_HEADS_UP:
setHeadsUpVisibility(true);
break;
- case MSG_DECAY_HEADS_UP:
- mHeadsUpNotificationView.release();
- setHeadsUpVisibility(false);
- break;
- case MSG_HIDE_HEADS_UP:
- mHeadsUpNotificationView.release();
- setHeadsUpVisibility(false);
- break;
case MSG_ESCALATE_HEADS_UP:
escalateHeadsUp();
+ case MSG_HIDE_HEADS_UP:
+ mHeadsUpNotificationView.releaseImmediately();
setHeadsUpVisibility(false);
break;
case MSG_LAUNCH_TRANSITION_TIMEOUT:
@@ -2232,11 +1901,41 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
}
+ @Override
+ public void scheduleHeadsUpDecay(long delay) {
+ mHandler.removeMessages(MSG_HIDE_HEADS_UP);
+ if (mHeadsUpNotificationView.isClearable()) {
+ mHandler.sendEmptyMessageDelayed(MSG_HIDE_HEADS_UP, delay);
+ }
+ }
+
+ @Override
+ public void scheduleHeadsUpOpen() {
+ mHandler.removeMessages(MSG_HIDE_HEADS_UP);
+ mHandler.removeMessages(MSG_SHOW_HEADS_UP);
+ mHandler.sendEmptyMessage(MSG_SHOW_HEADS_UP);
+ }
+
+ @Override
+ public void scheduleHeadsUpClose() {
+ mHandler.removeMessages(MSG_HIDE_HEADS_UP);
+ if (mHeadsUpNotificationView.getVisibility() != View.GONE) {
+ mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP);
+ }
+ }
+
+ @Override
+ public void scheduleHeadsUpEscalation() {
+ mHandler.removeMessages(MSG_HIDE_HEADS_UP);
+ mHandler.removeMessages(MSG_ESCALATE_HEADS_UP);
+ mHandler.sendEmptyMessage(MSG_ESCALATE_HEADS_UP);
+ }
+
/** if the interrupting notification had a fullscreen intent, fire it now. */
private void escalateHeadsUp() {
if (mHeadsUpNotificationView.getEntry() != null) {
final StatusBarNotification sbn = mHeadsUpNotificationView.getEntry().notification;
- mHeadsUpNotificationView.release();
+ mHeadsUpNotificationView.releaseImmediately();
final Notification notification = sbn.getNotification();
if (notification.fullScreenIntent != null) {
if (DEBUG)
@@ -2251,14 +1950,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
}
- View.OnFocusChangeListener mFocusChangeListener = new View.OnFocusChangeListener() {
- public void onFocusChange(View v, boolean hasFocus) {
- // Because 'v' is a ViewGroup, all its children will be (un)selected
- // too, which allows marqueeing to work.
- v.setSelected(hasFocus);
- }
- };
-
boolean panelsEnabled() {
return (mDisabled & StatusBarManager.DISABLE_EXPAND) == 0;
}
@@ -2273,10 +1964,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
if (mNavigationBarView != null)
mNavigationBarView.setSlippery(true);
- updateCarrierLabelVisibility(true);
-
- updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
-
// Expand the window to encompass the full screen in anticipation of the drag.
// This is only possible to do atomically because the status bar is at the top of the screen!
mStatusBarWindowManager.setStatusBarExpanded(true);
@@ -2326,11 +2013,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
}
- if ((flags & CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL) == 0) {
- mHandler.removeMessages(MSG_CLOSE_SEARCH_PANEL);
- mHandler.sendEmptyMessage(MSG_CLOSE_SEARCH_PANEL);
- }
-
if (mStatusBarWindow != null) {
// release focus immediately to kick off focus change transition
mStatusBarWindowManager.setStatusBarFocusable(false);
@@ -2348,50 +2030,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mPostCollapseRunnables.clear();
}
- public ViewPropertyAnimator setVisibilityWhenDone(
- final ViewPropertyAnimator a, final View v, final int vis) {
- a.setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- v.setVisibility(vis);
- a.setListener(null); // oneshot
- }
- });
- return a;
- }
-
- public Animator setVisibilityWhenDone(
- final Animator a, final View v, final int vis) {
- a.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- v.setVisibility(vis);
- }
- });
- return a;
- }
-
- public Animator interpolator(TimeInterpolator ti, Animator a) {
- a.setInterpolator(ti);
- return a;
- }
-
- public Animator startDelay(int d, Animator a) {
- a.setStartDelay(d);
- return a;
- }
-
- public Animator start(Animator a) {
- a.start();
- return a;
- }
-
- final TimeInterpolator mAccelerateInterpolator = new AccelerateInterpolator();
- final TimeInterpolator mDecelerateInterpolator = new DecelerateInterpolator();
- final int FLIP_DURATION_OUT = 125;
- final int FLIP_DURATION_IN = 225;
- final int FLIP_DURATION = (FLIP_DURATION_IN + FLIP_DURATION_OUT);
-
Animator mScrollViewAnim, mClearButtonAnim;
@Override
@@ -2439,9 +2077,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mStatusBarView.collapseAllPanels(/*animate=*/ false);
// reset things to their proper state
- if (mScrollViewAnim != null) mScrollViewAnim.cancel();
- if (mClearButtonAnim != null) mClearButtonAnim.cancel();
-
mStackScroller.setVisibility(View.VISIBLE);
mNotificationPanel.setVisibility(View.GONE);
@@ -2587,9 +2222,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
final boolean lightsOut = (vis & View.SYSTEM_UI_FLAG_LOW_PROFILE) != 0;
if (lightsOut) {
animateCollapsePanels();
- if (mTicking) {
- haltTicker();
- }
}
setAreThereNotifications();
@@ -2634,6 +2266,14 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mSystemUiVisibility &= ~View.NAVIGATION_BAR_UNHIDE;
}
+ if ((diff & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0 || sbModeChanged) {
+ boolean isTransparentBar = (mStatusBarMode == MODE_TRANSPARENT
+ || mStatusBarMode == MODE_LIGHTS_OUT_TRANSPARENT);
+ boolean allowLight = isTransparentBar && !mBatteryController.isPowerSave();
+ boolean light = (vis & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0;
+
+ mIconController.setIconsDark(allowLight && light);
+ }
// restore the recents bit
if (wasRecentsVisible) {
mSystemUiVisibility |= View.RECENT_APPS_VISIBLE;
@@ -2805,90 +2445,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
setNavigationIconHints(flags);
}
- @Override
- protected void tick(StatusBarNotification n, boolean firstTime) {
- if (!mTickerEnabled) return;
-
- // no ticking in lights-out mode
- if (!areLightsOn()) return;
-
- // no ticking in Setup
- if (!isDeviceProvisioned()) return;
-
- // not for you
- if (!isNotificationForCurrentProfiles(n)) return;
-
- // Show the ticker if one is requested. Also don't do this
- // until status bar window is attached to the window manager,
- // because... well, what's the point otherwise? And trying to
- // run a ticker without being attached will crash!
- if (n.getNotification().tickerText != null && mStatusBarWindow != null
- && mStatusBarWindow.getWindowToken() != null) {
- if (0 == (mDisabled & (StatusBarManager.DISABLE_NOTIFICATION_ICONS
- | StatusBarManager.DISABLE_NOTIFICATION_TICKER))) {
- mTicker.addEntry(n);
- }
- }
- }
-
- private class MyTicker extends Ticker {
- MyTicker(Context context, View sb) {
- super(context, sb);
- if (!mTickerEnabled) {
- Log.w(TAG, "MyTicker instantiated with mTickerEnabled=false", new Throwable());
- }
- }
-
- @Override
- public void tickerStarting() {
- if (!mTickerEnabled) return;
- mTicking = true;
- mStatusBarContents.setVisibility(View.GONE);
- mTickerView.setVisibility(View.VISIBLE);
- mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.push_up_in, null));
- mStatusBarContents.startAnimation(loadAnim(com.android.internal.R.anim.push_up_out, null));
- }
-
- @Override
- public void tickerDone() {
- if (!mTickerEnabled) return;
- mStatusBarContents.setVisibility(View.VISIBLE);
- mTickerView.setVisibility(View.GONE);
- mStatusBarContents.startAnimation(loadAnim(com.android.internal.R.anim.push_down_in, null));
- mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.push_down_out,
- mTickingDoneListener));
- }
-
- public void tickerHalting() {
- if (!mTickerEnabled) return;
- if (mStatusBarContents.getVisibility() != View.VISIBLE) {
- mStatusBarContents.setVisibility(View.VISIBLE);
- mStatusBarContents
- .startAnimation(loadAnim(com.android.internal.R.anim.fade_in, null));
- }
- mTickerView.setVisibility(View.GONE);
- // we do not animate the ticker away at this point, just get rid of it (b/6992707)
- }
- }
-
- Animation.AnimationListener mTickingDoneListener = new Animation.AnimationListener() {;
- public void onAnimationEnd(Animation animation) {
- mTicking = false;
- }
- public void onAnimationRepeat(Animation animation) {
- }
- public void onAnimationStart(Animation animation) {
- }
- };
-
- private Animation loadAnim(int id, Animation.AnimationListener listener) {
- Animation anim = AnimationUtils.loadAnimation(mContext, id);
- if (listener != null) {
- anim.setAnimationListener(listener);
- }
- return anim;
- }
-
public static String viewInfo(View v) {
return "[(" + v.getLeft() + "," + v.getTop() + ")(" + v.getRight() + "," + v.getBottom()
+ ") " + v.getWidth() + "x" + v.getHeight() + "]";
@@ -2899,11 +2455,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
pw.println("Current Status Bar state:");
pw.println(" mExpandedVisible=" + mExpandedVisible
+ ", mTrackingPosition=" + mTrackingPosition);
- pw.println(" mTickerEnabled=" + mTickerEnabled);
- if (mTickerEnabled) {
- pw.println(" mTicking=" + mTicking);
- pw.println(" mTickerView: " + viewInfo(mTickerView));
- }
pw.println(" mTracking=" + mTracking);
pw.println(" mDisplayMetrics=" + mDisplayMetrics);
pw.println(" mStackScroller: " + viewInfo(mStackScroller));
@@ -2972,12 +2523,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mNotificationData.dump(pw, " ");
}
- int N = mStatusIcons.getChildCount();
- pw.println(" system icons: " + N);
- for (int i=0; i<N; i++) {
- StatusBarIconView ic = (StatusBarIconView) mStatusIcons.getChildAt(i);
- pw.println(" [" + i + "] icon=" + ic);
- }
+ mIconController.dump(pw);
if (false) {
pw.println("see the logcat for a dump of the views we have created.");
@@ -3020,6 +2566,12 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
if (mSecurityController != null) {
mSecurityController.dump(fd, pw, args);
}
+ if (mHeadsUpNotificationView != null) {
+ mHeadsUpNotificationView.dump(fd, pw, args);
+ } else {
+ pw.println(" mHeadsUpNotificationView: null");
+ }
+
pw.println("SharedPreferences:");
for (Map.Entry<String, ?> entry : mContext.getSharedPreferences(mContext.getPackageName(),
Context.MODE_PRIVATE).getAll().entrySet()) {
@@ -3049,25 +2601,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
}
- static final float saturate(float a) {
- return a < 0f ? 0f : (a > 1f ? 1f : a);
- }
-
- @Override
- public void updateExpandedViewPos(int thingy) {
- if (SPEW) Log.v(TAG, "updateExpandedViewPos");
-
- // on larger devices, the notification panel is propped open a bit
- mNotificationPanel.setMinimumHeight(
- (int)(mNotificationPanelMinHeightFrac * mCurrentDisplaySize.y));
-
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mNotificationPanel.getLayoutParams();
- lp.gravity = mNotificationPanelGravity;
- mNotificationPanel.setLayoutParams(lp);
-
- updateCarrierLabelVisibility(false);
- }
-
// called by makeStatusbar and also by PhoneStatusBarView
void updateDisplaySize() {
mDisplay.getMetrics(mDisplayMetrics);
@@ -3195,11 +2728,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
updateDisplaySize(); // populates mDisplayMetrics
updateResources();
- updateClockSize();
repositionNavigationBar();
- updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
updateShowSearchHoldoff();
updateRowStates();
+ mIconController.updateResources();
mScreenPinningRequest.onConfigurationChanged();
}
@@ -3225,8 +2757,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mUserSetupObserver.onChange(false);
mContext.getContentResolver().registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE), true,
- mUserSetupObserver,
- mCurrentUserId);
+ mUserSetupObserver, mCurrentUserId);
}
private void setHeadsUpVisibility(boolean vis) {
@@ -3238,10 +2769,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mHeadsUpNotificationView.setVisibility(vis ? View.VISIBLE : View.GONE);
}
- public void onHeadsUpDismissed() {
- mHeadsUpNotificationView.dismiss();
- }
-
/**
* Reload some of our resources when the configuration changes.
*
@@ -3256,8 +2783,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
loadDimens();
- mLinearOutSlowIn = AnimationUtils.loadInterpolator(
- mContext, android.R.interpolator.linear_out_slow_in);
if (mNotificationPanel != null) {
mNotificationPanel.updateResources();
@@ -3270,47 +2795,14 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
}
- private void updateClockSize() {
- if (mStatusBarView == null) return;
- TextView clock = (TextView) mStatusBarView.findViewById(R.id.clock);
- if (clock != null) {
- FontSizeUtils.updateFontSize(clock, R.dimen.status_bar_clock_size);
- }
- }
protected void loadDimens() {
final Resources res = mContext.getResources();
mNaturalBarHeight = res.getDimensionPixelSize(
com.android.internal.R.dimen.status_bar_height);
- int newIconSize = res.getDimensionPixelSize(
- com.android.internal.R.dimen.status_bar_icon_size);
- int newIconHPadding = res.getDimensionPixelSize(
- R.dimen.status_bar_icon_padding);
-
- if (newIconHPadding != mIconHPadding || newIconSize != mIconSize) {
-// Log.d(TAG, "size=" + newIconSize + " padding=" + newIconHPadding);
- mIconHPadding = newIconHPadding;
- mIconSize = newIconSize;
- //reloadAllNotificationIcons(); // reload the tray
- }
-
mEdgeBorder = res.getDimensionPixelSize(R.dimen.status_bar_edge_ignore);
- mNotificationPanelGravity = res.getInteger(R.integer.notification_panel_layout_gravity);
- if (mNotificationPanelGravity <= 0) {
- mNotificationPanelGravity = Gravity.START | Gravity.TOP;
- }
-
- mCarrierLabelHeight = res.getDimensionPixelSize(R.dimen.carrier_label_height);
- mStatusBarHeaderHeight = res.getDimensionPixelSize(R.dimen.status_bar_header_height);
-
- mNotificationPanelMinHeightFrac = res.getFraction(R.dimen.notification_panel_min_height_frac, 1, 1);
- if (mNotificationPanelMinHeightFrac < 0f || mNotificationPanelMinHeightFrac > 1f) {
- mNotificationPanelMinHeightFrac = 0f;
- }
-
- mHeadsUpNotificationDecay = res.getInteger(R.integer.heads_up_notification_decay);
mRowMinHeight = res.getDimensionPixelSize(R.dimen.notification_min_height);
mRowMaxHeight = res.getDimensionPixelSize(R.dimen.notification_max_height);
@@ -3443,14 +2935,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
};
@Override
- protected void haltTicker() {
- if (mTickerEnabled) {
- mTicker.halt();
- }
- }
-
- @Override
- protected boolean shouldDisableNavbarGestures() {
+ public boolean shouldDisableNavbarGestures() {
return !isDeviceProvisioned()
|| mExpandedVisible
|| (mDisabled & StatusBarManager.DISABLE_SEARCH) != 0;
@@ -3486,7 +2971,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
@Override
- public void setColorFilter(ColorFilter cf) {
+ public void setColorFilter(ColorFilter colorFilter) {
}
@Override
@@ -3519,11 +3004,11 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mHandlerThread = null;
}
mContext.unregisterReceiver(mBroadcastReceiver);
+ mAssistGestureManager.destroy();
}
private boolean mDemoModeAllowed;
private boolean mDemoMode;
- private DemoStatusIcons mDemoStatusIcons;
@Override
public void dispatchDemoCommand(String command, Bundle args) {
@@ -3552,10 +3037,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
dispatchDemoCommandToView(command, args, R.id.battery);
}
if (modeChange || command.equals(COMMAND_STATUS)) {
- if (mDemoStatusIcons == null) {
- mDemoStatusIcons = new DemoStatusIcons(mStatusIcons, mIconSize);
- }
- mDemoStatusIcons.dispatchDemoCommand(command, args);
+ mIconController.dispatchDemoCommand(command, args);
+
}
if (mNetworkController != null && (modeChange || command.equals(COMMAND_NETWORK))) {
mNetworkController.dispatchDemoCommand(command, args);
@@ -3625,7 +3108,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mLeaveOpenOnKeyguardHide = false;
if (mDraggedDownRow != null) {
mDraggedDownRow.setUserLocked(false);
- mDraggedDownRow.notifyHeightChanged();
+ mDraggedDownRow.notifyHeightChanged(false /* needsAnimation */);
mDraggedDownRow = null;
}
}
@@ -3675,6 +3158,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mLaunchTransitionFadingAway = false;
}
});
+ mIconController.appTransitionStarting(SystemClock.uptimeMillis(),
+ StatusBarIconController.DEFAULT_TINT_ANIMATION_DURATION);
}
};
if (mNotificationPanel.isLaunchTransitionRunning()) {
@@ -3742,16 +3227,31 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
/**
+ * Notifies the status bar that Keyguard is going away very soon.
+ */
+ public void keyguardGoingAway() {
+
+ // Treat Keyguard exit animation as an app transition to achieve nice transition for status
+ // bar.
+ mIconController.appTransitionPending();
+ }
+
+ /**
* Notifies the status bar the Keyguard is fading away with the specified timings.
*
- * @param delay the animation delay in miliseconds
+ * @param startTime the start time of the animations in uptime millis
+ * @param delay the precalculated animation delay in miliseconds
* @param fadeoutDuration the duration of the exit animation, in milliseconds
*/
- public void setKeyguardFadingAway(long delay, long fadeoutDuration) {
+ public void setKeyguardFadingAway(long startTime, long delay, long fadeoutDuration) {
mKeyguardFadingAway = true;
mKeyguardFadingAwayDelay = delay;
mKeyguardFadingAwayDuration = fadeoutDuration;
mWaitingForKeyguardExit = false;
+ mIconController.appTransitionStarting(
+ startTime + fadeoutDuration
+ - StatusBarIconController.DEFAULT_TINT_ANIMATION_DURATION,
+ StatusBarIconController.DEFAULT_TINT_ANIMATION_DURATION);
disable(mDisabledUnmodified, true /* animate */);
}
@@ -3767,8 +3267,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
private void updatePublicMode() {
- setLockscreenPublicMode(mStatusBarKeyguardViewManager.isShowing()
- && mStatusBarKeyguardViewManager.isSecure(mCurrentUserId));
+ setLockscreenPublicMode(
+ mStatusBarKeyguardViewManager.isShowing() && mStatusBarKeyguardViewManager
+ .isSecure(mCurrentUserId));
}
private void updateKeyguardState(boolean goingToFullShade, boolean fromShadeLocked) {
@@ -3792,7 +3293,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
updateStackScrollerState(goingToFullShade);
updateNotifications();
checkBarModes();
- updateCarrierLabelVisibility(false);
updateMediaMetaData(false);
mKeyguardMonitor.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(),
mStatusBarKeyguardViewManager.isSecure());
@@ -3913,6 +3413,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
}
mState = state;
+ mGroupManager.setStatusBarState(state);
mStatusBarWindowManager.setStatusBarState(state);
}
@@ -4046,13 +3547,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
}
- /**
- * @return a ViewGroup that spans the entire panel which contains the quick settings
- */
- public ViewGroup getQuickSettingsOverlayParent() {
- return mNotificationPanel;
- }
-
public long getKeyguardFadingAwayDelay() {
return mKeyguardFadingAwayDelay;
}
@@ -4061,14 +3555,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
return mKeyguardFadingAwayDuration;
}
- public LinearLayout getSystemIcons() {
- return mSystemIcons;
- }
-
- public LinearLayout getSystemIconArea() {
- return mSystemIconArea;
- }
-
@Override
public void setBouncerShowing(boolean bouncerShowing) {
super.setBouncerShowing(bouncerShowing);
@@ -4211,6 +3697,31 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
}
+ @Override
+ public void appTransitionPending() {
+
+ // Use own timings when Keyguard is going away, see keyguardGoingAway and
+ // setKeyguardFadingAway
+ if (!mKeyguardFadingAway) {
+ mIconController.appTransitionPending();
+ }
+ }
+
+ @Override
+ public void appTransitionCancelled() {
+ mIconController.appTransitionCancelled();
+ }
+
+ @Override
+ public void appTransitionStarting(long startTime, long duration) {
+
+ // Use own timings when Keyguard is going away, see keyguardGoingAway and
+ // setKeyguardFadingAway
+ if (!mKeyguardFadingAway) {
+ mIconController.appTransitionStarting(startTime, duration);
+ }
+ }
+
private final class ShadeUpdates {
private final ArraySet<String> mVisibleNotifications = new ArraySet<String>();
private final ArraySet<String> mNewVisibleNotifications = new ArraySet<String>();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 5c254a26..ac93ced 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -33,6 +33,7 @@ import android.util.Log;
import com.android.internal.telephony.IccCardConstants;
import com.android.internal.telephony.TelephonyIntents;
import com.android.systemui.R;
+import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.CastController.CastDevice;
import com.android.systemui.statusbar.policy.HotspotController;
@@ -46,16 +47,12 @@ public class PhoneStatusBarPolicy {
private static final String TAG = "PhoneStatusBarPolicy";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private static final boolean SHOW_SYNC_ICON = false;
-
- private static final String SLOT_SYNC_ACTIVE = "sync_active";
private static final String SLOT_CAST = "cast";
private static final String SLOT_HOTSPOT = "hotspot";
private static final String SLOT_BLUETOOTH = "bluetooth";
private static final String SLOT_TTY = "tty";
private static final String SLOT_ZEN = "zen";
private static final String SLOT_VOLUME = "volume";
- private static final String SLOT_CDMA_ERI = "cdma_eri";
private static final String SLOT_ALARM_CLOCK = "alarm_clock";
private final Context mContext;
@@ -83,9 +80,6 @@ public class PhoneStatusBarPolicy {
if (action.equals(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)) {
updateAlarm();
}
- else if (action.equals(Intent.ACTION_SYNC_STATE_CHANGED)) {
- updateSyncState(intent);
- }
else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED) ||
action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
updateBluetooth();
@@ -115,7 +109,6 @@ public class PhoneStatusBarPolicy {
// listen for broadcasts
IntentFilter filter = new IntentFilter();
filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
- filter.addAction(Intent.ACTION_SYNC_STATE_CHANGED);
filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
@@ -129,10 +122,6 @@ public class PhoneStatusBarPolicy {
mService.setIcon(SLOT_TTY, R.drawable.stat_sys_tty_mode, 0, null);
mService.setIconVisibility(SLOT_TTY, false);
- // Cdma Roaming Indicator, ERI
- mService.setIcon(SLOT_CDMA_ERI, R.drawable.stat_sys_roaming_cdma_0, 0, null);
- mService.setIconVisibility(SLOT_CDMA_ERI, false);
-
// bluetooth status
updateBluetooth();
@@ -140,11 +129,6 @@ public class PhoneStatusBarPolicy {
mService.setIcon(SLOT_ALARM_CLOCK, R.drawable.stat_sys_alarm, 0, null);
mService.setIconVisibility(SLOT_ALARM_CLOCK, false);
- // Sync state
- mService.setIcon(SLOT_SYNC_ACTIVE, R.drawable.stat_sys_sync, 0, null);
- mService.setIconVisibility(SLOT_SYNC_ACTIVE, false);
- // "sync_failing" is obsolete: b/1297963
-
// zen
mService.setIcon(SLOT_ZEN, R.drawable.stat_sys_zen_important, 0, null);
mService.setIconVisibility(SLOT_ZEN, false);
@@ -172,16 +156,10 @@ public class PhoneStatusBarPolicy {
private void updateAlarm() {
AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
- boolean alarmSet = alarmManager.getNextAlarmClock(UserHandle.USER_CURRENT) != null;
+ boolean alarmSet = alarmManager.getNextAlarmClock(UserHandle.USER_CURRENT) != null;
mService.setIconVisibility(SLOT_ALARM_CLOCK, alarmSet);
}
- private final void updateSyncState(Intent intent) {
- if (!SHOW_SYNC_ICON) return;
- boolean isActive = intent.getBooleanExtra("active", false);
- mService.setIconVisibility(SLOT_SYNC_ACTIVE, isActive);
- }
-
private final void updateSimState(Intent intent) {
String stateExtra = intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE);
if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(stateExtra)) {
@@ -221,7 +199,11 @@ public class PhoneStatusBarPolicy {
int volumeIconId = 0;
String volumeDescription = null;
- if (mZen == Global.ZEN_MODE_NO_INTERRUPTIONS) {
+ if (DndTile.isVisible(mContext) || DndTile.isCombinedIcon(mContext)) {
+ zenVisible = mZen != Global.ZEN_MODE_OFF;
+ zenIconId = R.drawable.stat_sys_dnd;
+ zenDescription = mContext.getString(R.string.quick_settings_dnd_label);
+ } else if (mZen == Global.ZEN_MODE_NO_INTERRUPTIONS) {
zenVisible = true;
zenIconId = R.drawable.stat_sys_zen_none;
zenDescription = mContext.getString(R.string.zen_no_interruptions);
@@ -231,7 +213,12 @@ public class PhoneStatusBarPolicy {
zenDescription = mContext.getString(R.string.zen_important_interruptions);
}
- if (mZen != Global.ZEN_MODE_NO_INTERRUPTIONS &&
+ if (DndTile.isVisible(mContext)
+ && audioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_SILENT) {
+ volumeVisible = true;
+ volumeIconId = R.drawable.stat_sys_ringer_silent;
+ volumeDescription = mContext.getString(R.string.accessibility_ringer_silent);
+ } else if (mZen != Global.ZEN_MODE_NO_INTERRUPTIONS && mZen != Global.ZEN_MODE_ALARMS &&
audioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_VIBRATE) {
volumeVisible = true;
volumeIconId = R.drawable.stat_sys_ringer_vibrate;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 7cbf13f..aa499ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -77,8 +77,8 @@ public class PhoneStatusBarView extends PanelBar {
}
@Override
- public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
- if (super.onRequestSendAccessibilityEvent(child, event)) {
+ public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) {
+ if (super.onRequestSendAccessibilityEventInternal(child, event)) {
// The status bar is very small so augment the view that the user is touching
// with the content of the status bar a whole. This way an accessibility service
// may announce the current item as well as the entire content if appropriate.
@@ -177,6 +177,5 @@ public class PhoneStatusBarView extends PanelBar {
public void panelExpansionChanged(PanelView panel, float frac, boolean expanded) {
super.panelExpansionChanged(panel, frac, expanded);
mScrimController.setPanelExpansion(frac);
- mBar.updateCarrierLabelVisibility(false);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
index 45a1386..954eb10 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -41,6 +41,7 @@ import com.android.systemui.qs.tiles.IntentTile;
import com.android.systemui.qs.tiles.LocationTile;
import com.android.systemui.qs.tiles.RotationLockTile;
import com.android.systemui.qs.tiles.WifiTile;
+import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.settings.CurrentUserTracker;
import com.android.systemui.statusbar.policy.BluetoothController;
import com.android.systemui.statusbar.policy.CastController;
@@ -256,6 +257,7 @@ public class QSTileHost implements QSTile.Host {
else if (tileSpec.equals("inversion")) return new ColorInversionTile(this);
else if (tileSpec.equals("cell")) return new CellularTile(this);
else if (tileSpec.equals("airplane")) return new AirplaneModeTile(this);
+ else if (tileSpec.equals("dnd")) return new DndTile(this);
else if (tileSpec.equals("rotation")) return new RotationLockTile(this);
else if (tileSpec.equals("flashlight")) return new FlashlightTile(this);
else if (tileSpec.equals("location")) return new LocationTile(this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
new file mode 100644
index 0000000..45da297
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -0,0 +1,417 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.animation.ArgbEvaluator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.statusbar.StatusBarIcon;
+import com.android.internal.util.NotificationColorUtil;
+import com.android.systemui.BatteryMeterView;
+import com.android.systemui.FontSizeUtils;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.SignalClusterView;
+import com.android.systemui.statusbar.StatusBarIconView;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * Controls everything regarding the icons in the status bar and on Keyguard, including, but not
+ * limited to: notification icons, signal cluster, additional status icons, and clock in the status
+ * bar.
+ */
+public class StatusBarIconController {
+
+ public static final long DEFAULT_TINT_ANIMATION_DURATION = 120;
+
+ private Context mContext;
+ private PhoneStatusBar mPhoneStatusBar;
+ private Interpolator mLinearOutSlowIn;
+ private Interpolator mFastOutSlowIn;
+ private DemoStatusIcons mDemoStatusIcons;
+ private NotificationColorUtil mNotificationColorUtil;
+
+ private LinearLayout mSystemIconArea;
+ private LinearLayout mStatusIcons;
+ private SignalClusterView mSignalCluster;
+ private LinearLayout mStatusIconsKeyguard;
+ private IconMerger mNotificationIcons;
+ private View mNotificationIconArea;
+ private ImageView mMoreIcon;
+ private BatteryMeterView mBatteryMeterView;
+ private TextView mClock;
+
+ private int mIconSize;
+ private int mIconHPadding;
+
+ private int mIconTint = Color.WHITE;
+ private float mDarkIntensity;
+
+ private boolean mTransitionPending;
+ private boolean mTintChangePending;
+ private float mPendingDarkIntensity;
+ private ValueAnimator mTintAnimator;
+
+ private int mDarkModeIconColorSingleTone;
+ private int mLightModeIconColorSingleTone;
+
+ private final Handler mHandler;
+ private boolean mTransitionDeferring;
+ private long mTransitionDeferringStartTime;
+ private long mTransitionDeferringDuration;
+
+ private final Runnable mTransitionDeferringDoneRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mTransitionDeferring = false;
+ }
+ };
+
+ public StatusBarIconController(Context context, View statusBar, View keyguardStatusBar,
+ PhoneStatusBar phoneStatusBar) {
+ mContext = context;
+ mPhoneStatusBar = phoneStatusBar;
+ mNotificationColorUtil = NotificationColorUtil.getInstance(context);
+ mSystemIconArea = (LinearLayout) statusBar.findViewById(R.id.system_icon_area);
+ mStatusIcons = (LinearLayout) statusBar.findViewById(R.id.statusIcons);
+ mSignalCluster = (SignalClusterView) statusBar.findViewById(R.id.signal_cluster);
+ mNotificationIconArea = statusBar.findViewById(R.id.notification_icon_area_inner);
+ mNotificationIcons = (IconMerger) statusBar.findViewById(R.id.notificationIcons);
+ mMoreIcon = (ImageView) statusBar.findViewById(R.id.moreIcon);
+ mNotificationIcons.setOverflowIndicator(mMoreIcon);
+ mStatusIconsKeyguard = (LinearLayout) keyguardStatusBar.findViewById(R.id.statusIcons);
+ mBatteryMeterView = (BatteryMeterView) statusBar.findViewById(R.id.battery);
+ mClock = (TextView) statusBar.findViewById(R.id.clock);
+ mLinearOutSlowIn = AnimationUtils.loadInterpolator(mContext,
+ android.R.interpolator.linear_out_slow_in);
+ mFastOutSlowIn = AnimationUtils.loadInterpolator(mContext,
+ android.R.interpolator.fast_out_slow_in);
+ mDarkModeIconColorSingleTone = context.getColor(R.color.dark_mode_icon_color_single_tone);
+ mLightModeIconColorSingleTone = context.getColor(R.color.light_mode_icon_color_single_tone);
+ mHandler = new Handler();
+ updateResources();
+ }
+
+ public void updateResources() {
+ mIconSize = mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.status_bar_icon_size);
+ mIconHPadding = mContext.getResources().getDimensionPixelSize(
+ R.dimen.status_bar_icon_padding);
+ FontSizeUtils.updateFontSize(mClock, R.dimen.status_bar_clock_size);
+ }
+
+ public void addSystemIcon(String slot, int index, int viewIndex, StatusBarIcon icon) {
+ StatusBarIconView view = new StatusBarIconView(mContext, slot, null);
+ view.set(icon);
+ mStatusIcons.addView(view, viewIndex, new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize));
+ view = new StatusBarIconView(mContext, slot, null);
+ view.set(icon);
+ mStatusIconsKeyguard.addView(view, viewIndex, new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize));
+ applyIconTint();
+ }
+
+ public void updateSystemIcon(String slot, int index, int viewIndex,
+ StatusBarIcon old, StatusBarIcon icon) {
+ StatusBarIconView view = (StatusBarIconView) mStatusIcons.getChildAt(viewIndex);
+ view.set(icon);
+ view = (StatusBarIconView) mStatusIconsKeyguard.getChildAt(viewIndex);
+ view.set(icon);
+ applyIconTint();
+ }
+
+ public void removeSystemIcon(String slot, int index, int viewIndex) {
+ mStatusIcons.removeViewAt(viewIndex);
+ mStatusIconsKeyguard.removeViewAt(viewIndex);
+ }
+
+ public void updateNotificationIcons(NotificationData notificationData) {
+ final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
+ mIconSize + 2*mIconHPadding, mPhoneStatusBar.getStatusBarHeight());
+
+ ArrayList<NotificationData.Entry> activeNotifications =
+ notificationData.getActiveNotifications();
+ final int N = activeNotifications.size();
+ ArrayList<StatusBarIconView> toShow = new ArrayList<>(N);
+
+ // Filter out ambient notifications and notification children.
+ for (int i = 0; i < N; i++) {
+ NotificationData.Entry ent = activeNotifications.get(i);
+ if (notificationData.isAmbient(ent.key)
+ && !NotificationData.showNotificationEvenIfUnprovisioned(ent.notification)) {
+ continue;
+ }
+ if (!PhoneStatusBar.isTopLevelChild(ent)) {
+ continue;
+ }
+ toShow.add(ent.icon);
+ }
+
+ ArrayList<View> toRemove = new ArrayList<>();
+ for (int i=0; i<mNotificationIcons.getChildCount(); i++) {
+ View child = mNotificationIcons.getChildAt(i);
+ if (!toShow.contains(child)) {
+ toRemove.add(child);
+ }
+ }
+
+ final int toRemoveCount = toRemove.size();
+ for (int i = 0; i < toRemoveCount; i++) {
+ mNotificationIcons.removeView(toRemove.get(i));
+ }
+
+ for (int i=0; i<toShow.size(); i++) {
+ View v = toShow.get(i);
+ if (v.getParent() == null) {
+ mNotificationIcons.addView(v, i, params);
+ }
+ }
+
+ // Resort notification icons
+ final int childCount = mNotificationIcons.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View actual = mNotificationIcons.getChildAt(i);
+ StatusBarIconView expected = toShow.get(i);
+ if (actual == expected) {
+ continue;
+ }
+ mNotificationIcons.removeView(expected);
+ mNotificationIcons.addView(expected, i);
+ }
+
+ applyNotificationIconsTint();
+ }
+
+ public void hideSystemIconArea(boolean animate) {
+ animateHide(mSystemIconArea, animate);
+ }
+
+ public void showSystemIconArea(boolean animate) {
+ animateShow(mSystemIconArea, animate);
+ }
+
+ public void hideNotificationIconArea(boolean animate) {
+ animateHide(mNotificationIconArea, animate);
+ }
+
+ public void showNotificationIconArea(boolean animate) {
+ animateShow(mNotificationIconArea, animate);
+ }
+
+ public void setClockVisibility(boolean visible) {
+ mClock.setVisibility(visible ? View.VISIBLE : View.GONE);
+ }
+
+ public void dump(PrintWriter pw) {
+ int N = mStatusIcons.getChildCount();
+ pw.println(" system icons: " + N);
+ for (int i=0; i<N; i++) {
+ StatusBarIconView ic = (StatusBarIconView) mStatusIcons.getChildAt(i);
+ pw.println(" [" + i + "] icon=" + ic);
+ }
+ }
+
+ public void dispatchDemoCommand(String command, Bundle args) {
+ if (mDemoStatusIcons == null) {
+ mDemoStatusIcons = new DemoStatusIcons(mStatusIcons, mIconSize);
+ }
+ mDemoStatusIcons.dispatchDemoCommand(command, args);
+ }
+
+ /**
+ * Hides a view.
+ */
+ private void animateHide(final View v, boolean animate) {
+ v.animate().cancel();
+ if (!animate) {
+ v.setAlpha(0f);
+ v.setVisibility(View.INVISIBLE);
+ return;
+ }
+ v.animate()
+ .alpha(0f)
+ .setDuration(160)
+ .setStartDelay(0)
+ .setInterpolator(PhoneStatusBar.ALPHA_OUT)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ v.setVisibility(View.INVISIBLE);
+ }
+ });
+ }
+
+ /**
+ * Shows a view, and synchronizes the animation with Keyguard exit animations, if applicable.
+ */
+ private void animateShow(View v, boolean animate) {
+ v.animate().cancel();
+ v.setVisibility(View.VISIBLE);
+ if (!animate) {
+ v.setAlpha(1f);
+ return;
+ }
+ v.animate()
+ .alpha(1f)
+ .setDuration(320)
+ .setInterpolator(PhoneStatusBar.ALPHA_IN)
+ .setStartDelay(50)
+
+ // We need to clean up any pending end action from animateHide if we call
+ // both hide and show in the same frame before the animation actually gets started.
+ // cancel() doesn't really remove the end action.
+ .withEndAction(null);
+
+ // Synchronize the motion with the Keyguard fading if necessary.
+ if (mPhoneStatusBar.isKeyguardFadingAway()) {
+ v.animate()
+ .setDuration(mPhoneStatusBar.getKeyguardFadingAwayDuration())
+ .setInterpolator(mLinearOutSlowIn)
+ .setStartDelay(mPhoneStatusBar.getKeyguardFadingAwayDelay())
+ .start();
+ }
+ }
+
+ public void setIconsDark(boolean dark) {
+ if (mTransitionPending) {
+ deferIconTintChange(dark ? 1.0f : 0.0f);
+ } else if (mTransitionDeferring) {
+ animateIconTint(dark ? 1.0f : 0.0f,
+ Math.max(0, mTransitionDeferringStartTime - SystemClock.uptimeMillis()),
+ mTransitionDeferringDuration);
+ } else {
+ animateIconTint(dark ? 1.0f : 0.0f, 0 /* delay */, DEFAULT_TINT_ANIMATION_DURATION);
+ }
+ }
+
+ private void animateIconTint(float targetDarkIntensity, long delay,
+ long duration) {
+ if (mTintAnimator != null) {
+ mTintAnimator.cancel();
+ }
+ if (mDarkIntensity == targetDarkIntensity) {
+ return;
+ }
+ mTintAnimator = ValueAnimator.ofFloat(mDarkIntensity, targetDarkIntensity);
+ mTintAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ setIconTintInternal((Float) animation.getAnimatedValue());
+ }
+ });
+ mTintAnimator.setDuration(duration);
+ mTintAnimator.setStartDelay(delay);
+ mTintAnimator.setInterpolator(mFastOutSlowIn);
+ mTintAnimator.start();
+ }
+
+ private void setIconTintInternal(float darkIntensity) {
+ mDarkIntensity = darkIntensity;
+ mIconTint = (int) ArgbEvaluator.getInstance().evaluate(darkIntensity,
+ mLightModeIconColorSingleTone, mDarkModeIconColorSingleTone);
+ applyIconTint();
+ }
+
+ private void deferIconTintChange(float darkIntensity) {
+ if (mTintChangePending && darkIntensity == mPendingDarkIntensity) {
+ return;
+ }
+ mTintChangePending = true;
+ mPendingDarkIntensity = darkIntensity;
+ }
+
+ private void applyIconTint() {
+ for (int i = 0; i < mStatusIcons.getChildCount(); i++) {
+ StatusBarIconView v = (StatusBarIconView) mStatusIcons.getChildAt(i);
+ v.setImageTintList(ColorStateList.valueOf(mIconTint));
+ }
+ mSignalCluster.setIconTint(mIconTint, mDarkIntensity);
+ mMoreIcon.setImageTintList(ColorStateList.valueOf(mIconTint));
+ mBatteryMeterView.setDarkIntensity(mDarkIntensity);
+ mClock.setTextColor(mIconTint);
+ applyNotificationIconsTint();
+ }
+
+ private void applyNotificationIconsTint() {
+ for (int i = 0; i < mNotificationIcons.getChildCount(); i++) {
+ StatusBarIconView v = (StatusBarIconView) mNotificationIcons.getChildAt(i);
+ boolean isPreL = Boolean.TRUE.equals(v.getTag(R.id.icon_is_pre_L));
+ boolean colorize = !isPreL || isGrayscale(v);
+ if (colorize) {
+ v.setImageTintList(ColorStateList.valueOf(mIconTint));
+ }
+ }
+ }
+
+ private boolean isGrayscale(StatusBarIconView v) {
+ Object isGrayscale = v.getTag(R.id.icon_is_grayscale);
+ if (isGrayscale != null) {
+ return Boolean.TRUE.equals(isGrayscale);
+ }
+ boolean grayscale = mNotificationColorUtil.isGrayscaleIcon(v.getDrawable());
+ v.setTag(R.id.icon_is_grayscale, grayscale);
+ return grayscale;
+ }
+
+ public void appTransitionPending() {
+ mTransitionPending = true;
+ }
+
+ public void appTransitionCancelled() {
+ if (mTransitionPending && mTintChangePending) {
+ mTintChangePending = false;
+ animateIconTint(mPendingDarkIntensity, 0 /* delay */, DEFAULT_TINT_ANIMATION_DURATION);
+ }
+ mTransitionPending = false;
+ }
+
+ public void appTransitionStarting(long startTime, long duration) {
+ if (mTransitionPending && mTintChangePending) {
+ mTintChangePending = false;
+ animateIconTint(mPendingDarkIntensity,
+ Math.max(0, startTime - SystemClock.uptimeMillis()),
+ duration);
+
+ } else if (mTransitionPending) {
+
+ // If we don't have a pending tint change yet, the change might come in the future until
+ // startTime is reached.
+ mTransitionDeferring = true;
+ mTransitionDeferringStartTime = startTime;
+ mTransitionDeferringDuration = duration;
+ mHandler.removeCallbacks(mTransitionDeferringDoneRunnable);
+ mHandler.postAtTime(mTransitionDeferringDoneRunnable, startTime);
+ }
+ mTransitionPending = false;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 1724e70..6369d5e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -31,6 +31,7 @@ import com.android.internal.policy.IKeyguardShowCallback;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.ViewMediatorCallback;
+import com.android.systemui.statusbar.CommandQueue;
import static com.android.keyguard.KeyguardHostView.OnDismissAction;
@@ -187,10 +188,6 @@ public class StatusBarKeyguardViewManager {
mStatusBarWindowManager.setKeyguardNeedsInput(needsInput);
}
- public void updateUserActivityTimeout() {
- mStatusBarWindowManager.setKeyguardUserActivityTimeout(mBouncer.getUserActivityTimeout());
- }
-
public void setOccluded(boolean occluded) {
if (occluded && !mOccluded && mShowing) {
if (mPhoneStatusBar.isInLaunchTransition()) {
@@ -261,7 +258,7 @@ public class StatusBarKeyguardViewManager {
}
});
} else {
- mPhoneStatusBar.setKeyguardFadingAway(delay, fadeoutDuration);
+ mPhoneStatusBar.setKeyguardFadingAway(startTime, delay, fadeoutDuration);
boolean staying = mPhoneStatusBar.hideKeyguard();
if (!staying) {
mStatusBarWindowManager.setKeyguardFadingAway(true);
@@ -439,4 +436,12 @@ public class StatusBarKeyguardViewManager {
public boolean isInputRestricted() {
return mViewMediatorCallback.isInputRestricted();
}
+
+ public void keyguardGoingAway() {
+ mPhoneStatusBar.keyguardGoingAway();
+ }
+
+ public void animateCollapsePanels() {
+ mPhoneStatusBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
index 0dbdca1..63bbf97 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
@@ -27,6 +27,7 @@ import android.view.ViewGroup;
import android.view.WindowManager;
import com.android.keyguard.R;
+import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.statusbar.BaseStatusBar;
import com.android.systemui.statusbar.StatusBarState;
@@ -114,7 +115,8 @@ public class StatusBarWindowManager {
private void applyFocusableFlag(State state) {
if (state.isKeyguardShowingAndNotOccluded() && state.keyguardNeedsInput
- && state.bouncerShowing) {
+ && state.bouncerShowing
+ || BaseStatusBar.ENABLE_REMOTE_INPUT && state.statusBarExpanded) {
mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
} else if (state.isKeyguardShowingAndNotOccluded() || state.statusBarFocusable) {
@@ -144,7 +146,7 @@ public class StatusBarWindowManager {
if (state.isKeyguardShowingAndNotOccluded()
&& state.statusBarState == StatusBarState.KEYGUARD
&& !state.qsExpanded) {
- mLpChanged.userActivityTimeout = state.keyguardUserActivityTimeout;
+ mLpChanged.userActivityTimeout = KeyguardViewMediator.AWAKE_INTERVAL_DEFAULT_MS;
} else {
mLpChanged.userActivityTimeout = -1;
}
@@ -201,11 +203,6 @@ public class StatusBarWindowManager {
apply(mCurrentState);
}
- public void setKeyguardUserActivityTimeout(long timeout) {
- mCurrentState.keyguardUserActivityTimeout = timeout;
- apply(mCurrentState);
- }
-
public void setBouncerShowing(boolean showing) {
mCurrentState.bouncerShowing = showing;
apply(mCurrentState);
@@ -235,7 +232,6 @@ public class StatusBarWindowManager {
boolean keyguardNeedsInput;
boolean statusBarExpanded;
boolean statusBarFocusable;
- long keyguardUserActivityTimeout;
boolean bouncerShowing;
boolean keyguardFadingAway;
boolean qsExpanded;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/Ticker.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/Ticker.java
deleted file mode 100644
index a6ce288..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/Ticker.java
+++ /dev/null
@@ -1,305 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.service.notification.StatusBarNotification;
-import android.text.Layout.Alignment;
-import android.text.StaticLayout;
-import android.text.TextPaint;
-import android.view.View;
-import android.view.animation.AnimationUtils;
-import android.widget.ImageSwitcher;
-import android.widget.TextSwitcher;
-import android.widget.TextView;
-
-import com.android.internal.statusbar.StatusBarIcon;
-import com.android.systemui.R;
-import com.android.systemui.statusbar.StatusBarIconView;
-
-import java.util.ArrayList;
-
-public abstract class Ticker {
- private static final int TICKER_SEGMENT_DELAY = 3000;
-
- private Context mContext;
- private Handler mHandler = new Handler();
- private ArrayList<Segment> mSegments = new ArrayList();
- private TextPaint mPaint;
- private View mTickerView;
- private ImageSwitcher mIconSwitcher;
- private TextSwitcher mTextSwitcher;
- private float mIconScale;
-
- public static boolean isGraphicOrEmoji(char c) {
- int gc = Character.getType(c);
- return gc != Character.CONTROL
- && gc != Character.FORMAT
- && gc != Character.UNASSIGNED
- && gc != Character.LINE_SEPARATOR
- && gc != Character.PARAGRAPH_SEPARATOR
- && gc != Character.SPACE_SEPARATOR;
- }
-
- private final class Segment {
- StatusBarNotification notification;
- Drawable icon;
- CharSequence text;
- int current;
- int next;
- boolean first;
-
- StaticLayout getLayout(CharSequence substr) {
- int w = mTextSwitcher.getWidth() - mTextSwitcher.getPaddingLeft()
- - mTextSwitcher.getPaddingRight();
- return new StaticLayout(substr, mPaint, w, Alignment.ALIGN_NORMAL, 1, 0, true);
- }
-
- CharSequence rtrim(CharSequence substr, int start, int end) {
- while (end > start && !isGraphicOrEmoji(substr.charAt(end-1))) {
- end--;
- }
- if (end > start) {
- return substr.subSequence(start, end);
- }
- return null;
- }
-
- /** returns null if there is no more text */
- CharSequence getText() {
- if (this.current > this.text.length()) {
- return null;
- }
- CharSequence substr = this.text.subSequence(this.current, this.text.length());
- StaticLayout l = getLayout(substr);
- int lineCount = l.getLineCount();
- if (lineCount > 0) {
- int start = l.getLineStart(0);
- int end = l.getLineEnd(0);
- this.next = this.current + end;
- return rtrim(substr, start, end);
- } else {
- throw new RuntimeException("lineCount=" + lineCount + " current=" + current +
- " text=" + text);
- }
- }
-
- /** returns null if there is no more text */
- CharSequence advance() {
- this.first = false;
- int index = this.next;
- final int len = this.text.length();
- while (index < len && !isGraphicOrEmoji(this.text.charAt(index))) {
- index++;
- }
- if (index >= len) {
- return null;
- }
-
- CharSequence substr = this.text.subSequence(index, this.text.length());
- StaticLayout l = getLayout(substr);
- final int lineCount = l.getLineCount();
- int i;
- for (i=0; i<lineCount; i++) {
- int start = l.getLineStart(i);
- int end = l.getLineEnd(i);
- if (i == lineCount-1) {
- this.next = len;
- } else {
- this.next = index + l.getLineStart(i+1);
- }
- CharSequence result = rtrim(substr, start, end);
- if (result != null) {
- this.current = index + start;
- return result;
- }
- }
- this.current = len;
- return null;
- }
-
- Segment(StatusBarNotification n, Drawable icon, CharSequence text) {
- this.notification = n;
- this.icon = icon;
- this.text = text;
- int index = 0;
- final int len = text.length();
- while (index < len && !isGraphicOrEmoji(text.charAt(index))) {
- index++;
- }
- this.current = index;
- this.next = index;
- this.first = true;
- }
- };
-
- public Ticker(Context context, View sb) {
- mContext = context;
- final Resources res = context.getResources();
- final int outerBounds = res.getDimensionPixelSize(R.dimen.status_bar_icon_size);
- final int imageBounds = res.getDimensionPixelSize(R.dimen.status_bar_icon_drawing_size);
- mIconScale = (float)imageBounds / (float)outerBounds;
-
- mTickerView = sb.findViewById(R.id.ticker);
-
- mIconSwitcher = (ImageSwitcher)sb.findViewById(R.id.tickerIcon);
- mIconSwitcher.setInAnimation(
- AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_in));
- mIconSwitcher.setOutAnimation(
- AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_out));
- mIconSwitcher.setScaleX(mIconScale);
- mIconSwitcher.setScaleY(mIconScale);
-
- mTextSwitcher = (TextSwitcher)sb.findViewById(R.id.tickerText);
- mTextSwitcher.setInAnimation(
- AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_in));
- mTextSwitcher.setOutAnimation(
- AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_out));
-
- // Copy the paint style of one of the TextSwitchers children to use later for measuring
- TextView text = (TextView)mTextSwitcher.getChildAt(0);
- mPaint = text.getPaint();
- }
-
-
- public void addEntry(StatusBarNotification n) {
- int initialCount = mSegments.size();
-
- // If what's being displayed has the same text and icon, just drop it
- // (which will let the current one finish, this happens when apps do
- // a notification storm).
- if (initialCount > 0) {
- final Segment seg = mSegments.get(0);
- if (n.getPackageName().equals(seg.notification.getPackageName())
- && n.getNotification().icon == seg.notification.getNotification().icon
- && n.getNotification().iconLevel == seg.notification.getNotification().iconLevel
- && charSequencesEqual(seg.notification.getNotification().tickerText,
- n.getNotification().tickerText)) {
- return;
- }
- }
-
- final Drawable icon = StatusBarIconView.getIcon(mContext,
- new StatusBarIcon(n.getPackageName(), n.getUser(), n.getNotification().icon, n.getNotification().iconLevel, 0,
- n.getNotification().tickerText));
- final CharSequence text = n.getNotification().tickerText;
- final Segment newSegment = new Segment(n, icon, text);
-
- // If there's already a notification schedule for this package and id, remove it.
- for (int i=0; i<mSegments.size(); i++) {
- Segment seg = mSegments.get(i);
- if (n.getId() == seg.notification.getId() && n.getPackageName().equals(seg.notification.getPackageName())) {
- // just update that one to use this new data instead
- mSegments.remove(i--); // restart iteration here
- }
- }
-
- mSegments.add(newSegment);
-
- if (initialCount == 0 && mSegments.size() > 0) {
- Segment seg = mSegments.get(0);
- seg.first = false;
-
- mIconSwitcher.setAnimateFirstView(false);
- mIconSwitcher.reset();
- mIconSwitcher.setImageDrawable(seg.icon);
-
- mTextSwitcher.setAnimateFirstView(false);
- mTextSwitcher.reset();
- mTextSwitcher.setText(seg.getText());
-
- tickerStarting();
- scheduleAdvance();
- }
- }
-
- private static boolean charSequencesEqual(CharSequence a, CharSequence b) {
- if (a.length() != b.length()) {
- return false;
- }
-
- int length = a.length();
- for (int i = 0; i < length; i++) {
- if (a.charAt(i) != b.charAt(i)) {
- return false;
- }
- }
- return true;
- }
-
- public void removeEntry(StatusBarNotification n) {
- for (int i=mSegments.size()-1; i>=0; i--) {
- Segment seg = mSegments.get(i);
- if (n.getId() == seg.notification.getId() && n.getPackageName().equals(seg.notification.getPackageName())) {
- mSegments.remove(i);
- }
- }
- }
-
- public void halt() {
- mHandler.removeCallbacks(mAdvanceTicker);
- mSegments.clear();
- tickerHalting();
- }
-
- public void reflowText() {
- if (mSegments.size() > 0) {
- Segment seg = mSegments.get(0);
- CharSequence text = seg.getText();
- mTextSwitcher.setCurrentText(text);
- }
- }
-
- private Runnable mAdvanceTicker = new Runnable() {
- public void run() {
- while (mSegments.size() > 0) {
- Segment seg = mSegments.get(0);
-
- if (seg.first) {
- // this makes the icon slide in for the first one for a given
- // notification even if there are two notifications with the
- // same icon in a row
- mIconSwitcher.setImageDrawable(seg.icon);
- }
- CharSequence text = seg.advance();
- if (text == null) {
- mSegments.remove(0);
- continue;
- }
- mTextSwitcher.setText(text);
-
- scheduleAdvance();
- break;
- }
- if (mSegments.size() == 0) {
- tickerDone();
- }
- }
- };
-
- private void scheduleAdvance() {
- mHandler.postDelayed(mAdvanceTicker, TICKER_SEGMENT_DELAY);
- }
-
- public abstract void tickerStarting();
- public abstract void tickerDone();
- public abstract void tickerHalting();
-}
-
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TickerView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/TickerView.java
deleted file mode 100644
index bf13751..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TickerView.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.TextSwitcher;
-
-
-public class TickerView extends TextSwitcher
-{
- Ticker mTicker;
-
- public TickerView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
- if (mTicker != null) mTicker.reflowText();
- }
-
- public void setTicker(Ticker t) {
- mTicker = t;
- }
-}
-
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TrustDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/TrustDrawable.java
index b89aa8f..56c1e10 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TrustDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/TrustDrawable.java
@@ -120,7 +120,7 @@ public class TrustDrawable extends Drawable {
}
@Override
- public void setColorFilter(ColorFilter cf) {
+ public void setColorFilter(ColorFilter colorFilter) {
throw new UnsupportedOperationException("not implemented");
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java
index 5ef345b..65cd268 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java
@@ -125,7 +125,7 @@ public class UnlockMethodCache {
}
@Override
- public void onFingerprintRecognized(int userId) {
+ public void onFingerprintAuthenticated(int userId) {
update(false /* updateAlways */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/VelocityTrackerFactory.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/VelocityTrackerFactory.java
index 4f43b4d..e153b85 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/VelocityTrackerFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/VelocityTrackerFactory.java
@@ -20,8 +20,6 @@ import android.content.Context;
import com.android.systemui.R;
-import static android.util.Pools.SynchronizedPool;
-
/**
* A class to generate {@link VelocityTrackerInterface}, depending on the configuration.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
index ad4c211..18983ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
@@ -17,35 +17,25 @@
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.settingslib.wifi.AccessPoint;
+import com.android.settingslib.wifi.WifiTracker;
+import com.android.settingslib.wifi.WifiTracker.WifiListener;
import com.android.systemui.R;
+import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
import java.util.List;
-
-// TODO: Unify this logic with platform settings (see WifiSettings and AccessPoint). There is a
-// fair amount of complexity here in statuses and logic beyond just connected/disconnected.
-public class AccessPointControllerImpl implements NetworkController.AccessPointController {
+public class AccessPointControllerImpl
+ implements NetworkController.AccessPointController, WifiListener {
private static final String TAG = "AccessPointController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -63,25 +53,18 @@ public class AccessPointControllerImpl implements NetworkController.AccessPointC
private final Context mContext;
private final ArrayList<AccessPointCallback> mCallbacks = new ArrayList<AccessPointCallback>();
- private final WifiManager mWifiManager;
+ private final WifiTracker mWifiTracker;
private final UserManager mUserManager;
- private final Receiver mReceiver = new Receiver();
- private NetworkControllerImpl mNetworkController;
- private boolean mScanning;
private int mCurrentUser;
public AccessPointControllerImpl(Context context) {
mContext = context;
- mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ mWifiTracker = new WifiTracker(context, this, false, true);
mCurrentUser = ActivityManager.getCurrentUser();
}
- void setNetworkController(NetworkControllerImpl networkController) {
- mNetworkController = networkController;
- }
-
public boolean canConfigWifi() {
return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI,
new UserHandle(mCurrentUser));
@@ -96,7 +79,9 @@ public class AccessPointControllerImpl implements NetworkController.AccessPointC
if (callback == null || mCallbacks.contains(callback)) return;
if (DEBUG) Log.d(TAG, "addCallback " + callback);
mCallbacks.add(callback);
- mReceiver.setListening(!mCallbacks.isEmpty());
+ if (mCallbacks.size() == 1) {
+ mWifiTracker.startTracking();
+ }
}
@Override
@@ -104,37 +89,40 @@ public class AccessPointControllerImpl implements NetworkController.AccessPointC
if (callback == null) return;
if (DEBUG) Log.d(TAG, "removeCallback " + callback);
mCallbacks.remove(callback);
- mReceiver.setListening(!mCallbacks.isEmpty());
+ if (mCallbacks.isEmpty()) {
+ mWifiTracker.stopTracking();
+ }
}
@Override
public void scanForAccessPoints() {
- if (mScanning) return;
if (DEBUG) Log.d(TAG, "scan!");
- mScanning = mWifiManager.startScan();
- // Grab current networks immediately while we wait for scan.
- updateAccessPoints();
+ mWifiTracker.forceScan();
+ }
+
+ @Override
+ public int getIcon(AccessPoint ap) {
+ int level = ap.getLevel();
+ return ICONS[level >= 0 ? level : 0];
}
public boolean connect(AccessPoint ap) {
if (ap == null) return false;
- if (DEBUG) Log.d(TAG, "connect networkId=" + ap.networkId);
- if (ap.networkId < 0) {
+ if (DEBUG) Log.d(TAG, "connect networkId=" + ap.getConfig().networkId);
+ if (ap.isSaved()) {
+ mWifiTracker.getManager().connect(ap.getConfig().networkId, mConnectListener);
+ } else {
// Unknown network, need to add it.
- if (ap.hasSecurity) {
+ if (ap.getSecurity() != AccessPoint.SECURITY_NONE) {
Intent intent = new Intent(Settings.ACTION_WIFI_SETTINGS);
- intent.putExtra(EXTRA_START_CONNECT_SSID, ap.ssid);
+ intent.putExtra(EXTRA_START_CONNECT_SSID, ap.getSsid());
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
fireSettingsIntentCallback(intent);
return true;
} else {
- WifiConfiguration config = new WifiConfiguration();
- config.SSID = "\"" + ap.ssid + "\"";
- config.allowedKeyManagement.set(KeyMgmt.NONE);
- mWifiManager.connect(config, mConnectListener);
+ ap.generateOpenNetworkConfig();
+ mWifiTracker.getManager().connect(ap.getConfig(), mConnectListener);
}
- } else {
- mWifiManager.connect(ap.networkId, mConnectListener);
}
return false;
}
@@ -145,76 +133,28 @@ public class AccessPointControllerImpl implements NetworkController.AccessPointC
}
}
- private void fireAcccessPointsCallback(AccessPoint[] aps) {
+ private void fireAcccessPointsCallback(List<AccessPoint> aps) {
for (AccessPointCallback callback : mCallbacks) {
callback.onAccessPointsChanged(aps);
}
}
- private static String trimDoubleQuotes(String v) {
- return v != null && v.length() >= 2 && v.charAt(0) == '\"'
- && v.charAt(v.length() - 1) == '\"' ? v.substring(1, v.length() - 1) : v;
+ public void dump(PrintWriter pw) {
+ mWifiTracker.dump(pw);
}
- private int getConnectedNetworkId(WifiInfo wifiInfo) {
- return wifiInfo != null ? wifiInfo.getNetworkId() : AccessPoint.NO_NETWORK;
+ @Override
+ public void onWifiStateChanged(int state) {
}
- private ArrayMap<String, WifiConfiguration> getConfiguredNetworksBySsid() {
- final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
- if (configs == null || configs.size() == 0) return ArrayMap.EMPTY;
- final ArrayMap<String, WifiConfiguration> rt = new ArrayMap<String, WifiConfiguration>();
- for (WifiConfiguration config : configs) {
- rt.put(trimDoubleQuotes(config.SSID), config);
- }
- return rt;
+ @Override
+ public void onConnectedChanged() {
+ fireAcccessPointsCallback(mWifiTracker.getAccessPoints());
}
- private void updateAccessPoints() {
- final WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
- final int connectedNetworkId = getConnectedNetworkId(wifiInfo);
- if (DEBUG) Log.d(TAG, "connectedNetworkId: " + connectedNetworkId);
- final List<ScanResult> scanResults = mWifiManager.getScanResults();
- final ArrayMap<String, WifiConfiguration> configured = getConfiguredNetworksBySsid();
- if (DEBUG) Log.d(TAG, "scanResults: " + scanResults);
- final List<AccessPoint> aps = new ArrayList<AccessPoint>(scanResults.size());
- final ArraySet<String> ssids = new ArraySet<String>();
- for (ScanResult scanResult : scanResults) {
- if (scanResult == null) {
- continue;
- }
- final String ssid = scanResult.SSID;
- if (TextUtils.isEmpty(ssid) || ssids.contains(ssid)) continue;
- ssids.add(ssid);
- final WifiConfiguration config = configured.get(ssid);
- final int level = WifiManager.calculateSignalLevel(scanResult.level, ICONS.length);
- final AccessPoint ap = new AccessPoint();
- ap.isConfigured = config != null;
- ap.networkId = config != null ? config.networkId : AccessPoint.NO_NETWORK;
- ap.ssid = ssid;
- // Connected if either:
- // -The network ID in the active WifiInfo matches this network's ID.
- // -The network is ephemeral (no configuration) but the SSID matches.
- ap.isConnected = (ap.networkId != AccessPoint.NO_NETWORK
- && ap.networkId == connectedNetworkId) ||
- (ap.networkId == WifiConfiguration.INVALID_NETWORK_ID && wifiInfo != null &&
- ap.ssid.equals(trimDoubleQuotes(wifiInfo.getSSID())));
- if (ap.isConnected && mNetworkController != null) {
- // Ensure we have the connected network's RSSI.
- ap.level = mNetworkController.getConnectedWifiLevel();
- } else {
- ap.level = level;
- }
- ap.iconId = ICONS[ap.level];
- // Based on Settings AccessPoint#getSecurity, keep up to date
- // with better methods of determining no security or not.
- ap.hasSecurity = scanResult.capabilities.contains("WEP")
- || scanResult.capabilities.contains("PSK")
- || scanResult.capabilities.contains("EAP");
- aps.add(ap);
- }
- Collections.sort(aps, mByStrength);
- fireAcccessPointsCallback(aps.toArray(new AccessPoint[aps.size()]));
+ @Override
+ public void onAccessPointsChanged() {
+ fireAcccessPointsCallback(mWifiTracker.getAccessPoints());
}
private final ActionListener mConnectListener = new ActionListener() {
@@ -228,49 +168,4 @@ public class AccessPointControllerImpl implements NetworkController.AccessPointC
if (DEBUG) Log.d(TAG, "connect failure reason=" + reason);
}
};
-
- private final Comparator<AccessPoint> mByStrength = new Comparator<AccessPoint> () {
- @Override
- public int compare(AccessPoint lhs, AccessPoint rhs) {
- return -Integer.compare(score(lhs), score(rhs));
- }
-
- private int score(AccessPoint ap) {
- return ap.level + (ap.isConnected ? 20 : 0) + (ap.isConfigured ? 10 : 0);
- }
- };
-
- private final class Receiver extends BroadcastReceiver {
- private boolean mRegistered;
-
- public void setListening(boolean listening) {
- if (listening && !mRegistered) {
- if (DEBUG) Log.d(TAG, "Registering receiver");
- final IntentFilter filter = new IntentFilter();
- filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
- filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
- filter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION);
- filter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
- filter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
- filter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
- filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
- filter.addAction(WifiManager.RSSI_CHANGED_ACTION);
- mContext.registerReceiver(this, filter);
- mRegistered = true;
- } else if (!listening && mRegistered) {
- if (DEBUG) Log.d(TAG, "Unregistering receiver");
- mContext.unregisterReceiver(this);
- mRegistered = false;
- }
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (DEBUG) Log.d(TAG, "onReceive " + intent.getAction());
- if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(intent.getAction())) {
- updateAccessPoints();
- mScanning = false;
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityController.java
index 89ed787..cc431dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityController.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.policy;
import android.content.Context;
-import android.util.Log;
import android.view.accessibility.AccessibilityManager;
import java.io.FileDescriptor;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
index 49693f5fe..cbe4c4d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
@@ -16,7 +16,9 @@
package com.android.systemui.statusbar.policy;
-import java.util.Set;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+
+import java.util.Collection;
public interface BluetoothController {
void addStateChangedCallback(Callback callback);
@@ -28,36 +30,12 @@ public interface BluetoothController {
boolean isBluetoothConnecting();
String getLastDeviceName();
void setBluetoothEnabled(boolean enabled);
- Set<PairedDevice> getPairedDevices();
- void connect(PairedDevice device);
- void disconnect(PairedDevice device);
+ Collection<CachedBluetoothDevice> getDevices();
+ void connect(CachedBluetoothDevice device);
+ void disconnect(CachedBluetoothDevice device);
public interface Callback {
void onBluetoothStateChange(boolean enabled, boolean connecting);
- void onBluetoothPairedDevicesChanged();
- }
-
- public static final class PairedDevice implements Comparable<PairedDevice> {
- public static int STATE_DISCONNECTED = 0;
- public static int STATE_CONNECTING = 1;
- public static int STATE_CONNECTED = 2;
- public static int STATE_DISCONNECTING = 3;
-
- public String id;
- public String name;
- public int state = STATE_DISCONNECTED;
- public Object tag;
-
- public static String stateToString(int state) {
- if (state == STATE_DISCONNECTED) return "STATE_DISCONNECTED";
- if (state == STATE_CONNECTING) return "STATE_CONNECTING";
- if (state == STATE_CONNECTED) return "STATE_CONNECTED";
- if (state == STATE_DISCONNECTING) return "STATE_DISCONNECTING";
- return "UNKNOWN";
- }
-
- public int compareTo(PairedDevice another) {
- return name.compareTo(another.name);
- }
+ void onBluetoothDevicesChanged();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 894f82a..8d4f302 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -16,138 +16,57 @@
package com.android.systemui.statusbar.policy;
-import static android.bluetooth.BluetoothAdapter.ERROR;
-import static com.android.systemui.statusbar.policy.BluetoothUtil.connectionStateToString;
-import static com.android.systemui.statusbar.policy.BluetoothUtil.deviceToString;
-import static com.android.systemui.statusbar.policy.BluetoothUtil.profileToString;
-import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidToProfile;
-import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidToString;
-import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidsToString;
-
-import android.bluetooth.BluetoothA2dp;
-import android.bluetooth.BluetoothA2dpSink;
import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHeadset;
-import android.bluetooth.BluetoothHeadsetClient;
-import android.bluetooth.BluetoothInputDevice;
-import android.bluetooth.BluetoothManager;
-import android.bluetooth.BluetoothMap;
-import android.bluetooth.BluetoothPan;
-import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothProfile.ServiceListener;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Handler;
import android.os.Looper;
-import android.os.Message;
-import android.os.ParcelUuid;
-import android.util.ArrayMap;
import android.util.Log;
-import android.util.SparseArray;
-import com.android.systemui.statusbar.policy.BluetoothUtil.Profile;
+import com.android.settingslib.bluetooth.BluetoothCallback;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Set;
-import java.util.TreeSet;
+import java.util.Collection;
-public class BluetoothControllerImpl implements BluetoothController {
+public class BluetoothControllerImpl implements BluetoothController, BluetoothCallback,
+ CachedBluetoothDevice.Callback {
private static final String TAG = "BluetoothController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- // This controls the order in which we check the states. Since a device can only have
- // one state on screen, but can have multiple profiles, the later states override the
- // value of earlier states. So if a device has a profile in CONNECTING and one in
- // CONNECTED, it will show as CONNECTED, theoretically this shouldn't really happen often,
- // but seemed worth noting.
- private static final int[] CONNECTION_STATES = {
- BluetoothProfile.STATE_DISCONNECTED,
- BluetoothProfile.STATE_DISCONNECTING,
- BluetoothProfile.STATE_CONNECTING,
- BluetoothProfile.STATE_CONNECTED,
- };
- // Update all the BT device states.
- private static final int MSG_UPDATE_CONNECTION_STATES = 1;
- // Update just one BT device.
- private static final int MSG_UPDATE_SINGLE_CONNECTION_STATE = 2;
- // Update whether devices are bonded or not.
- private static final int MSG_UPDATE_BONDED_DEVICES = 3;
-
- private static final int MSG_ADD_PROFILE = 4;
- private static final int MSG_REM_PROFILE = 5;
-
- private final Context mContext;
- private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
- private final BluetoothAdapter mAdapter;
- private final Receiver mReceiver = new Receiver();
- private final ArrayMap<BluetoothDevice, DeviceInfo> mDeviceInfo = new ArrayMap<>();
- private final SparseArray<BluetoothProfile> mProfiles = new SparseArray<>();
- private final H mHandler;
+ private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
+ private final LocalBluetoothManager mLocalBluetoothManager;
private boolean mEnabled;
private boolean mConnecting;
- private BluetoothDevice mLastDevice;
+ private CachedBluetoothDevice mLastDevice;
public BluetoothControllerImpl(Context context, Looper bgLooper) {
- mContext = context;
- mHandler = new H(bgLooper);
-
- final BluetoothManager bluetoothManager =
- (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
- mAdapter = bluetoothManager.getAdapter();
- if (mAdapter == null) {
- Log.w(TAG, "Default BT adapter not found");
- return;
+ mLocalBluetoothManager = LocalBluetoothManager.getInstance(context, null);
+ if (mLocalBluetoothManager != null) {
+ mLocalBluetoothManager.getEventManager().registerCallback(this);
+ onBluetoothStateChanged(
+ mLocalBluetoothManager.getBluetoothAdapter().getBluetoothState());
}
-
- mReceiver.register();
- setAdapterState(mAdapter.getState());
- updateBondedDevices();
- bindAllProfiles();
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("BluetoothController state:");
- pw.print(" mAdapter="); pw.println(mAdapter);
+ pw.print(" mLocalBluetoothManager="); pw.println(mLocalBluetoothManager);
pw.print(" mEnabled="); pw.println(mEnabled);
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);
- final DeviceInfo info = mDeviceInfo.valueAt(i);
- pw.print(" "); pw.print(deviceToString(device));
- pw.print('('); pw.print(uuidsToString(device)); pw.print(')');
- pw.print(" "); pw.println(infoToString(info));
+ pw.println(" Bluetooth Devices:");
+ for (CachedBluetoothDevice device :
+ mLocalBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy()) {
+ pw.println(" " + getDeviceString(device));
}
}
- private static String infoToString(DeviceInfo info) {
- return info == null ? null : ("connectionState=" +
- connectionStateToString(CONNECTION_STATES[info.connectionStateIndex])
- + ",bonded=" + info.bonded + ",profiles="
- + profilesToString(info.connectedProfiles));
- }
-
- private static String profilesToString(SparseArray<?> profiles) {
- final int N = profiles.size();
- final StringBuffer buffer = new StringBuffer();
- buffer.append('[');
- for (int i = 0; i < N; i++) {
- if (i != 0) {
- buffer.append(',');
- }
- buffer.append(BluetoothUtil.profileToString(profiles.keyAt(i)));
- }
- buffer.append(']');
- return buffer.toString();
+ private String getDeviceString(CachedBluetoothDevice device) {
+ return device.getName() + " " + device.getBondState() + " " + device.isConnected();
}
public void addStateChangedCallback(Callback cb) {
@@ -162,411 +81,126 @@ public class BluetoothControllerImpl implements BluetoothController {
@Override
public boolean isBluetoothEnabled() {
- return mAdapter != null && mAdapter.isEnabled();
+ return mEnabled;
}
@Override
public boolean isBluetoothConnected() {
- return mAdapter != null
- && mAdapter.getConnectionState() == BluetoothAdapter.STATE_CONNECTED;
+ return mLocalBluetoothManager != null
+ && mLocalBluetoothManager.getBluetoothAdapter().getConnectionState()
+ == BluetoothAdapter.STATE_CONNECTED;
}
@Override
public boolean isBluetoothConnecting() {
- return mAdapter != null
- && mAdapter.getConnectionState() == BluetoothAdapter.STATE_CONNECTING;
+ return mConnecting;
}
@Override
public void setBluetoothEnabled(boolean enabled) {
- if (mAdapter != null) {
- if (enabled) {
- mAdapter.enable();
- } else {
- mAdapter.disable();
- }
+ if (mLocalBluetoothManager != null) {
+ mLocalBluetoothManager.getBluetoothAdapter().setBluetoothEnabled(enabled);
}
}
@Override
public boolean isBluetoothSupported() {
- return mAdapter != null;
+ return mLocalBluetoothManager != null;
}
@Override
- public Set<PairedDevice> getPairedDevices() {
- final Set<PairedDevice> rt = new TreeSet<>();
- for (int i = 0; i < mDeviceInfo.size(); i++) {
- final BluetoothDevice device = mDeviceInfo.keyAt(i);
- final DeviceInfo info = mDeviceInfo.valueAt(i);
- if (!info.bonded) continue;
- final PairedDevice paired = new PairedDevice();
- paired.id = device.getAddress();
- paired.tag = device;
- paired.name = device.getAliasName();
- paired.state = connectionStateToPairedDeviceState(info.connectionStateIndex);
- rt.add(paired);
- }
- return rt;
- }
-
- private static int connectionStateToPairedDeviceState(int index) {
- int state = CONNECTION_STATES[index];
- if (state == BluetoothAdapter.STATE_CONNECTED) return PairedDevice.STATE_CONNECTED;
- if (state == BluetoothAdapter.STATE_CONNECTING) return PairedDevice.STATE_CONNECTING;
- if (state == BluetoothAdapter.STATE_DISCONNECTING) return PairedDevice.STATE_DISCONNECTING;
- return PairedDevice.STATE_DISCONNECTED;
+ public void connect(final CachedBluetoothDevice device) {
+ if (mLocalBluetoothManager == null || device == null) return;
+ device.connect(true);
}
@Override
- public void connect(final PairedDevice pd) {
- connect(pd, true);
- }
-
- @Override
- public void disconnect(PairedDevice pd) {
- connect(pd, false);
- }
-
- private void connect(PairedDevice pd, final boolean connect) {
- if (mAdapter == null || pd == null || pd.tag == null) return;
- final BluetoothDevice device = (BluetoothDevice) pd.tag;
- final DeviceInfo info = mDeviceInfo.get(device);
- final String action = connect ? "connect" : "disconnect";
- if (DEBUG) Log.d(TAG, action + " " + deviceToString(device));
- final ParcelUuid[] uuids = device.getUuids();
- if (uuids == null) {
- Log.w(TAG, "No uuids returned, aborting " + action + " for " + deviceToString(device));
- return;
- }
- SparseArray<Boolean> profiles = new SparseArray<>();
- if (connect) {
- // When connecting add every profile we can recognize by uuid.
- for (ParcelUuid uuid : uuids) {
- final int profile = uuidToProfile(uuid);
- if (profile == 0) {
- Log.w(TAG, "Device " + deviceToString(device) + " has an unsupported uuid: "
- + uuidToString(uuid));
- continue;
- }
- final boolean connected = info.connectedProfiles.get(profile, false);
- if (!connected) {
- profiles.put(profile, true);
- }
- }
- } else {
- // When disconnecting, just add every profile we know they are connected to.
- profiles = info.connectedProfiles;
- }
- for (int i = 0; i < profiles.size(); i++) {
- final int profile = profiles.keyAt(i);
- if (mProfiles.indexOfKey(profile) >= 0) {
- final Profile p = BluetoothUtil.getProfile(mProfiles.get(profile));
- final boolean ok = connect ? p.connect(device) : p.disconnect(device);
- if (DEBUG) Log.d(TAG, action + " " + profileToString(profile) + " "
- + (ok ? "succeeded" : "failed"));
- } else {
- Log.w(TAG, "Unable get get Profile for " + profileToString(profile));
- }
- }
+ public void disconnect(CachedBluetoothDevice device) {
+ if (mLocalBluetoothManager == null || device == null) return;
+ device.disconnect();
}
@Override
public String getLastDeviceName() {
- return mLastDevice != null ? mLastDevice.getAliasName() : null;
+ return mLastDevice != null ? mLastDevice.getName() : null;
}
- private void updateBondedDevices() {
- mHandler.removeMessages(MSG_UPDATE_BONDED_DEVICES);
- mHandler.sendEmptyMessage(MSG_UPDATE_BONDED_DEVICES);
- }
-
- private void updateConnectionStates() {
- mHandler.removeMessages(MSG_UPDATE_CONNECTION_STATES);
- mHandler.removeMessages(MSG_UPDATE_SINGLE_CONNECTION_STATE);
- mHandler.sendEmptyMessage(MSG_UPDATE_CONNECTION_STATES);
+ @Override
+ public Collection<CachedBluetoothDevice> getDevices() {
+ return mLocalBluetoothManager != null
+ ? mLocalBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy()
+ : null;
}
- 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;
+ private void firePairedDevicesChanged() {
+ for (Callback cb : mCallbacks) {
+ cb.onBluetoothDevicesChanged();
}
- 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()) {
- info.bonded = false;
- }
- int bondedCount = 0;
- BluetoothDevice lastBonded = null;
- if (bondedDevices != null) {
- for (BluetoothDevice bondedDevice : bondedDevices) {
- final boolean bonded = bondedDevice.getBondState() != BluetoothDevice.BOND_NONE;
- updateInfo(bondedDevice).bonded = bonded;
- if (bonded) {
- bondedCount++;
- lastBonded = bondedDevice;
- }
- }
- }
- if (mLastDevice == null && bondedCount == 1) {
- mLastDevice = lastBonded;
+ private void fireStateChange() {
+ for (Callback cb : mCallbacks) {
+ fireStateChange(cb);
}
- 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 fireStateChange(Callback cb) {
+ cb.onBluetoothStateChange(mEnabled, mConnecting);
}
- 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 updateConnected() {
+ if (mLastDevice != null && mLastDevice.isConnected()) {
+ // Our current device is still valid.
+ return;
}
- }
-
- 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;
- }
+ for (CachedBluetoothDevice device : getDevices()) {
+ if (device.isConnected()) {
+ mLastDevice = device;
}
}
}
- 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();
- }
- }
-
- private void setAdapterState(int adapterState) {
- final boolean enabled = adapterState == BluetoothAdapter.STATE_ON;
- if (mEnabled == enabled) return;
- mEnabled = enabled;
- fireStateChange();
- }
-
- private void setConnecting(boolean connecting) {
- if (mConnecting == connecting) return;
- mConnecting = connecting;
+ @Override
+ public void onBluetoothStateChanged(int bluetoothState) {
+ mEnabled = bluetoothState == BluetoothAdapter.STATE_ON;
fireStateChange();
}
- private void fireStateChange() {
- for (Callback cb : mCallbacks) {
- fireStateChange(cb);
- }
+ @Override
+ public void onScanningStateChanged(boolean started) {
+ // Don't care.
}
- private void fireStateChange(Callback cb) {
- cb.onBluetoothStateChange(mEnabled, mConnecting);
+ @Override
+ public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {
+ cachedDevice.registerCallback(this);
+ updateConnected();
+ firePairedDevicesChanged();
}
- 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;
+ @Override
+ public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {
+ updateConnected();
+ firePairedDevicesChanged();
}
- 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();
- filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
- filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
- filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
- filter.addAction(BluetoothDevice.ACTION_ALIAS_CHANGED);
- filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
- filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
- filter.addAction(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED);
- filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
- filter.addAction(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED);
- filter.addAction(BluetoothMap.ACTION_CONNECTION_STATE_CHANGED);
- filter.addAction(BluetoothPan.ACTION_CONNECTION_STATE_CHANGED);
- mContext.registerReceiver(this, filter);
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-
- if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
- setAdapterState(intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, ERROR));
- updateBondedDevices();
- if (DEBUG) Log.d(TAG, "ACTION_STATE_CHANGED " + mEnabled);
- } else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
- updateInfo(device);
- final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
- ERROR);
- mLastDevice = device;
- if (DEBUG) Log.d(TAG, "ACTION_CONNECTION_STATE_CHANGED "
- + connectionStateToString(state) + " " + deviceToString(device));
- setConnecting(state == BluetoothAdapter.STATE_CONNECTING);
- } else if (action.equals(BluetoothDevice.ACTION_ALIAS_CHANGED)) {
- updateInfo(device);
- mLastDevice = device;
- } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
- if (DEBUG) Log.d(TAG, "ACTION_BOND_STATE_CHANGED " + device);
- updateBondedDevices();
- } else {
- int profile = getProfileFromAction(intent.getAction());
- int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
- if (DEBUG) Log.d(TAG, "ACTION_CONNECTION_STATE_CHANGE "
- + BluetoothUtil.profileToString(profile)
- + " " + BluetoothUtil.connectionStateToString(state));
- if ((profile != -1) && (state != -1)) {
- updateConnectionState(device, profile, state);
- }
- }
- }
+ @Override
+ public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
+ updateConnected();
+ firePairedDevicesChanged();
}
- private DeviceInfo updateInfo(BluetoothDevice device) {
- DeviceInfo info = mDeviceInfo.get(device);
- info = info != null ? info : new DeviceInfo();
- mDeviceInfo.put(device, info);
- return info;
+ @Override
+ public void onDeviceAttributesChanged() {
+ updateConnected();
+ firePairedDevicesChanged();
}
- 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 connectionStateIndex = 0;
- boolean bonded; // per getBondedDevices
- SparseArray<Boolean> connectedProfiles = new SparseArray<>();
- SparseArray<Integer> profileStates = new SparseArray<>();
+ @Override
+ public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
+ mConnecting = state == BluetoothAdapter.STATE_CONNECTING;
+ mLastDevice = cachedDevice;
+ updateConnected();
+ fireStateChange();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothUtil.java
deleted file mode 100644
index ed8ac2c..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothUtil.java
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.policy;
-
-import android.bluetooth.BluetoothA2dp;
-import android.bluetooth.BluetoothA2dpSink;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHeadset;
-import android.bluetooth.BluetoothHeadsetClient;
-import android.bluetooth.BluetoothInputDevice;
-import android.bluetooth.BluetoothMap;
-import android.bluetooth.BluetoothPan;
-import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothUuid;
-import android.os.ParcelUuid;
-import android.text.TextUtils;
-
-public class BluetoothUtil {
-
- public static String profileToString(int profile) {
- if (profile == BluetoothProfile.HEADSET) return "HEADSET";
- if (profile == BluetoothProfile.A2DP) return "A2DP";
- if (profile == BluetoothProfile.AVRCP_CONTROLLER) return "AVRCP_CONTROLLER";
- if (profile == BluetoothProfile.PAN) return "PAN";
- if (profile == BluetoothProfile.INPUT_DEVICE) return "INPUT_DEVICE";
- if (profile == BluetoothProfile.MAP) return "MAP";
- return "UNKNOWN(" + profile + ")";
- }
-
- public static String profileStateToString(int state) {
- if (state == BluetoothProfile.STATE_CONNECTED) return "STATE_CONNECTED";
- if (state == BluetoothProfile.STATE_CONNECTING) return "STATE_CONNECTING";
- if (state == BluetoothProfile.STATE_DISCONNECTED) return "STATE_DISCONNECTED";
- if (state == BluetoothProfile.STATE_DISCONNECTING) return "STATE_DISCONNECTING";
- return "STATE_UNKNOWN";
- }
-
- public static String uuidToString(ParcelUuid uuid) {
- if (BluetoothUuid.AudioSink.equals(uuid)) return "AudioSink";
- if (BluetoothUuid.AudioSource.equals(uuid)) return "AudioSource";
- if (BluetoothUuid.AdvAudioDist.equals(uuid)) return "AdvAudioDist";
- if (BluetoothUuid.HSP.equals(uuid)) return "HSP";
- if (BluetoothUuid.HSP_AG.equals(uuid)) return "HSP_AG";
- if (BluetoothUuid.Handsfree.equals(uuid)) return "Handsfree";
- if (BluetoothUuid.Handsfree_AG.equals(uuid)) return "Handsfree_AG";
- if (BluetoothUuid.AvrcpController.equals(uuid)) return "AvrcpController";
- if (BluetoothUuid.AvrcpTarget.equals(uuid)) return "AvrcpTarget";
- if (BluetoothUuid.ObexObjectPush.equals(uuid)) return "ObexObjectPush";
- if (BluetoothUuid.Hid.equals(uuid)) return "Hid";
- if (BluetoothUuid.Hogp.equals(uuid)) return "Hogp";
- if (BluetoothUuid.PANU.equals(uuid)) return "PANU";
- if (BluetoothUuid.NAP.equals(uuid)) return "NAP";
- if (BluetoothUuid.BNEP.equals(uuid)) return "BNEP";
- if (BluetoothUuid.PBAP_PSE.equals(uuid)) return "PBAP_PSE";
- if (BluetoothUuid.MAP.equals(uuid)) return "MAP";
- if (BluetoothUuid.MNS.equals(uuid)) return "MNS";
- if (BluetoothUuid.MAS.equals(uuid)) return "MAS";
- return uuid != null ? uuid.toString() : null;
- }
-
- public static String connectionStateToString(int connectionState) {
- if (connectionState == BluetoothAdapter.STATE_DISCONNECTED) return "STATE_DISCONNECTED";
- if (connectionState == BluetoothAdapter.STATE_CONNECTED) return "STATE_CONNECTED";
- if (connectionState == BluetoothAdapter.STATE_DISCONNECTING) return "STATE_DISCONNECTING";
- if (connectionState == BluetoothAdapter.STATE_CONNECTING) return "STATE_CONNECTING";
- return "ERROR";
- }
-
- public static String deviceToString(BluetoothDevice device) {
- return device == null ? null : (device.getAddress() + '[' + device.getAliasName() + ']');
- }
-
- public static String uuidsToString(BluetoothDevice device) {
- if (device == null) return null;
- final ParcelUuid[] ids = device.getUuids();
- if (ids == null) return null;
- final String[] tokens = new String[ids.length];
- for (int i = 0; i < tokens.length; i++) {
- tokens[i] = uuidToString(ids[i]);
- }
- return TextUtils.join(",", tokens);
- }
-
- public static int uuidToProfile(ParcelUuid uuid) {
- if (BluetoothUuid.AudioSink.equals(uuid)) return BluetoothProfile.A2DP;
- if (BluetoothUuid.AdvAudioDist.equals(uuid)) return BluetoothProfile.A2DP;
-
- if (BluetoothUuid.HSP.equals(uuid)) return BluetoothProfile.HEADSET;
- if (BluetoothUuid.Handsfree.equals(uuid)) return BluetoothProfile.HEADSET;
-
- if (BluetoothUuid.MAP.equals(uuid)) return BluetoothProfile.MAP;
- if (BluetoothUuid.MNS.equals(uuid)) return BluetoothProfile.MAP;
- if (BluetoothUuid.MAS.equals(uuid)) return BluetoothProfile.MAP;
-
- if (BluetoothUuid.AvrcpController.equals(uuid)) return BluetoothProfile.AVRCP_CONTROLLER;
-
- if (BluetoothUuid.Hid.equals(uuid)) return BluetoothProfile.INPUT_DEVICE;
- if (BluetoothUuid.Hogp.equals(uuid)) return BluetoothProfile.INPUT_DEVICE;
-
- if (BluetoothUuid.NAP.equals(uuid)) return BluetoothProfile.PAN;
-
- return 0;
- }
-
- public static Profile getProfile(BluetoothProfile p) {
- if (p instanceof BluetoothA2dp) return newProfile((BluetoothA2dp) p);
- if (p instanceof BluetoothHeadset) return newProfile((BluetoothHeadset) p);
- if (p instanceof BluetoothA2dpSink) return newProfile((BluetoothA2dpSink) p);
- if (p instanceof BluetoothHeadsetClient) return newProfile((BluetoothHeadsetClient) p);
- if (p instanceof BluetoothInputDevice) return newProfile((BluetoothInputDevice) p);
- if (p instanceof BluetoothMap) return newProfile((BluetoothMap) p);
- if (p instanceof BluetoothPan) return newProfile((BluetoothPan) p);
- return null;
- }
-
- private static Profile newProfile(final BluetoothA2dp a2dp) {
- return new Profile() {
- @Override
- public boolean connect(BluetoothDevice device) {
- return a2dp.connect(device);
- }
-
- @Override
- public boolean disconnect(BluetoothDevice device) {
- return a2dp.disconnect(device);
- }
- };
- }
-
- private static Profile newProfile(final BluetoothHeadset headset) {
- return new Profile() {
- @Override
- public boolean connect(BluetoothDevice device) {
- return headset.connect(device);
- }
-
- @Override
- public boolean disconnect(BluetoothDevice device) {
- return headset.disconnect(device);
- }
- };
- }
-
- private static Profile newProfile(final BluetoothA2dpSink sink) {
- return new Profile() {
- @Override
- public boolean connect(BluetoothDevice device) {
- return sink.connect(device);
- }
-
- @Override
- public boolean disconnect(BluetoothDevice device) {
- return sink.disconnect(device);
- }
- };
- }
-
- private static Profile newProfile(final BluetoothHeadsetClient client) {
- return new Profile() {
- @Override
- public boolean connect(BluetoothDevice device) {
- return client.connect(device);
- }
-
- @Override
- public boolean disconnect(BluetoothDevice device) {
- return client.disconnect(device);
- }
- };
- }
-
- private static Profile newProfile(final BluetoothInputDevice input) {
- return new Profile() {
- @Override
- public boolean connect(BluetoothDevice device) {
- return input.connect(device);
- }
-
- @Override
- public boolean disconnect(BluetoothDevice device) {
- return input.disconnect(device);
- }
- };
- }
-
- private static Profile newProfile(final BluetoothMap map) {
- return new Profile() {
- @Override
- public boolean connect(BluetoothDevice device) {
- return map.connect(device);
- }
-
- @Override
- public boolean disconnect(BluetoothDevice device) {
- return map.disconnect(device);
- }
- };
- }
-
- private static Profile newProfile(final BluetoothPan pan) {
- return new Profile() {
- @Override
- public boolean connect(BluetoothDevice device) {
- return pan.connect(device);
- }
-
- @Override
- public boolean disconnect(BluetoothDevice device) {
- return pan.disconnect(device);
- }
- };
- }
-
- // common abstraction for supported profiles
- public interface Profile {
- boolean connect(BluetoothDevice device);
- boolean disconnect(BluetoothDevice device);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java
index 33f7aff..cd1914c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java
@@ -17,21 +17,14 @@
package com.android.systemui.statusbar.policy;
import android.content.Context;
-import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
-import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
-import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
-import android.hardware.camera2.CameraMetadata;
-import android.hardware.camera2.CaptureRequest;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
-import android.os.SystemProperties;
+import android.text.TextUtils;
import android.util.Log;
-import android.util.Size;
-import android.view.Surface;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -45,7 +38,7 @@ public class FlashlightController {
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final int DISPATCH_ERROR = 0;
- private static final int DISPATCH_OFF = 1;
+ private static final int DISPATCH_CHANGED = 1;
private static final int DISPATCH_AVAILABILITY_CHANGED = 2;
private final CameraManager mCameraManager;
@@ -58,52 +51,50 @@ public class FlashlightController {
/** Lock on {@code this} when accessing */
private boolean mFlashlightEnabled;
- private String mCameraId;
- private boolean mCameraAvailable;
- private CameraDevice mCameraDevice;
- private CaptureRequest mFlashlightRequest;
- private CameraCaptureSession mSession;
- private SurfaceTexture mSurfaceTexture;
- private Surface mSurface;
+ private final String mCameraId;
+ private boolean mTorchAvailable;
public FlashlightController(Context mContext) {
mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
- initialize();
- }
- public void initialize() {
+ String cameraId = null;
try {
- mCameraId = getCameraId();
+ cameraId = getCameraId();
} catch (Throwable e) {
Log.e(TAG, "Couldn't initialize.", e);
return;
+ } finally {
+ mCameraId = cameraId;
}
if (mCameraId != null) {
ensureHandler();
- mCameraManager.registerAvailabilityCallback(mAvailabilityCallback, mHandler);
+ mCameraManager.registerTorchCallback(mTorchCallback, mHandler);
}
}
- public synchronized void setFlashlight(boolean enabled) {
- if (mFlashlightEnabled != enabled) {
- mFlashlightEnabled = enabled;
- postUpdateFlashlight();
- }
- }
-
- public void killFlashlight() {
- boolean enabled;
+ public void setFlashlight(boolean enabled) {
+ boolean pendingError = false;
synchronized (this) {
- enabled = mFlashlightEnabled;
+ if (mFlashlightEnabled != enabled) {
+ mFlashlightEnabled = enabled;
+ try {
+ mCameraManager.setTorchMode(mCameraId, enabled);
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Couldn't set torch mode", e);
+ mFlashlightEnabled = false;
+ pendingError = true;
+ }
+ }
}
- if (enabled) {
- mHandler.post(mKillFlashlightRunnable);
+ dispatchModeChanged(mFlashlightEnabled);
+ if (pendingError) {
+ dispatchError();
}
}
public synchronized boolean isAvailable() {
- return mCameraAvailable;
+ return mTorchAvailable;
}
public void addListener(FlashlightListener l) {
@@ -127,42 +118,6 @@ public class FlashlightController {
}
}
- private void startDevice() throws CameraAccessException {
- mCameraManager.openCamera(getCameraId(), mCameraListener, mHandler);
- }
-
- private void startSession() throws CameraAccessException {
- mSurfaceTexture = new SurfaceTexture(false);
- Size size = getSmallestSize(mCameraDevice.getId());
- mSurfaceTexture.setDefaultBufferSize(size.getWidth(), size.getHeight());
- mSurface = new Surface(mSurfaceTexture);
- ArrayList<Surface> outputs = new ArrayList<>(1);
- outputs.add(mSurface);
- mCameraDevice.createCaptureSession(outputs, mSessionListener, mHandler);
- }
-
- private Size getSmallestSize(String cameraId) throws CameraAccessException {
- Size[] outputSizes = mCameraManager.getCameraCharacteristics(cameraId)
- .get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
- .getOutputSizes(SurfaceTexture.class);
- if (outputSizes == null || outputSizes.length == 0) {
- throw new IllegalStateException(
- "Camera " + cameraId + "doesn't support any outputSize.");
- }
- Size chosen = outputSizes[0];
- for (Size s : outputSizes) {
- if (chosen.getWidth() >= s.getWidth() && chosen.getHeight() >= s.getHeight()) {
- chosen = s;
- }
- }
- return chosen;
- }
-
- private void postUpdateFlashlight() {
- ensureHandler();
- mHandler.post(mUpdateFlashlightRunnable);
- }
-
private String getCameraId() throws CameraAccessException {
String[] ids = mCameraManager.getCameraIdList();
for (String id : ids) {
@@ -177,70 +132,12 @@ public class FlashlightController {
return null;
}
- private void updateFlashlight(boolean forceDisable) {
- try {
- boolean enabled;
- synchronized (this) {
- enabled = mFlashlightEnabled && !forceDisable;
- }
- if (enabled) {
- if (mCameraDevice == null) {
- startDevice();
- return;
- }
- if (mSession == null) {
- startSession();
- return;
- }
- if (mFlashlightRequest == null) {
- CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(
- CameraDevice.TEMPLATE_PREVIEW);
- builder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_TORCH);
- builder.addTarget(mSurface);
- CaptureRequest request = builder.build();
- mSession.capture(request, null, mHandler);
- mFlashlightRequest = request;
- }
- } else {
- if (mCameraDevice != null) {
- mCameraDevice.close();
- teardown();
- }
- }
-
- } catch (CameraAccessException|IllegalStateException|UnsupportedOperationException e) {
- Log.e(TAG, "Error in updateFlashlight", e);
- handleError();
- }
- }
-
- private void teardown() {
- mCameraDevice = null;
- mSession = null;
- mFlashlightRequest = null;
- if (mSurface != null) {
- mSurface.release();
- mSurfaceTexture.release();
- }
- mSurface = null;
- mSurfaceTexture = null;
- }
-
- private void handleError() {
- synchronized (this) {
- mFlashlightEnabled = false;
- }
- dispatchError();
- dispatchOff();
- updateFlashlight(true /* forceDisable */);
- }
-
- private void dispatchOff() {
- dispatchListeners(DISPATCH_OFF, false /* argument (ignored) */);
+ private void dispatchModeChanged(boolean enabled) {
+ dispatchListeners(DISPATCH_CHANGED, enabled);
}
private void dispatchError() {
- dispatchListeners(DISPATCH_ERROR, false /* argument (ignored) */);
+ dispatchListeners(DISPATCH_CHANGED, false /* argument (ignored) */);
}
private void dispatchAvailabilityChanged(boolean available) {
@@ -256,8 +153,8 @@ public class FlashlightController {
if (l != null) {
if (message == DISPATCH_ERROR) {
l.onFlashlightError();
- } else if (message == DISPATCH_OFF) {
- l.onFlashlightOff();
+ } else if (message == DISPATCH_CHANGED) {
+ l.onFlashlightChanged(argument);
} else if (message == DISPATCH_AVAILABILITY_CHANGED) {
l.onFlashlightAvailabilityChanged(argument);
}
@@ -280,106 +177,57 @@ public class FlashlightController {
}
}
- private final CameraDevice.StateListener mCameraListener = new CameraDevice.StateListener() {
- @Override
- public void onOpened(CameraDevice camera) {
- mCameraDevice = camera;
- postUpdateFlashlight();
- }
-
- @Override
- public void onDisconnected(CameraDevice camera) {
- if (mCameraDevice == camera) {
- dispatchOff();
- teardown();
- }
- }
-
- @Override
- public void onError(CameraDevice camera, int error) {
- Log.e(TAG, "Camera error: camera=" + camera + " error=" + error);
- if (camera == mCameraDevice || mCameraDevice == null) {
- handleError();
- }
- }
- };
+ private final CameraManager.TorchCallback mTorchCallback =
+ new CameraManager.TorchCallback() {
- private final CameraCaptureSession.StateListener mSessionListener =
- new CameraCaptureSession.StateListener() {
@Override
- public void onConfigured(CameraCaptureSession session) {
- if (session.getDevice() == mCameraDevice) {
- mSession = session;
- } else {
- session.close();
- }
- postUpdateFlashlight();
- }
-
- @Override
- public void onConfigureFailed(CameraCaptureSession session) {
- Log.e(TAG, "Configure failed.");
- if (mSession == null || mSession == session) {
- handleError();
- }
- }
- };
-
- private final Runnable mUpdateFlashlightRunnable = new Runnable() {
- @Override
- public void run() {
- updateFlashlight(false /* forceDisable */);
- }
- };
-
- private final Runnable mKillFlashlightRunnable = new Runnable() {
- @Override
- public void run() {
- synchronized (this) {
- mFlashlightEnabled = false;
+ public void onTorchModeUnavailable(String cameraId) {
+ if (TextUtils.equals(cameraId, mCameraId)) {
+ setCameraAvailable(false);
}
- updateFlashlight(true /* forceDisable */);
- dispatchOff();
}
- };
- private final CameraManager.AvailabilityCallback mAvailabilityCallback =
- new CameraManager.AvailabilityCallback() {
@Override
- public void onCameraAvailable(String cameraId) {
- if (DEBUG) Log.d(TAG, "onCameraAvailable(" + cameraId + ")");
- if (cameraId.equals(mCameraId)) {
+ public void onTorchModeChanged(String cameraId, boolean enabled) {
+ if (TextUtils.equals(cameraId, mCameraId)) {
setCameraAvailable(true);
- }
- }
-
- @Override
- public void onCameraUnavailable(String cameraId) {
- if (DEBUG) Log.d(TAG, "onCameraUnavailable(" + cameraId + ")");
- if (cameraId.equals(mCameraId)) {
- setCameraAvailable(false);
+ setTorchMode(enabled);
}
}
private void setCameraAvailable(boolean available) {
boolean changed;
synchronized (FlashlightController.this) {
- changed = mCameraAvailable != available;
- mCameraAvailable = available;
+ changed = mTorchAvailable != available;
+ mTorchAvailable = available;
}
if (changed) {
if (DEBUG) Log.d(TAG, "dispatchAvailabilityChanged(" + available + ")");
dispatchAvailabilityChanged(available);
}
}
+
+ private void setTorchMode(boolean enabled) {
+ boolean changed;
+ synchronized (FlashlightController.this) {
+ changed = mFlashlightEnabled != enabled;
+ mFlashlightEnabled = enabled;
+ }
+ if (changed) {
+ if (DEBUG) Log.d(TAG, "dispatchModeChanged(" + enabled + ")");
+ dispatchModeChanged(enabled);
+ }
+ }
};
public interface FlashlightListener {
/**
- * Called when the flashlight turns off unexpectedly.
+ * Called when the flashlight was turned off or on.
+ * @param enabled true if the flashlight is currently turned on.
*/
- void onFlashlightOff();
+ void onFlashlightChanged(boolean enabled);
+
/**
* Called when there is an error that turns the flashlight off.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
index 2e3e67a..1e40bab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
@@ -36,6 +36,7 @@ import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityEvent;
import android.widget.FrameLayout;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.ExpandHelper;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.R;
@@ -44,7 +45,8 @@ import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
-import java.util.ArrayList;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper.Callback, ExpandHelper.Callback,
ViewTreeObserver.OnComputeInternalInsetsListener {
@@ -56,6 +58,9 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper.
Rect mTmpRect = new Rect();
int[] mTmpTwoArray = new int[2];
+ private final int mHeadsUpNotificationDecay;
+ private final int mMinimumDisplayTime;
+
private final int mTouchSensitivityDelay;
private final float mMaxAlpha = 1f;
private final ArrayMap<String, Long> mSnoozedPackages;
@@ -66,6 +71,7 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper.
private PhoneStatusBar mBar;
+ private long mLingerUntilMs;
private long mStartTouchTime;
private ViewGroup mContentHolder;
private int mSnoozeLengthMs;
@@ -74,6 +80,14 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper.
private NotificationData.Entry mHeadsUp;
private int mUser;
private String mMostRecentPackageName;
+ private boolean mTouched;
+ private Clock mClock;
+
+ public static class Clock {
+ public long currentTimeMillis() {
+ return SystemClock.elapsedRealtime();
+ }
+ }
public HeadsUpNotificationView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
@@ -87,6 +101,24 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper.
mSnoozedPackages = new ArrayMap<>();
mDefaultSnoozeLengthMs = resources.getInteger(R.integer.heads_up_default_snooze_length_ms);
mSnoozeLengthMs = mDefaultSnoozeLengthMs;
+ mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time);
+ mHeadsUpNotificationDecay = resources.getInteger(R.integer.heads_up_notification_decay);
+ mClock = new Clock();
+ }
+
+ @VisibleForTesting
+ public HeadsUpNotificationView(Context context, Clock clock, SwipeHelper swipeHelper,
+ EdgeSwipeHelper edgeSwipeHelper, int headsUpNotificationDecay, int minimumDisplayTime,
+ int touchSensitivityDelay, int snoozeLength) {
+ super(context, null);
+ mClock = clock;
+ mSwipeHelper = swipeHelper;
+ mEdgeSwipeHelper = edgeSwipeHelper;
+ mMinimumDisplayTime = minimumDisplayTime;
+ mHeadsUpNotificationDecay = headsUpNotificationDecay;
+ mTouchSensitivityDelay = touchSensitivityDelay;
+ mSnoozedPackages = new ArrayMap<>();
+ mDefaultSnoozeLengthMs = snoozeLength;
}
public void updateResources() {
@@ -102,88 +134,147 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper.
mBar = bar;
}
+ public PhoneStatusBar getBar() {
+ return mBar;
+ }
+
public ViewGroup getHolder() {
return mContentHolder;
}
- public boolean showNotification(NotificationData.Entry headsUp) {
- if (mHeadsUp != null && headsUp != null && !mHeadsUp.key.equals(headsUp.key)) {
+ /**
+ * Called when posting a new notification to the heads up.
+ */
+ public void showNotification(NotificationData.Entry headsUp) {
+ if (DEBUG) Log.v(TAG, "showNotification");
+ if (mHeadsUp != null) {
// bump any previous heads up back to the shade
- release();
+ releaseImmediately();
+ }
+ mTouched = false;
+ updateNotification(headsUp, true);
+ mLingerUntilMs = mClock.currentTimeMillis() + mMinimumDisplayTime;
+ }
+
+ /**
+ * Called when updating or posting a notification to the heads up.
+ */
+ public void updateNotification(NotificationData.Entry headsUp, boolean alert) {
+ if (DEBUG) Log.v(TAG, "updateNotification");
+
+ if (mHeadsUp == headsUp) {
+ resetViewForHeadsup();
+ // This is an in-place update. Noting more to do.
+ return;
}
mHeadsUp = headsUp;
+
if (mContentHolder != null) {
mContentHolder.removeAllViews();
}
if (mHeadsUp != null) {
mMostRecentPackageName = mHeadsUp.notification.getPackageName();
- mHeadsUp.row.setSystemExpanded(true);
- mHeadsUp.row.setSensitive(false);
- mHeadsUp.row.setHeadsUp(true);
- mHeadsUp.row.setHideSensitive(
- false, false /* animated */, 0 /* delay */, 0 /* duration */);
- if (mContentHolder == null) {
- // too soon!
- return false;
+ if (mHeadsUp.row != null) {
+ resetViewForHeadsup();
}
- mContentHolder.setX(0);
- mContentHolder.setVisibility(View.VISIBLE);
- mContentHolder.setAlpha(mMaxAlpha);
- mContentHolder.addView(mHeadsUp.row);
- sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
- mSwipeHelper.snapChild(mContentHolder, 1f);
mStartTouchTime = SystemClock.elapsedRealtime() + mTouchSensitivityDelay;
+ if (mContentHolder != null) { // only null in tests and before we are attached to a window
+ mContentHolder.setX(0);
+ mContentHolder.setVisibility(View.VISIBLE);
+ mContentHolder.setAlpha(mMaxAlpha);
+ mContentHolder.addView(mHeadsUp.row);
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+
+ mSwipeHelper.snapChild(mContentHolder, 1f);
+ }
mHeadsUp.setInterruption();
-
- // 2. Animate mHeadsUpNotificationView in
+ }
+ if (alert) {
+ // Make sure the heads up window is open.
mBar.scheduleHeadsUpOpen();
-
- // 3. Set alarm to age the notification off
- mBar.resetHeadsUpDecayTimer();
+ mBar.scheduleHeadsUpDecay(mHeadsUpNotificationDecay);
}
- return true;
}
- @Override
- protected void onVisibilityChanged(View changedView, int visibility) {
- super.onVisibilityChanged(changedView, visibility);
- if (changedView.getVisibility() == VISIBLE) {
- sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ private void resetViewForHeadsup() {
+ if (mHeadsUp.row.areChildrenExpanded()) {
+ mHeadsUp.row.setChildrenExpanded(false /* expanded */, false /* animated */);
+ }
+ mHeadsUp.row.setSystemExpanded(true);
+ mHeadsUp.row.setSensitive(false);
+ mHeadsUp.row.setHeadsUp(true);
+ mHeadsUp.row.setTranslationY(0);
+ mHeadsUp.row.setTranslationZ(0);
+ mHeadsUp.row.setHideSensitive(
+ false, false /* animated */, 0 /* delay */, 0 /* duration */);
+ }
+
+ /**
+ * Possibly enter the lingering state by delaying the closing of the window.
+ *
+ * @return true if the notification has entered the lingering state.
+ */
+ private boolean startLingering(boolean removed) {
+ final long now = mClock.currentTimeMillis();
+ if (!mTouched && mHeadsUp != null && now < mLingerUntilMs) {
+ if (removed) {
+ mHeadsUp = null;
+ }
+ mBar.scheduleHeadsUpDecay(mLingerUntilMs - now);
+ return true;
}
+ return false;
}
- public boolean isShowing(String key) {
- return mHeadsUp != null && mHeadsUp.key.equals(key);
+ /**
+ * React to the removal of the notification in the heads up.
+ */
+ public void removeNotification(String key) {
+ if (DEBUG) Log.v(TAG, "remove");
+ if (mHeadsUp == null || !mHeadsUp.key.equals(key)) {
+ return;
+ }
+ if (!startLingering(/* removed */ true)) {
+ mHeadsUp = null;
+ releaseImmediately();
+ }
}
- /** Discard the Heads Up notification. */
- public void clear() {
- mHeadsUp = null;
- mBar.scheduleHeadsUpClose();
+ /**
+ * Ask for any current Heads Up notification to be pushed down into the shade.
+ */
+ public void release() {
+ if (DEBUG) Log.v(TAG, "release");
+ if (!startLingering(/* removed */ false)) {
+ releaseImmediately();
+ }
}
- /** Respond to dismissal of the Heads Up window. */
- public void dismiss() {
- if (mHeadsUp == null) return;
- if (mHeadsUp.notification.isClearable()) {
- mBar.onNotificationClear(mHeadsUp.notification);
- } else {
- release();
+ /**
+ * Push any current Heads Up notification down into the shade.
+ */
+ public void releaseImmediately() {
+ if (DEBUG) Log.v(TAG, "releaseImmediately");
+ if (mHeadsUp != null) {
+ mContentHolder.removeView(mHeadsUp.row);
+ mBar.displayNotificationFromHeadsUp(mHeadsUp);
}
mHeadsUp = null;
mBar.scheduleHeadsUpClose();
}
- /** Push any current Heads Up notification down into the shade. */
- public void release() {
- if (mHeadsUp != null) {
- mBar.displayNotificationFromHeadsUp(mHeadsUp.notification);
+ @Override
+ protected void onVisibilityChanged(View changedView, int visibility) {
+ super.onVisibilityChanged(changedView, visibility);
+ if (DEBUG) Log.v(TAG, "onVisibilityChanged: " + visibility);
+ if (changedView.getVisibility() == VISIBLE) {
+ mStartTouchTime = mClock.currentTimeMillis() + mTouchSensitivityDelay;
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
}
- mHeadsUp = null;
}
public boolean isSnoozed(String packageName) {
@@ -204,16 +295,15 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper.
mSnoozedPackages.put(snoozeKey(mMostRecentPackageName, mUser),
SystemClock.elapsedRealtime() + mSnoozeLengthMs);
}
- releaseAndClose();
+ releaseImmediately();
}
private static String snoozeKey(String packageName, int user) {
return user + "," + packageName;
}
- public void releaseAndClose() {
- release();
- mBar.scheduleHeadsUpClose();
+ public boolean isShowing(String key) {
+ return mHeadsUp != null && mHeadsUp.key.equals(key);
}
public NotificationData.Entry getEntry() {
@@ -226,19 +316,19 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper.
// ViewGroup methods
- private static final ViewOutlineProvider CONTENT_HOLDER_OUTLINE_PROVIDER =
- new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- int outlineLeft = view.getPaddingLeft();
- int outlineTop = view.getPaddingTop();
-
- // Apply padding to shadow.
- outline.setRect(outlineLeft, outlineTop,
- view.getWidth() - outlineLeft - view.getPaddingRight(),
- view.getHeight() - outlineTop - view.getPaddingBottom());
- }
- };
+private static final ViewOutlineProvider CONTENT_HOLDER_OUTLINE_PROVIDER =
+ new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ int outlineLeft = view.getPaddingLeft();
+ int outlineTop = view.getPaddingTop();
+
+ // Apply padding to shadow.
+ outline.setRect(outlineLeft, outlineTop,
+ view.getWidth() - outlineLeft - view.getPaddingRight(),
+ view.getHeight() - outlineTop - view.getPaddingBottom());
+ }
+ };
@Override
public void onAttachedToWindow() {
@@ -246,7 +336,7 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper.
float touchSlop = viewConfiguration.getScaledTouchSlop();
mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, getContext());
mSwipeHelper.setMaxSwipeProgress(mMaxAlpha);
- mEdgeSwipeHelper = new EdgeSwipeHelper(touchSlop);
+ mEdgeSwipeHelper = new EdgeSwipeHelper(this, touchSlop);
int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_max_height);
@@ -280,6 +370,7 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper.
getViewTreeObserver().addOnComputeInternalInsetsListener(this);
}
+
@Override
protected void onDetachedFromWindow() {
mContext.getContentResolver().unregisterContentObserver(mSettingsObserver);
@@ -288,11 +379,13 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper.
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (DEBUG) Log.v(TAG, "onInterceptTouchEvent()");
- if (SystemClock.elapsedRealtime() < mStartTouchTime) {
+ if (mClock.currentTimeMillis() < mStartTouchTime) {
return true;
}
+ mTouched = true;
return mEdgeSwipeHelper.onInterceptTouchEvent(ev)
|| mSwipeHelper.onInterceptTouchEvent(ev)
+ || mHeadsUp == null // lingering
|| super.onInterceptTouchEvent(ev);
}
@@ -314,12 +407,17 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper.
@Override
public boolean onTouchEvent(MotionEvent ev) {
- if (SystemClock.elapsedRealtime() < mStartTouchTime) {
+ if (mClock.currentTimeMillis() < mStartTouchTime) {
return false;
}
- mBar.resetHeadsUpDecayTimer();
+
+ final boolean wasRemoved = mHeadsUp == null;
+ if (!wasRemoved) {
+ mBar.scheduleHeadsUpDecay(mHeadsUpNotificationDecay);
+ }
return mEdgeSwipeHelper.onTouchEvent(ev)
|| mSwipeHelper.onTouchEvent(ev)
+ || wasRemoved
|| super.onTouchEvent(ev);
}
@@ -388,7 +486,11 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper.
@Override
public void onChildDismissed(View v) {
Log.v(TAG, "User swiped heads up to dismiss");
- mBar.onHeadsUpDismissed();
+ if (mHeadsUp != null && mHeadsUp.notification.isClearable()) {
+ mBar.onNotificationClear(mHeadsUp.notification);
+ mHeadsUp = null;
+ }
+ releaseImmediately();
}
@Override
@@ -442,14 +544,39 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper.
mUser = user;
}
- private class EdgeSwipeHelper implements Gefingerpoken {
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("HeadsUpNotificationView state:");
+ pw.print(" mTouchSensitivityDelay="); pw.println(mTouchSensitivityDelay);
+ pw.print(" mSnoozeLengthMs="); pw.println(mSnoozeLengthMs);
+ pw.print(" mLingerUntilMs="); pw.println(mLingerUntilMs);
+ pw.print(" mTouched="); pw.println(mTouched);
+ pw.print(" mMostRecentPackageName="); pw.println(mMostRecentPackageName);
+ pw.print(" mStartTouchTime="); pw.println(mStartTouchTime);
+ pw.print(" now="); pw.println(SystemClock.elapsedRealtime());
+ pw.print(" mUser="); pw.println(mUser);
+ if (mHeadsUp == null) {
+ pw.println(" mHeadsUp=null");
+ } else {
+ pw.print(" mHeadsUp="); pw.println(mHeadsUp.notification.getKey());
+ }
+ int N = mSnoozedPackages.size();
+ pw.println(" snoozed packages: " + N);
+ for (int i = 0; i < N; i++) {
+ pw.print(" "); pw.print(mSnoozedPackages.valueAt(i));
+ pw.print(", "); pw.println(mSnoozedPackages.keyAt(i));
+ }
+ }
+
+ public static class EdgeSwipeHelper implements Gefingerpoken {
private static final boolean DEBUG_EDGE_SWIPE = false;
private final float mTouchSlop;
+ private final HeadsUpNotificationView mHeadsUpView;
private boolean mConsuming;
private float mFirstY;
private float mFirstX;
- public EdgeSwipeHelper(float touchSlop) {
+ public EdgeSwipeHelper(HeadsUpNotificationView headsUpView, float touchSlop) {
+ mHeadsUpView = headsUpView;
mTouchSlop = touchSlop;
}
@@ -469,10 +596,10 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper.
final float daX = Math.abs(ev.getX() - mFirstX);
final float daY = Math.abs(dY);
if (!mConsuming && daX < daY && daY > mTouchSlop) {
- snooze();
+ mHeadsUpView.snooze();
if (dY > 0) {
if (DEBUG_EDGE_SWIPE) Log.d(TAG, "found an open");
- mBar.animateExpandNotificationsPanel();
+ mHeadsUpView.getBar().animateExpandNotificationsPanel();
}
mConsuming = true;
}
@@ -480,7 +607,7 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper.
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
- if (DEBUG_EDGE_SWIPE) Log.d(TAG, "action done" );
+ if (DEBUG_EDGE_SWIPE) Log.d(TAG, "action done");
mConsuming = false;
break;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java
index 0863c86..7ca91a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java
@@ -22,7 +22,6 @@ public interface HotspotController {
boolean isHotspotEnabled();
boolean isHotspotSupported();
void setHotspotEnabled(boolean enabled);
- boolean isProvisioningNeeded();
public interface Callback {
void onHotspotChanged(boolean enabled);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
index 5eff5a6..4bfd528 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
@@ -16,45 +16,38 @@
package com.android.systemui.statusbar.policy;
-import android.app.ActivityManager;
import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.net.ConnectivityManager;
import android.net.wifi.WifiManager;
-import android.os.SystemProperties;
import android.os.UserHandle;
-import android.provider.Settings;
import android.util.Log;
+import com.android.settingslib.TetherUtil;
+
import java.util.ArrayList;
public class HotspotControllerImpl implements HotspotController {
private static final String TAG = "HotspotController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- // Keep these in sync with Settings TetherService.java
- public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType";
- public static final String EXTRA_SET_ALARM = "extraSetAlarm";
- public static final String EXTRA_RUN_PROVISION = "extraRunProvision";
- public static final String EXTRA_ENABLE_WIFI_TETHER = "extraEnableWifiTether";
- // Keep this in sync with Settings TetherSettings.java
- public static final int WIFI_TETHERING = 0;
+ private static final Intent TETHER_SERVICE_INTENT = new Intent()
+ .putExtra(TetherUtil.EXTRA_ADD_TETHER_TYPE, TetherUtil.TETHERING_WIFI)
+ .putExtra(TetherUtil.EXTRA_SET_ALARM, true)
+ .putExtra(TetherUtil.EXTRA_RUN_PROVISION, true)
+ .putExtra(TetherUtil.EXTRA_ENABLE_WIFI_TETHER, true)
+ .setComponent(TetherUtil.TETHER_SERVICE);
private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
private final Receiver mReceiver = new Receiver();
private final Context mContext;
private final WifiManager mWifiManager;
- private final ConnectivityManager mConnectivityManager;
public HotspotControllerImpl(Context context) {
mContext = context;
mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
- mConnectivityManager = (ConnectivityManager)
- mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
}
public void addCallback(Callback callback) {
@@ -78,54 +71,17 @@ public class HotspotControllerImpl implements HotspotController {
@Override
public boolean isHotspotSupported() {
- final boolean isSecondaryUser = ActivityManager.getCurrentUser() != UserHandle.USER_OWNER;
- return !isSecondaryUser && mConnectivityManager.isTetheringSupported();
- }
-
- @Override
- public boolean isProvisioningNeeded() {
- // Keep in sync with other usage of config_mobile_hotspot_provision_app.
- // TetherSettings#isProvisioningNeeded and
- // ConnectivityManager#enforceTetherChangePermission
- String[] provisionApp = mContext.getResources().getStringArray(
- com.android.internal.R.array.config_mobile_hotspot_provision_app);
- if (SystemProperties.getBoolean("net.tethering.noprovisioning", false)
- || provisionApp == null) {
- return false;
- }
- return (provisionApp.length == 2);
+ return TetherUtil.isTetheringSupported(mContext);
}
@Override
public void setHotspotEnabled(boolean enabled) {
final ContentResolver cr = mContext.getContentResolver();
// Call provisioning app which is called when enabling Tethering from Settings
- if (enabled) {
- if (isProvisioningNeeded()) {
- String tetherEnable = mContext.getResources().getString(
- com.android.internal.R.string.config_wifi_tether_enable);
- Intent intent = new Intent();
- intent.putExtra(EXTRA_ADD_TETHER_TYPE, WIFI_TETHERING);
- intent.putExtra(EXTRA_SET_ALARM, true);
- intent.putExtra(EXTRA_RUN_PROVISION, true);
- intent.putExtra(EXTRA_ENABLE_WIFI_TETHER, true);
- intent.setComponent(ComponentName.unflattenFromString(tetherEnable));
- mContext.startServiceAsUser(intent, UserHandle.CURRENT);
- } else {
- int wifiState = mWifiManager.getWifiState();
- if ((wifiState == WifiManager.WIFI_STATE_ENABLING) ||
- (wifiState == WifiManager.WIFI_STATE_ENABLED)) {
- mWifiManager.setWifiEnabled(false);
- Settings.Global.putInt(cr, Settings.Global.WIFI_SAVED_STATE, 1);
- }
- mWifiManager.setWifiApEnabled(null, true);
- }
+ if (enabled && TetherUtil.isProvisioningNeeded(mContext)) {
+ mContext.startServiceAsUser(TETHER_SERVICE_INTENT, UserHandle.CURRENT);
} else {
- mWifiManager.setWifiApEnabled(null, false);
- if (Settings.Global.getInt(cr, Settings.Global.WIFI_SAVED_STATE, 0) == 1) {
- mWifiManager.setWifiEnabled(true);
- Settings.Global.putInt(cr, Settings.Global.WIFI_SAVED_STATE, 0);
- }
+ TetherUtil.setWifiTethering(enabled, mContext);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java
index 6998791..3f63b5f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java
@@ -26,7 +26,7 @@ import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
-import android.view.HardwareCanvas;
+import android.view.DisplayListCanvas;
import android.view.RenderNodeAnimator;
import android.view.View;
import android.view.animation.Interpolator;
@@ -106,7 +106,7 @@ public class KeyButtonRipple extends Drawable {
public void draw(Canvas canvas) {
mSupportHardware = canvas.isHardwareAccelerated();
if (mSupportHardware) {
- drawHardware((HardwareCanvas) canvas);
+ drawHardware((DisplayListCanvas) canvas);
} else {
drawSoftware(canvas);
}
@@ -118,7 +118,7 @@ public class KeyButtonRipple extends Drawable {
}
@Override
- public void setColorFilter(ColorFilter cf) {
+ public void setColorFilter(ColorFilter colorFilter) {
// Not supported.
}
@@ -131,7 +131,7 @@ public class KeyButtonRipple extends Drawable {
return getBounds().width() > getBounds().height();
}
- private void drawHardware(HardwareCanvas c) {
+ private void drawHardware(DisplayListCanvas c) {
if (mDrawingHardwareGlow) {
c.drawRoundRect(mLeftProp, mTopProp, mRightProp, mBottomProp, mRxProp, mRyProp,
mPaintProp);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
index b9cc0f9..6bc51fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar.policy;
-import android.animation.Animator;
-import android.animation.ObjectAnimator;
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.TypedArray;
@@ -26,7 +24,6 @@ import android.media.AudioManager;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.HapticFeedbackConstants;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
@@ -45,20 +42,13 @@ import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
public class KeyButtonView extends ImageView {
- private static final String TAG = "StatusBar.KeyButtonView";
- private static final boolean DEBUG = false;
-
- // TODO: Get rid of this
- public static final float DEFAULT_QUIESCENT_ALPHA = 1f;
private long mDownTime;
private int mCode;
private int mTouchSlop;
- private float mDrawingAlpha = 1f;
- private float mQuiescentAlpha = DEFAULT_QUIESCENT_ALPHA;
private boolean mSupportsLongpress = true;
private AudioManager mAudioManager;
- private Animator mAnimateToQuiescent = new ObjectAnimator();
+ private boolean mGestureAborted;
private final Runnable mCheckLongPress = new Runnable() {
public void run() {
@@ -89,9 +79,6 @@ public class KeyButtonView extends ImageView {
mSupportsLongpress = a.getBoolean(R.styleable.KeyButtonView_keyRepeat, true);
-
- setDrawingAlpha(mQuiescentAlpha);
-
a.recycle();
setClickable(true);
@@ -121,7 +108,7 @@ public class KeyButtonView extends ImageView {
}
@Override
- public boolean performAccessibilityAction(int action, Bundle arguments) {
+ public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
if (action == ACTION_CLICK && mCode != 0) {
sendEvent(KeyEvent.ACTION_DOWN, 0, SystemClock.uptimeMillis());
sendEvent(KeyEvent.ACTION_UP, 0);
@@ -134,47 +121,21 @@ public class KeyButtonView extends ImageView {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
return true;
}
- return super.performAccessibilityAction(action, arguments);
- }
-
- public void setQuiescentAlpha(float alpha, boolean animate) {
- mAnimateToQuiescent.cancel();
- alpha = Math.min(Math.max(alpha, 0), 1);
- if (alpha == mQuiescentAlpha && alpha == mDrawingAlpha) return;
- mQuiescentAlpha = alpha;
- if (DEBUG) Log.d(TAG, "New quiescent alpha = " + mQuiescentAlpha);
- if (animate) {
- mAnimateToQuiescent = animateToQuiescent();
- mAnimateToQuiescent.start();
- } else {
- setDrawingAlpha(mQuiescentAlpha);
- }
- }
-
- private ObjectAnimator animateToQuiescent() {
- return ObjectAnimator.ofFloat(this, "drawingAlpha", mQuiescentAlpha);
- }
-
- public float getQuiescentAlpha() {
- return mQuiescentAlpha;
- }
-
- public float getDrawingAlpha() {
- return mDrawingAlpha;
- }
-
- public void setDrawingAlpha(float x) {
- setImageAlpha((int) (x * 255));
- mDrawingAlpha = x;
+ return super.performAccessibilityActionInternal(action, arguments);
}
public boolean onTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
int x, y;
+ if (action == MotionEvent.ACTION_DOWN) {
+ mGestureAborted = false;
+ }
+ if (mGestureAborted) {
+ return false;
+ }
switch (action) {
case MotionEvent.ACTION_DOWN:
- //Log.d("KeyButtonView", "press");
mDownTime = SystemClock.uptimeMillis();
setPressed(true);
if (mCode != 0) {
@@ -248,6 +209,11 @@ public class KeyButtonView extends ImageView {
InputManager.getInstance().injectInputEvent(ev,
InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
}
+
+ public void abortCurrentGesture() {
+ setPressed(false);
+ mGestureAborted = true;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherScrim.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherScrim.java
index a5fc2fe..353e07d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherScrim.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherScrim.java
@@ -19,7 +19,6 @@ package com.android.systemui.statusbar.policy;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
-import android.graphics.LightingColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.RadialGradient;
@@ -48,7 +47,7 @@ public class KeyguardUserSwitcherScrim extends Drawable
public KeyguardUserSwitcherScrim(View host) {
host.addOnLayoutChangeListener(this);
- mDarkColor = host.getResources().getColor(
+ mDarkColor = host.getContext().getColor(
R.color.keyguard_user_switcher_background_gradient_color);
}
@@ -77,7 +76,7 @@ public class KeyguardUserSwitcherScrim extends Drawable
}
@Override
- public void setColorFilter(ColorFilter cf) {
+ public void setColorFilter(ColorFilter colorFilter) {
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
new file mode 100644
index 0000000..ba938cc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -0,0 +1,495 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.policy;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.NetworkCapabilities;
+import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.cdma.EriInfo;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.NetworkController.NetworkSignalChangedCallback;
+import com.android.systemui.statusbar.policy.NetworkControllerImpl.Config;
+import com.android.systemui.statusbar.policy.NetworkControllerImpl.SignalCluster;
+
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Objects;
+
+
+public 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 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();
+ }
+
+ public int getDataContentDescription() {
+ return getIcons().mDataContentDescription;
+ }
+
+ public void setAirplaneMode(boolean airplaneMode) {
+ mCurrentState.airplaneMode = airplaneMode;
+ notifyListenersIfNecessary();
+ }
+
+ public void setInetCondition(int inetCondition, int inetConditionForNetwork) {
+ // For mobile data, use general inet condition for phone signal indexing,
+ // and network specific for data indexing (I think this might be a bug, but
+ // keeping for now).
+ // TODO: Update with explanation of why.
+ mCurrentState.inetForNetwork = inetConditionForNetwork;
+ setInetCondition(inetCondition);
+ }
+
+ /**
+ * Start listening for phone state changes.
+ */
+ public void registerListener() {
+ mPhone.listen(mPhoneStateListener,
+ PhoneStateListener.LISTEN_SERVICE_STATE
+ | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS
+ | PhoneStateListener.LISTEN_CALL_STATE
+ | PhoneStateListener.LISTEN_DATA_CONNECTION_STATE
+ | PhoneStateListener.LISTEN_DATA_ACTIVITY);
+ }
+
+ /**
+ * Stop listening for phone state changes.
+ */
+ public void unregisterListener() {
+ mPhone.listen(mPhoneStateListener, 0);
+ }
+
+ /**
+ * Produce a mapping of data network types to icon groups for simple and quick use in
+ * updateTelephony.
+ */
+ private void mapIconSets() {
+ mNetworkToIconLookup.clear();
+
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_0, TelephonyIcons.THREE_G);
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_A, TelephonyIcons.THREE_G);
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_B, TelephonyIcons.THREE_G);
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EHRPD, TelephonyIcons.THREE_G);
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UMTS, TelephonyIcons.THREE_G);
+
+ if (!mConfig.showAtLeast3G) {
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ TelephonyIcons.UNKNOWN);
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EDGE, TelephonyIcons.E);
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_CDMA, TelephonyIcons.ONE_X);
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_1xRTT, TelephonyIcons.ONE_X);
+
+ mDefaultIcons = TelephonyIcons.G;
+ } else {
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ TelephonyIcons.THREE_G);
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EDGE,
+ TelephonyIcons.THREE_G);
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_CDMA,
+ TelephonyIcons.THREE_G);
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_1xRTT,
+ TelephonyIcons.THREE_G);
+ mDefaultIcons = TelephonyIcons.THREE_G;
+ }
+
+ MobileIconGroup hGroup = TelephonyIcons.THREE_G;
+ if (mConfig.hspaDataDistinguishable) {
+ hGroup = TelephonyIcons.H;
+ }
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSDPA, hGroup);
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSUPA, hGroup);
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSPA, hGroup);
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSPAP, hGroup);
+
+ if (mConfig.show4gForLte) {
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE, TelephonyIcons.FOUR_G);
+ } else {
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE, TelephonyIcons.LTE);
+ }
+ }
+
+ @Override
+ public void notifyListeners() {
+ MobileIconGroup icons = getIcons();
+
+ String contentDescription = getStringIfExists(getContentDescription());
+ String dataContentDescription = getStringIfExists(icons.mDataContentDescription);
+
+ boolean showDataIcon = mCurrentState.dataConnected && mCurrentState.inetForNetwork != 0
+ || mCurrentState.iconGroup == TelephonyIcons.ROAMING;
+
+ // Only send data sim callbacks to QS.
+ if (mCurrentState.dataSim) {
+ int qsTypeIcon = showDataIcon ? icons.mQsDataType[mCurrentState.inetForNetwork] : 0;
+ int length = mSignalsChangedCallbacks.size();
+ for (int i = 0; i < length; i++) {
+ mSignalsChangedCallbacks.get(i).onMobileDataSignalChanged(mCurrentState.enabled
+ && !mCurrentState.isEmergency,
+ getQsCurrentIconId(), contentDescription,
+ qsTypeIcon,
+ mCurrentState.dataConnected && mCurrentState.activityIn,
+ mCurrentState.dataConnected && mCurrentState.activityOut,
+ dataContentDescription,
+ mCurrentState.isEmergency ? null : mCurrentState.networkName,
+ // Only wide if actually showing something.
+ icons.mIsWide && qsTypeIcon != 0);
+ }
+ }
+ int typeIcon = showDataIcon ? icons.mDataType : 0;
+ int signalClustersLength = mSignalClusters.size();
+ for (int i = 0; i < signalClustersLength; i++) {
+ mSignalClusters.get(i).setMobileDataIndicators(
+ mCurrentState.enabled && !mCurrentState.airplaneMode,
+ getCurrentIconId(),
+ typeIcon,
+ contentDescription,
+ dataContentDescription,
+ // Only wide if actually showing something.
+ icons.mIsWide && typeIcon != 0,
+ mSubscriptionInfo.getSubscriptionId());
+ }
+ }
+
+ @Override
+ protected MobileState cleanState() {
+ return new MobileState();
+ }
+
+ private boolean hasService() {
+ if (mServiceState != null) {
+ // Consider the device to be in service if either voice or data
+ // service is available. Some SIM cards are marketed as data-only
+ // and do not support voice service, and on these SIM cards, we
+ // want to show signal bars for data service as well as the "no
+ // service" or "emergency calls only" text that indicates that voice
+ // is not available.
+ switch (mServiceState.getVoiceRegState()) {
+ case ServiceState.STATE_POWER_OFF:
+ return false;
+ case ServiceState.STATE_OUT_OF_SERVICE:
+ case ServiceState.STATE_EMERGENCY_ONLY:
+ return mServiceState.getDataRegState() == ServiceState.STATE_IN_SERVICE;
+ default:
+ return true;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ private boolean isCdma() {
+ return (mSignalStrength != null) && !mSignalStrength.isGsm();
+ }
+
+ public boolean isEmergencyOnly() {
+ return (mServiceState != null && mServiceState.isEmergencyOnly());
+ }
+
+ private boolean isRoaming() {
+ if (isCdma()) {
+ final int iconMode = mServiceState.getCdmaEriIconMode();
+ return mServiceState.getCdmaEriIconIndex() != EriInfo.ROAMING_INDICATOR_OFF
+ && (iconMode == EriInfo.ROAMING_ICON_MODE_NORMAL
+ || iconMode == EriInfo.ROAMING_ICON_MODE_FLASH);
+ } else {
+ return mServiceState != null && mServiceState.getRoaming();
+ }
+ }
+
+ public void handleBroadcast(Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION)) {
+ updateNetworkName(intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false),
+ intent.getStringExtra(TelephonyIntents.EXTRA_SPN),
+ intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false),
+ intent.getStringExtra(TelephonyIntents.EXTRA_PLMN));
+ notifyListenersIfNecessary();
+ } else if (action.equals(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)) {
+ updateDataSim();
+ }
+ }
+
+ private void updateDataSim() {
+ int defaultDataSub = SubscriptionManager.getDefaultDataSubId();
+ if (SubscriptionManager.isValidSubscriptionId(defaultDataSub)) {
+ mCurrentState.dataSim = defaultDataSub == mSubscriptionInfo.getSubscriptionId();
+ } else {
+ // There doesn't seem to be a data sim selected, however if
+ // there isn't a MobileSignalController with dataSim set, then
+ // QS won't get any callbacks and will be blank. Instead
+ // lets just assume we are the data sim (which will basically
+ // show one at random) in QS until one is selected. The user
+ // should pick one soon after, so we shouldn't be in this state
+ // for long.
+ mCurrentState.dataSim = true;
+ }
+ notifyListenersIfNecessary();
+ }
+
+ /**
+ * Updates the network's name based on incoming spn and plmn.
+ */
+ void updateNetworkName(boolean showSpn, String spn, boolean showPlmn, String plmn) {
+ if (CHATTY) {
+ Log.d("CarrierLabel", "updateNetworkName showSpn=" + showSpn + " spn=" + spn
+ + " showPlmn=" + showPlmn + " plmn=" + plmn);
+ }
+ StringBuilder str = new StringBuilder();
+ if (showPlmn && plmn != null) {
+ str.append(plmn);
+ }
+ if (showSpn && spn != null) {
+ if (str.length() != 0) {
+ str.append(mNetworkNameSeparator);
+ }
+ str.append(spn);
+ }
+ if (str.length() != 0) {
+ mCurrentState.networkName = str.toString();
+ } else {
+ mCurrentState.networkName = mNetworkNameDefault;
+ }
+ }
+
+ /**
+ * Updates the current state based on mServiceState, mSignalStrength, mDataNetType,
+ * mDataState, and mSimState. It should be called any time one of these is updated.
+ * This will call listeners if necessary.
+ */
+ private final void updateTelephony() {
+ if (DEBUG) {
+ Log.d(mTag, "updateTelephonySignalStrength: hasService=" + hasService()
+ + " ss=" + mSignalStrength);
+ }
+ mCurrentState.connected = hasService() && mSignalStrength != null;
+ if (mCurrentState.connected) {
+ if (!mSignalStrength.isGsm() && mConfig.alwaysShowCdmaRssi) {
+ mCurrentState.level = mSignalStrength.getCdmaLevel();
+ } else {
+ mCurrentState.level = mSignalStrength.getLevel();
+ }
+ }
+ if (mNetworkToIconLookup.indexOfKey(mDataNetType) >= 0) {
+ mCurrentState.iconGroup = mNetworkToIconLookup.get(mDataNetType);
+ } else {
+ mCurrentState.iconGroup = mDefaultIcons;
+ }
+ mCurrentState.dataConnected = mCurrentState.connected
+ && mDataState == TelephonyManager.DATA_CONNECTED;
+
+ if (isRoaming()) {
+ mCurrentState.iconGroup = TelephonyIcons.ROAMING;
+ }
+ if (isEmergencyOnly() != mCurrentState.isEmergency) {
+ mCurrentState.isEmergency = isEmergencyOnly();
+ mNetworkController.recalculateEmergency();
+ }
+ // Fill in the network name if we think we have it.
+ if (mCurrentState.networkName == mNetworkNameDefault && mServiceState != null
+ && mServiceState.getOperatorAlphaShort() != null) {
+ mCurrentState.networkName = mServiceState.getOperatorAlphaShort();
+ }
+ notifyListenersIfNecessary();
+ }
+
+ @VisibleForTesting
+ void setActivity(int activity) {
+ mCurrentState.activityIn = activity == TelephonyManager.DATA_ACTIVITY_INOUT
+ || activity == TelephonyManager.DATA_ACTIVITY_IN;
+ mCurrentState.activityOut = activity == TelephonyManager.DATA_ACTIVITY_INOUT
+ || activity == TelephonyManager.DATA_ACTIVITY_OUT;
+ notifyListenersIfNecessary();
+ }
+
+ @Override
+ public void dump(PrintWriter pw) {
+ super.dump(pw);
+ pw.println(" mSubscription=" + mSubscriptionInfo + ",");
+ pw.println(" mServiceState=" + mServiceState + ",");
+ pw.println(" mSignalStrength=" + mSignalStrength + ",");
+ pw.println(" mDataState=" + mDataState + ",");
+ pw.println(" mDataNetType=" + mDataNetType + ",");
+ }
+
+ class MobilePhoneStateListener extends PhoneStateListener {
+ public MobilePhoneStateListener(int subId) {
+ super(subId);
+ }
+
+ @Override
+ public void onSignalStrengthsChanged(SignalStrength signalStrength) {
+ if (DEBUG) {
+ Log.d(mTag, "onSignalStrengthsChanged signalStrength=" + signalStrength +
+ ((signalStrength == null) ? "" : (" level=" + signalStrength.getLevel())));
+ }
+ mSignalStrength = signalStrength;
+ updateTelephony();
+ }
+
+ @Override
+ public void onServiceStateChanged(ServiceState state) {
+ if (DEBUG) {
+ Log.d(mTag, "onServiceStateChanged voiceState=" + state.getVoiceRegState()
+ + " dataState=" + state.getDataRegState());
+ }
+ mServiceState = state;
+ updateTelephony();
+ }
+
+ @Override
+ public void onDataConnectionStateChanged(int state, int networkType) {
+ if (DEBUG) {
+ Log.d(mTag, "onDataConnectionStateChanged: state=" + state
+ + " type=" + networkType);
+ }
+ mDataState = state;
+ mDataNetType = networkType;
+ updateTelephony();
+ }
+
+ @Override
+ public void onDataActivity(int direction) {
+ if (DEBUG) {
+ Log.d(mTag, "onDataActivity: direction=" + direction);
+ }
+ setActivity(direction);
+ }
+ };
+
+ static class MobileIconGroup extends SignalController.IconGroup {
+ final int mDataContentDescription; // mContentDescriptionDataType
+ final int mDataType;
+ final boolean mIsWide;
+ final int[] mQsDataType;
+
+ public MobileIconGroup(String name, int[][] sbIcons, int[][] qsIcons, int[] contentDesc,
+ int sbNullState, int qsNullState, int sbDiscState, int qsDiscState,
+ int discContentDesc, int dataContentDesc, int dataType, boolean isWide,
+ int[] qsDataType) {
+ super(name, sbIcons, qsIcons, contentDesc, sbNullState, qsNullState, sbDiscState,
+ qsDiscState, discContentDesc);
+ mDataContentDescription = dataContentDesc;
+ mDataType = dataType;
+ mIsWide = isWide;
+ mQsDataType = qsDataType;
+ }
+ }
+
+ static class MobileState extends SignalController.State {
+ String networkName;
+ boolean dataSim;
+ boolean dataConnected;
+ boolean isEmergency;
+ boolean airplaneMode;
+ int inetForNetwork;
+
+ @Override
+ public void copyFrom(State s) {
+ super.copyFrom(s);
+ MobileState state = (MobileState) s;
+ dataSim = state.dataSim;
+ networkName = state.networkName;
+ dataConnected = state.dataConnected;
+ inetForNetwork = state.inetForNetwork;
+ isEmergency = state.isEmergency;
+ airplaneMode = state.airplaneMode;
+ }
+
+ @Override
+ protected void toString(StringBuilder builder) {
+ super.toString(builder);
+ builder.append(',');
+ builder.append("dataSim=").append(dataSim).append(',');
+ builder.append("networkName=").append(networkName).append(',');
+ builder.append("dataConnected=").append(dataConnected).append(',');
+ builder.append("inetForNetwork=").append(inetForNetwork).append(',');
+ builder.append("isEmergency=").append(isEmergency).append(',');
+ builder.append("airplaneMode=").append(airplaneMode);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return super.equals(o)
+ && Objects.equals(((MobileState) o).networkName, networkName)
+ && ((MobileState) o).dataSim == dataSim
+ && ((MobileState) o).dataConnected == dataConnected
+ && ((MobileState) o).isEmergency == isEmergency
+ && ((MobileState) o).airplaneMode == airplaneMode
+ && ((MobileState) o).inetForNetwork == inetForNetwork;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index 3cffc85..9212837 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -18,6 +18,10 @@ package com.android.systemui.statusbar.policy;
import android.content.Intent;
+import com.android.settingslib.wifi.AccessPoint;
+
+import java.util.List;
+
public interface NetworkController {
boolean hasMobileDataFeature();
@@ -50,25 +54,14 @@ public interface NetworkController {
void addAccessPointCallback(AccessPointCallback callback);
void removeAccessPointCallback(AccessPointCallback callback);
void scanForAccessPoints();
+ int getIcon(AccessPoint ap);
boolean connect(AccessPoint ap);
boolean canConfigWifi();
public interface AccessPointCallback {
- void onAccessPointsChanged(AccessPoint[] accessPoints);
+ void onAccessPointsChanged(List<AccessPoint> accessPoints);
void onSettingsActivityTriggered(Intent settingsIntent);
}
-
- public static class AccessPoint {
- public static final int NO_NETWORK = -1; // see WifiManager
-
- public int networkId;
- public int iconId;
- public String ssid;
- public boolean isConnected;
- public boolean isConfigured;
- public boolean hasSecurity;
- public int level; // 0 - 5
- }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 9a7f21e..bb3eb7a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -17,10 +17,7 @@
package com.android.systemui.statusbar.policy;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
-import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
-import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
-import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -28,34 +25,20 @@ import android.content.IntentFilter;
import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
-import android.net.NetworkInfo;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.AsyncTask;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.os.Messenger;
import android.provider.Settings;
-import android.telephony.PhoneStateListener;
-import android.telephony.ServiceState;
-import android.telephony.SignalStrength;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
-import android.text.format.DateFormat;
import android.util.Log;
-import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.IccCardConstants;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.TelephonyIntents;
-import com.android.internal.telephony.cdma.EriInfo;
-import com.android.internal.util.AsyncChannel;
import com.android.systemui.DemoMode;
import com.android.systemui.R;
@@ -69,7 +52,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
-import java.util.Objects;
/** Platform implementation of the network controller. **/
public class NetworkControllerImpl extends BroadcastReceiver
@@ -79,12 +61,6 @@ public class NetworkControllerImpl extends BroadcastReceiver
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;
@@ -106,12 +82,6 @@ public class NetworkControllerImpl extends BroadcastReceiver
private final AccessPointControllerImpl mAccessPoints;
private final MobileDataControllerImpl mMobileDataController;
- // Network types that replace the carrier label if the device does not support mobile data.
- private boolean mBluetoothTethered = false;
- private boolean mEthernetConnected = false;
-
- // state of inet connection
- private boolean mConnected = false;
private boolean mInetCondition; // Used for Logging and demo.
// BitSets indicating which network transport types (e.g., TRANSPORT_WIFI, TRANSPORT_MOBILE) are
@@ -129,8 +99,6 @@ public class NetworkControllerImpl extends BroadcastReceiver
// 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>();
@@ -188,7 +156,6 @@ public class NetworkControllerImpl extends BroadcastReceiver
// AIRPLANE_MODE_CHANGED is sent at boot; we've probably already missed it
updateAirplaneMode(true /* force callback */);
- mAccessPoints.setNetworkController(this);
}
private void registerListeners() {
@@ -244,11 +211,6 @@ public class NetworkControllerImpl extends BroadcastReceiver
listener.setEmergencyCallsOnly(isEmergencyOnly());
}
- public void addCarrierLabel(CarrierLabelListener listener) {
- mCarrierListeners.add(listener);
- refreshCarrierLabel();
- }
-
private void notifyMobileDataEnabled(boolean enabled) {
final int length = mSignalsChangedCallbacks.size();
for (int i = 0; i < length; i++) {
@@ -310,9 +272,6 @@ public class NetworkControllerImpl extends BroadcastReceiver
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) {
@@ -364,7 +323,6 @@ public class NetworkControllerImpl extends BroadcastReceiver
mCurrentUserId = newUserId;
mAccessPoints.onUserSwitched(newUserId);
updateConnectivity();
- refreshCarrierLabel();
}
@Override
@@ -376,14 +334,12 @@ public class NetworkControllerImpl extends BroadcastReceiver
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();
@@ -419,7 +375,6 @@ public class NetworkControllerImpl extends BroadcastReceiver
mobileSignalController.setConfiguration(mConfig);
}
refreshLocale();
- refreshCarrierLabel();
}
private void updateMobileControllers() {
@@ -525,7 +480,6 @@ public class NetworkControllerImpl extends BroadcastReceiver
mobileSignalController.setAirplaneMode(mAirplaneMode);
}
notifyListeners();
- refreshCarrierLabel();
}
}
@@ -589,10 +543,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
Log.d(TAG, "updateConnectivity: mValidatedTransports=" + mValidatedTransports);
}
- mConnected = !mConnectedTransports.isEmpty();
mInetCondition = !mValidatedTransports.isEmpty();
- mBluetoothTethered = mConnectedTransports.get(TRANSPORT_BLUETOOTH);
- mEthernetConnected = mConnectedTransports.get(TRANSPORT_ETHERNET);
pushConnectivityToSignals();
}
@@ -611,59 +562,6 @@ public class NetworkControllerImpl extends BroadcastReceiver
mValidatedTransports.get(mWifiSignalController.getTransportType()) ? 1 : 0);
}
- /**
- * Recalculate and update the carrier label.
- */
- void refreshCarrierLabel() {
- Context context = mContext;
-
- WifiSignalController.WifiState wifiState = mWifiSignalController.getState();
- String label = "";
- for (MobileSignalController controller : mMobileSignalControllers.values()) {
- label = controller.getLabel(label, mConnected, mHasMobileDataFeature);
- }
-
- // TODO Simplify this ugliness, some of the flows below shouldn't be possible anymore
- // but stay for the sake of history.
- if (mBluetoothTethered && !mHasMobileDataFeature) {
- label = mContext.getString(R.string.bluetooth_tethered);
- }
-
- if (mEthernetConnected && !mHasMobileDataFeature) {
- label = context.getString(R.string.ethernet_label);
- }
-
- if (mAirplaneMode && !isEmergencyOnly()) {
- // combined values from connected wifi take precedence over airplane mode
- if (wifiState.connected && mHasMobileDataFeature) {
- // Suppress "No internet connection." from mobile if wifi connected.
- label = "";
- } else {
- if (!mHasMobileDataFeature) {
- label = context.getString(
- R.string.status_bar_settings_signal_meter_disconnected);
- }
- }
- } else if (!isMobileDataConnected() && !wifiState.connected && !mBluetoothTethered &&
- !mEthernetConnected && !mHasMobileDataFeature) {
- // Pretty much no connection.
- label = context.getString(R.string.status_bar_settings_signal_meter_disconnected);
- }
-
- // for mobile devices, we always show mobile connection info here (SPN/PLMN)
- // for other devices, we show whatever network is connected
- // This is determined above by references to mHasMobileDataFeature.
- int length = mCarrierListeners.size();
- for (int i = 0; i < length; i++) {
- mCarrierListeners.get(i).setCarrierLabel(label);
- }
- }
-
- private boolean isMobileDataConnected() {
- MobileSignalController controller = getDataController();
- return controller != null ? controller.getState().dataConnected : false;
- }
-
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("NetworkController state:");
@@ -671,10 +569,6 @@ public class NetworkControllerImpl extends BroadcastReceiver
pw.print(" hasVoiceCallingFeature()=");
pw.println(hasVoiceCallingFeature());
- pw.println(" - Bluetooth ----");
- pw.print(" mBtReverseTethered=");
- pw.println(mBluetoothTethered);
-
pw.println(" - connectivity ------");
pw.print(" mConnectedTransports=");
pw.println(mConnectedTransports);
@@ -691,6 +585,8 @@ public class NetworkControllerImpl extends BroadcastReceiver
mobileSignalController.dump(pw);
}
mWifiSignalController.dump(pw);
+
+ mAccessPoints.dump(pw);
}
private boolean mDemoMode;
@@ -717,7 +613,6 @@ public class NetworkControllerImpl extends BroadcastReceiver
mWifiSignalController.resetLastState();
registerListeners();
notifyAllListeners();
- refreshCarrierLabel();
} else if (mDemoMode && command.equals(COMMAND_NETWORK)) {
String airplane = args.getString("airplane");
if (airplane != null) {
@@ -809,7 +704,6 @@ public class NetworkControllerImpl extends BroadcastReceiver
controller.getState().enabled = show;
controller.notifyListeners();
}
- refreshCarrierLabel();
}
}
@@ -821,965 +715,6 @@ public class NetworkControllerImpl extends BroadcastReceiver
};
};
- // TODO: Move to its own file.
- static class WifiSignalController extends
- SignalController<WifiSignalController.WifiState, SignalController.IconGroup> {
- private final WifiManager mWifiManager;
- private final AsyncChannel mWifiChannel;
- private final boolean mHasMobileData;
-
- public WifiSignalController(Context context, boolean hasMobileData,
- List<NetworkSignalChangedCallback> signalCallbacks,
- List<SignalCluster> signalClusters, NetworkControllerImpl networkController) {
- super("WifiSignalController", context, NetworkCapabilities.TRANSPORT_WIFI,
- signalCallbacks, signalClusters, networkController);
- mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
- mHasMobileData = hasMobileData;
- Handler handler = new WifiHandler();
- mWifiChannel = new AsyncChannel();
- Messenger wifiMessenger = mWifiManager.getWifiServiceMessenger();
- if (wifiMessenger != null) {
- mWifiChannel.connect(context, handler, wifiMessenger);
- }
- // WiFi only has one state.
- mCurrentState.iconGroup = mLastState.iconGroup = new IconGroup(
- "Wi-Fi Icons",
- WifiIcons.WIFI_SIGNAL_STRENGTH,
- WifiIcons.QS_WIFI_SIGNAL_STRENGTH,
- AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH,
- WifiIcons.WIFI_NO_NETWORK,
- WifiIcons.QS_WIFI_NO_NETWORK,
- WifiIcons.WIFI_NO_NETWORK,
- WifiIcons.QS_WIFI_NO_NETWORK,
- AccessibilityContentDescriptions.WIFI_NO_CONNECTION
- );
- }
-
- @Override
- protected WifiState cleanState() {
- return new WifiState();
- }
-
- @Override
- public void notifyListeners() {
- // only show wifi in the cluster if connected or if wifi-only
- boolean wifiVisible = mCurrentState.enabled
- && (mCurrentState.connected || !mHasMobileData);
- String wifiDesc = wifiVisible ? mCurrentState.ssid : null;
- boolean ssidPresent = wifiVisible && mCurrentState.ssid != null;
- String contentDescription = getStringIfExists(getContentDescription());
- int length = mSignalsChangedCallbacks.size();
- for (int i = 0; i < length; i++) {
- mSignalsChangedCallbacks.get(i).onWifiSignalChanged(mCurrentState.enabled,
- mCurrentState.connected, getQsCurrentIconId(),
- ssidPresent && mCurrentState.activityIn,
- ssidPresent && mCurrentState.activityOut, contentDescription, wifiDesc);
- }
-
- int signalClustersLength = mSignalClusters.size();
- for (int i = 0; i < signalClustersLength; i++) {
- mSignalClusters.get(i).setWifiIndicators(wifiVisible, getCurrentIconId(),
- contentDescription);
- }
- }
-
- /**
- * Extract wifi state directly from broadcasts about changes in wifi state.
- */
- public void handleBroadcast(Intent intent) {
- String action = intent.getAction();
- if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
- mCurrentState.enabled = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
- WifiManager.WIFI_STATE_UNKNOWN) == WifiManager.WIFI_STATE_ENABLED;
- } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
- final NetworkInfo networkInfo = (NetworkInfo)
- intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
- mCurrentState.connected = networkInfo != null && networkInfo.isConnected();
- // If Connected grab the signal strength and ssid.
- if (mCurrentState.connected) {
- // try getting it out of the intent first
- WifiInfo info = intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO) != null
- ? (WifiInfo) intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO)
- : mWifiManager.getConnectionInfo();
- if (info != null) {
- mCurrentState.ssid = getSsid(info);
- } else {
- mCurrentState.ssid = null;
- }
- } else if (!mCurrentState.connected) {
- mCurrentState.ssid = null;
- }
- } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
- // Default to -200 as its below WifiManager.MIN_RSSI.
- mCurrentState.rssi = intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200);
- mCurrentState.level = WifiManager.calculateSignalLevel(
- mCurrentState.rssi, WifiIcons.WIFI_LEVEL_COUNT);
- }
-
- notifyListenersIfNecessary();
- }
-
- private String getSsid(WifiInfo info) {
- String ssid = info.getSSID();
- if (ssid != null) {
- return ssid;
- }
- // OK, it's not in the connectionInfo; we have to go hunting for it
- List<WifiConfiguration> networks = mWifiManager.getConfiguredNetworks();
- int length = networks.size();
- for (int i = 0; i < length; i++) {
- if (networks.get(i).networkId == info.getNetworkId()) {
- return networks.get(i).SSID;
- }
- }
- return null;
- }
-
- @VisibleForTesting
- void setActivity(int wifiActivity) {
- mCurrentState.activityIn = wifiActivity == WifiManager.DATA_ACTIVITY_INOUT
- || wifiActivity == WifiManager.DATA_ACTIVITY_IN;
- mCurrentState.activityOut = wifiActivity == WifiManager.DATA_ACTIVITY_INOUT
- || wifiActivity == WifiManager.DATA_ACTIVITY_OUT;
- notifyListenersIfNecessary();
- }
-
- /**
- * Handler to receive the data activity on wifi.
- */
- class WifiHandler extends Handler {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
- if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
- mWifiChannel.sendMessage(Message.obtain(this,
- AsyncChannel.CMD_CHANNEL_FULL_CONNECTION));
- } else {
- Log.e(mTag, "Failed to connect to wifi");
- }
- break;
- case WifiManager.DATA_ACTIVITY_NOTIFICATION:
- setActivity(msg.arg1);
- break;
- default:
- // Ignore
- break;
- }
- }
- }
-
- static class WifiState extends SignalController.State {
- String ssid;
-
- @Override
- public void copyFrom(State s) {
- super.copyFrom(s);
- WifiState state = (WifiState) s;
- ssid = state.ssid;
- }
-
- @Override
- protected void toString(StringBuilder builder) {
- super.toString(builder);
- builder.append(',').append("ssid=").append(ssid);
- }
-
- @Override
- public boolean equals(Object o) {
- return super.equals(o)
- && Objects.equals(((WifiState) o).ssid, ssid);
- }
- }
- }
-
- // TODO: Move to its own file.
- public static class MobileSignalController extends SignalController<
- MobileSignalController.MobileState, MobileSignalController.MobileIconGroup> {
- private final TelephonyManager mPhone;
- private final String mNetworkNameDefault;
- private final String mNetworkNameSeparator;
- @VisibleForTesting
- final PhoneStateListener mPhoneStateListener;
- // Save entire info for logging, we only use the id.
- private final SubscriptionInfo mSubscriptionInfo;
-
- // @VisibleForDemoMode
- final SparseArray<MobileIconGroup> mNetworkToIconLookup;
-
- // Since some pieces of the phone state are interdependent we store it locally,
- // this could potentially become part of MobileState for simplification/complication
- // of code.
- private IccCardConstants.State mSimState = IccCardConstants.State.READY;
- private int mDataNetType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
- private int mDataState = TelephonyManager.DATA_DISCONNECTED;
- private ServiceState mServiceState;
- private SignalStrength mSignalStrength;
- private MobileIconGroup mDefaultIcons;
- private Config mConfig;
-
- // TODO: Reduce number of vars passed in, if we have the NetworkController, probably don't
- // need listener lists anymore.
- public MobileSignalController(Context context, Config config, boolean hasMobileData,
- TelephonyManager phone, List<NetworkSignalChangedCallback> signalCallbacks,
- List<SignalCluster> signalClusters, NetworkControllerImpl networkController,
- SubscriptionInfo info) {
- super("MobileSignalController(" + info.getSubscriptionId() + ")", context,
- NetworkCapabilities.TRANSPORT_CELLULAR, signalCallbacks, signalClusters,
- networkController);
- mNetworkToIconLookup = new SparseArray<>();
- mConfig = config;
- mPhone = phone;
- mSubscriptionInfo = info;
- mPhoneStateListener = new MobilePhoneStateListener(info.getSubscriptionId());
- mNetworkNameSeparator = getStringIfExists(R.string.status_bar_network_name_separator);
- mNetworkNameDefault = getStringIfExists(
- com.android.internal.R.string.lockscreen_carrier_default);
-
- mapIconSets();
-
- mLastState.networkName = mCurrentState.networkName = mNetworkNameDefault;
- mLastState.enabled = mCurrentState.enabled = hasMobileData;
- mLastState.iconGroup = mCurrentState.iconGroup = mDefaultIcons;
- // Get initial data sim state.
- updateDataSim();
- }
-
- public void setConfiguration(Config config) {
- mConfig = config;
- mapIconSets();
- updateTelephony();
- }
-
- /**
- * Get (the mobile parts of) the carrier string.
- *
- * @param currentLabel can be used for concatenation, currently just empty
- * @param connected whether the device has connection to the internet at all
- * @param isMobileLabel whether to always return the network or just when data is connected
- */
- public String getLabel(String currentLabel, boolean connected, boolean isMobileLabel) {
- if (!mCurrentState.enabled) {
- return "";
- } else {
- String mobileLabel = "";
- // We want to show the carrier name if in service and either:
- // - We are connected to mobile data, or
- // - We are not connected to mobile data, as long as the *reason* packets are not
- // being routed over that link is that we have better connectivity via wifi.
- // If data is disconnected for some other reason but wifi (or ethernet/bluetooth)
- // is connected, we show nothing.
- // Otherwise (nothing connected) we show "No internet connection".
- if (mCurrentState.dataConnected) {
- mobileLabel = mCurrentState.networkName;
- } else if (connected || mCurrentState.isEmergency) {
- if (mCurrentState.connected || mCurrentState.isEmergency) {
- // The isEmergencyOnly test covers the case of a phone with no SIM
- mobileLabel = mCurrentState.networkName;
- }
- } else {
- mobileLabel = mContext.getString(
- R.string.status_bar_settings_signal_meter_disconnected);
- }
-
- if (currentLabel.length() != 0) {
- currentLabel = currentLabel + mNetworkNameSeparator;
- }
- // Now for things that should only be shown when actually using mobile data.
- if (isMobileLabel) {
- return currentLabel + mobileLabel;
- } else {
- return currentLabel
- + (mCurrentState.dataConnected ? mobileLabel : currentLabel);
- }
- }
- }
-
- public int getDataContentDescription() {
- return getIcons().mDataContentDescription;
- }
-
- public void setAirplaneMode(boolean airplaneMode) {
- mCurrentState.airplaneMode = airplaneMode;
- notifyListenersIfNecessary();
- }
-
- public void setInetCondition(int inetCondition, int inetConditionForNetwork) {
- // For mobile data, use general inet condition for phone signal indexing,
- // and network specific for data indexing (I think this might be a bug, but
- // keeping for now).
- // TODO: Update with explanation of why.
- mCurrentState.inetForNetwork = inetConditionForNetwork;
- setInetCondition(inetCondition);
- }
-
- /**
- * Start listening for phone state changes.
- */
- public void registerListener() {
- mPhone.listen(mPhoneStateListener,
- PhoneStateListener.LISTEN_SERVICE_STATE
- | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS
- | PhoneStateListener.LISTEN_CALL_STATE
- | PhoneStateListener.LISTEN_DATA_CONNECTION_STATE
- | PhoneStateListener.LISTEN_DATA_ACTIVITY);
- }
-
- /**
- * Stop listening for phone state changes.
- */
- public void unregisterListener() {
- mPhone.listen(mPhoneStateListener, 0);
- }
-
- /**
- * Produce a mapping of data network types to icon groups for simple and quick use in
- * updateTelephony.
- */
- private void mapIconSets() {
- mNetworkToIconLookup.clear();
-
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_0, TelephonyIcons.THREE_G);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_A, TelephonyIcons.THREE_G);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_B, TelephonyIcons.THREE_G);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EHRPD, TelephonyIcons.THREE_G);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UMTS, TelephonyIcons.THREE_G);
-
- if (!mConfig.showAtLeast3G) {
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UNKNOWN,
- TelephonyIcons.UNKNOWN);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EDGE, TelephonyIcons.E);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_CDMA, TelephonyIcons.ONE_X);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_1xRTT, TelephonyIcons.ONE_X);
-
- mDefaultIcons = TelephonyIcons.G;
- } else {
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UNKNOWN,
- TelephonyIcons.THREE_G);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EDGE,
- TelephonyIcons.THREE_G);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_CDMA,
- TelephonyIcons.THREE_G);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_1xRTT,
- TelephonyIcons.THREE_G);
- mDefaultIcons = TelephonyIcons.THREE_G;
- }
-
- MobileIconGroup hGroup = TelephonyIcons.THREE_G;
- if (mConfig.hspaDataDistinguishable) {
- hGroup = TelephonyIcons.H;
- }
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSDPA, hGroup);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSUPA, hGroup);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSPA, hGroup);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSPAP, hGroup);
-
- if (mConfig.show4gForLte) {
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE, TelephonyIcons.FOUR_G);
- } else {
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE, TelephonyIcons.LTE);
- }
- }
-
- @Override
- public void notifyListeners() {
- MobileIconGroup icons = getIcons();
-
- String contentDescription = getStringIfExists(getContentDescription());
- String dataContentDescription = getStringIfExists(icons.mDataContentDescription);
-
- boolean showDataIcon = mCurrentState.dataConnected && mCurrentState.inetForNetwork != 0
- || mCurrentState.iconGroup == TelephonyIcons.ROAMING;
-
- // Only send data sim callbacks to QS.
- if (mCurrentState.dataSim) {
- int qsTypeIcon = showDataIcon ? icons.mQsDataType[mCurrentState.inetForNetwork] : 0;
- int length = mSignalsChangedCallbacks.size();
- for (int i = 0; i < length; i++) {
- mSignalsChangedCallbacks.get(i).onMobileDataSignalChanged(mCurrentState.enabled
- && !mCurrentState.isEmergency,
- getQsCurrentIconId(), contentDescription,
- qsTypeIcon,
- mCurrentState.dataConnected && mCurrentState.activityIn,
- mCurrentState.dataConnected && mCurrentState.activityOut,
- dataContentDescription,
- mCurrentState.isEmergency ? null : mCurrentState.networkName,
- // Only wide if actually showing something.
- icons.mIsWide && qsTypeIcon != 0);
- }
- }
- int typeIcon = showDataIcon ? icons.mDataType : 0;
- int signalClustersLength = mSignalClusters.size();
- for (int i = 0; i < signalClustersLength; i++) {
- mSignalClusters.get(i).setMobileDataIndicators(
- mCurrentState.enabled && !mCurrentState.airplaneMode,
- getCurrentIconId(),
- typeIcon,
- contentDescription,
- dataContentDescription,
- // Only wide if actually showing something.
- icons.mIsWide && typeIcon != 0,
- mSubscriptionInfo.getSubscriptionId());
- }
- }
-
- @Override
- protected MobileState cleanState() {
- return new MobileState();
- }
-
- private boolean hasService() {
- if (mServiceState != null) {
- // Consider the device to be in service if either voice or data
- // service is available. Some SIM cards are marketed as data-only
- // and do not support voice service, and on these SIM cards, we
- // want to show signal bars for data service as well as the "no
- // service" or "emergency calls only" text that indicates that voice
- // is not available.
- switch (mServiceState.getVoiceRegState()) {
- case ServiceState.STATE_POWER_OFF:
- return false;
- case ServiceState.STATE_OUT_OF_SERVICE:
- case ServiceState.STATE_EMERGENCY_ONLY:
- return mServiceState.getDataRegState() == ServiceState.STATE_IN_SERVICE;
- default:
- return true;
- }
- } else {
- return false;
- }
- }
-
- private boolean isCdma() {
- return (mSignalStrength != null) && !mSignalStrength.isGsm();
- }
-
- public boolean isEmergencyOnly() {
- return (mServiceState != null && mServiceState.isEmergencyOnly());
- }
-
- private boolean isRoaming() {
- if (isCdma()) {
- final int iconMode = mServiceState.getCdmaEriIconMode();
- return mServiceState.getCdmaEriIconIndex() != EriInfo.ROAMING_INDICATOR_OFF
- && (iconMode == EriInfo.ROAMING_ICON_MODE_NORMAL
- || iconMode == EriInfo.ROAMING_ICON_MODE_FLASH);
- } else {
- return mServiceState != null && mServiceState.getRoaming();
- }
- }
-
- public void handleBroadcast(Intent intent) {
- String action = intent.getAction();
- if (action.equals(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION)) {
- updateNetworkName(intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false),
- intent.getStringExtra(TelephonyIntents.EXTRA_SPN),
- intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false),
- intent.getStringExtra(TelephonyIntents.EXTRA_PLMN));
- notifyListenersIfNecessary();
- } else if (action.equals(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)) {
- updateDataSim();
- }
- }
-
- private void updateDataSim() {
- int defaultDataSub = SubscriptionManager.getDefaultDataSubId();
- if (SubscriptionManager.isValidSubscriptionId(defaultDataSub)) {
- mCurrentState.dataSim = defaultDataSub == mSubscriptionInfo.getSubscriptionId();
- } else {
- // There doesn't seem to be a data sim selected, however if
- // there isn't a MobileSignalController with dataSim set, then
- // QS won't get any callbacks and will be blank. Instead
- // lets just assume we are the data sim (which will basically
- // show one at random) in QS until one is selected. The user
- // should pick one soon after, so we shouldn't be in this state
- // for long.
- mCurrentState.dataSim = true;
- }
- notifyListenersIfNecessary();
- }
-
- /**
- * Updates the network's name based on incoming spn and plmn.
- */
- void updateNetworkName(boolean showSpn, String spn, boolean showPlmn, String plmn) {
- if (CHATTY) {
- Log.d("CarrierLabel", "updateNetworkName showSpn=" + showSpn + " spn=" + spn
- + " showPlmn=" + showPlmn + " plmn=" + plmn);
- }
- StringBuilder str = new StringBuilder();
- if (showPlmn && plmn != null) {
- str.append(plmn);
- }
- if (showSpn && spn != null) {
- if (str.length() != 0) {
- str.append(mNetworkNameSeparator);
- }
- str.append(spn);
- }
- if (str.length() != 0) {
- mCurrentState.networkName = str.toString();
- } else {
- mCurrentState.networkName = mNetworkNameDefault;
- }
- }
-
- /**
- * Updates the current state based on mServiceState, mSignalStrength, mDataNetType,
- * mDataState, and mSimState. It should be called any time one of these is updated.
- * This will call listeners if necessary.
- */
- private final void updateTelephony() {
- if (DEBUG) {
- Log.d(TAG, "updateTelephonySignalStrength: hasService=" + hasService()
- + " ss=" + mSignalStrength);
- }
- mCurrentState.connected = hasService() && mSignalStrength != null;
- if (mCurrentState.connected) {
- if (!mSignalStrength.isGsm() && mConfig.alwaysShowCdmaRssi) {
- mCurrentState.level = mSignalStrength.getCdmaLevel();
- } else {
- mCurrentState.level = mSignalStrength.getLevel();
- }
- }
- if (mNetworkToIconLookup.indexOfKey(mDataNetType) >= 0) {
- mCurrentState.iconGroup = mNetworkToIconLookup.get(mDataNetType);
- } else {
- mCurrentState.iconGroup = mDefaultIcons;
- }
- mCurrentState.dataConnected = mCurrentState.connected
- && mDataState == TelephonyManager.DATA_CONNECTED;
-
- if (isRoaming()) {
- mCurrentState.iconGroup = TelephonyIcons.ROAMING;
- }
- if (isEmergencyOnly() != mCurrentState.isEmergency) {
- mCurrentState.isEmergency = isEmergencyOnly();
- mNetworkController.recalculateEmergency();
- }
- // Fill in the network name if we think we have it.
- if (mCurrentState.networkName == mNetworkNameDefault && mServiceState != null
- && mServiceState.getOperatorAlphaShort() != null) {
- mCurrentState.networkName = mServiceState.getOperatorAlphaShort();
- }
- notifyListenersIfNecessary();
- }
-
- @VisibleForTesting
- void setActivity(int activity) {
- mCurrentState.activityIn = activity == TelephonyManager.DATA_ACTIVITY_INOUT
- || activity == TelephonyManager.DATA_ACTIVITY_IN;
- mCurrentState.activityOut = activity == TelephonyManager.DATA_ACTIVITY_INOUT
- || activity == TelephonyManager.DATA_ACTIVITY_OUT;
- notifyListenersIfNecessary();
- }
-
- @Override
- public void dump(PrintWriter pw) {
- super.dump(pw);
- pw.println(" mSubscription=" + mSubscriptionInfo + ",");
- pw.println(" mServiceState=" + mServiceState + ",");
- pw.println(" mSignalStrength=" + mSignalStrength + ",");
- pw.println(" mDataState=" + mDataState + ",");
- pw.println(" mDataNetType=" + mDataNetType + ",");
- }
-
- class MobilePhoneStateListener extends PhoneStateListener {
- public MobilePhoneStateListener(int subId) {
- super(subId);
- }
-
- @Override
- public void onSignalStrengthsChanged(SignalStrength signalStrength) {
- if (DEBUG) {
- Log.d(mTag, "onSignalStrengthsChanged signalStrength=" + signalStrength +
- ((signalStrength == null) ? "" : (" level=" + signalStrength.getLevel())));
- }
- mSignalStrength = signalStrength;
- updateTelephony();
- }
-
- @Override
- public void onServiceStateChanged(ServiceState state) {
- if (DEBUG) {
- Log.d(mTag, "onServiceStateChanged voiceState=" + state.getVoiceRegState()
- + " dataState=" + state.getDataRegState());
- }
- mServiceState = state;
- updateTelephony();
- }
-
- @Override
- public void onDataConnectionStateChanged(int state, int networkType) {
- if (DEBUG) {
- Log.d(mTag, "onDataConnectionStateChanged: state=" + state
- + " type=" + networkType);
- }
- mDataState = state;
- mDataNetType = networkType;
- updateTelephony();
- }
-
- @Override
- public void onDataActivity(int direction) {
- if (DEBUG) {
- Log.d(mTag, "onDataActivity: direction=" + direction);
- }
- setActivity(direction);
- }
- };
-
- static class MobileIconGroup extends SignalController.IconGroup {
- final int mDataContentDescription; // mContentDescriptionDataType
- final int mDataType;
- final boolean mIsWide;
- final int[] mQsDataType;
-
- public MobileIconGroup(String name, int[][] sbIcons, int[][] qsIcons, int[] contentDesc,
- int sbNullState, int qsNullState, int sbDiscState, int qsDiscState,
- int discContentDesc, int dataContentDesc, int dataType, boolean isWide,
- int[] qsDataType) {
- super(name, sbIcons, qsIcons, contentDesc, sbNullState, qsNullState, sbDiscState,
- qsDiscState, discContentDesc);
- mDataContentDescription = dataContentDesc;
- mDataType = dataType;
- mIsWide = isWide;
- mQsDataType = qsDataType;
- }
- }
-
- static class MobileState extends SignalController.State {
- String networkName;
- boolean dataSim;
- boolean dataConnected;
- boolean isEmergency;
- boolean airplaneMode;
- int inetForNetwork;
-
- @Override
- public void copyFrom(State s) {
- super.copyFrom(s);
- MobileState state = (MobileState) s;
- dataSim = state.dataSim;
- networkName = state.networkName;
- dataConnected = state.dataConnected;
- inetForNetwork = state.inetForNetwork;
- isEmergency = state.isEmergency;
- airplaneMode = state.airplaneMode;
- }
-
- @Override
- protected void toString(StringBuilder builder) {
- super.toString(builder);
- builder.append(',');
- builder.append("dataSim=").append(dataSim).append(',');
- builder.append("networkName=").append(networkName).append(',');
- builder.append("dataConnected=").append(dataConnected).append(',');
- builder.append("inetForNetwork=").append(inetForNetwork).append(',');
- builder.append("isEmergency=").append(isEmergency).append(',');
- builder.append("airplaneMode=").append(airplaneMode);
- }
-
- @Override
- public boolean equals(Object o) {
- return super.equals(o)
- && Objects.equals(((MobileState) o).networkName, networkName)
- && ((MobileState) o).dataSim == dataSim
- && ((MobileState) o).dataConnected == dataConnected
- && ((MobileState) o).isEmergency == isEmergency
- && ((MobileState) o).airplaneMode == airplaneMode
- && ((MobileState) o).inetForNetwork == inetForNetwork;
- }
- }
- }
-
- /**
- * 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();
- }
- }
- }
-
- public T getState() {
- return mCurrentState;
- }
-
- public int getTransportType() {
- return mTransportType;
- }
-
- public void setInetCondition(int inetCondition) {
- mCurrentState.inetCondition = inetCondition;
- notifyListenersIfNecessary();
- }
-
- /**
- * Used at the end of demo mode to clear out any ugly state that it has created.
- * Since we haven't had any callbacks, then isDirty will not have been triggered,
- * so we can just take the last good state directly from there.
- *
- * Used for demo mode.
- */
- void resetLastState() {
- mCurrentState.copyFrom(mLastState);
- }
-
- /**
- * Determines if the state of this signal controller has changed and
- * needs to trigger callbacks related to it.
- */
- public boolean isDirty() {
- if (!mLastState.equals(mCurrentState)) {
- if (DEBUG) {
- Log.d(mTag, "Change in state from: " + mLastState + "\n"
- + "\tto: " + mCurrentState);
- }
- return true;
- }
- return false;
- }
-
- public void saveLastState() {
- if (RECORD_HISTORY) {
- recordLastState();
- }
- // Updates the current time.
- mCurrentState.time = System.currentTimeMillis();
- mLastState.copyFrom(mCurrentState);
- }
-
- /**
- * Gets the signal icon for QS based on current state of connected, enabled, and level.
- */
- public int getQsCurrentIconId() {
- if (mCurrentState.connected) {
- return getIcons().mQsIcons[mCurrentState.inetCondition][mCurrentState.level];
- } else if (mCurrentState.enabled) {
- return getIcons().mQsDiscState;
- } else {
- return getIcons().mQsNullState;
- }
- }
-
- /**
- * Gets the signal icon for SB based on current state of connected, enabled, and level.
- */
- public int getCurrentIconId() {
- if (mCurrentState.connected) {
- return getIcons().mSbIcons[mCurrentState.inetCondition][mCurrentState.level];
- } else if (mCurrentState.enabled) {
- return getIcons().mSbDiscState;
- } else {
- return getIcons().mSbNullState;
- }
- }
-
- /**
- * Gets the content description id for the signal based on current state of connected and
- * level.
- */
- public int getContentDescription() {
- if (mCurrentState.connected) {
- return getIcons().mContentDesc[mCurrentState.level];
- } else {
- return getIcons().mDiscContentDesc;
- }
- }
-
- public void notifyListenersIfNecessary() {
- if (isDirty()) {
- saveLastState();
- notifyListeners();
- mNetworkController.refreshCarrierLabel();
- }
- }
-
- /**
- * Returns the resource if resId is not 0, and an empty string otherwise.
- */
- protected String getStringIfExists(int resId) {
- return resId != 0 ? mContext.getString(resId) : "";
- }
-
- protected I getIcons() {
- return (I) mCurrentState.iconGroup;
- }
-
- /**
- * Saves the last state of any changes, so we can log the current
- * and last value of any state data.
- */
- protected void recordLastState() {
- mHistory[mHistoryIndex++ & (HISTORY_SIZE - 1)].copyFrom(mLastState);
- }
-
- public void dump(PrintWriter pw) {
- pw.println(" - " + mTag + " -----");
- pw.println(" Current State: " + mCurrentState);
- if (RECORD_HISTORY) {
- // Count up the states that actually contain time stamps, and only display those.
- int size = 0;
- for (int i = 0; i < HISTORY_SIZE; i++) {
- if (mHistory[i].time != 0) size++;
- }
- // Print out the previous states in ordered number.
- for (int i = mHistoryIndex + HISTORY_SIZE - 1;
- i >= mHistoryIndex + HISTORY_SIZE - size; i--) {
- pw.println(" Previous State(" + (mHistoryIndex + HISTORY_SIZE - i) + ": "
- + mHistory[i & (HISTORY_SIZE - 1)]);
- }
- }
- }
-
- /**
- * Trigger callbacks based on current state. The callbacks should be completely
- * based on current state, and only need to be called in the scenario where
- * mCurrentState != mLastState.
- */
- public abstract void notifyListeners();
-
- /**
- * Generate a blank T.
- */
- protected abstract T cleanState();
-
- /*
- * Holds icons for a given state. Arrays are generally indexed as inet
- * state (full connectivity or not) first, and second dimension as
- * signal strength.
- */
- static class IconGroup {
- final int[][] mSbIcons;
- final int[][] mQsIcons;
- final int[] mContentDesc;
- final int mSbNullState;
- final int mQsNullState;
- final int mSbDiscState;
- final int mQsDiscState;
- final int mDiscContentDesc;
- // For logging.
- final String mName;
-
- public IconGroup(String name, int[][] sbIcons, int[][] qsIcons, int[] contentDesc,
- int sbNullState, int qsNullState, int sbDiscState, int qsDiscState,
- int discContentDesc) {
- mName = name;
- mSbIcons = sbIcons;
- mQsIcons = qsIcons;
- mContentDesc = contentDesc;
- mSbNullState = sbNullState;
- mQsNullState = qsNullState;
- mSbDiscState = sbDiscState;
- mQsDiscState = qsDiscState;
- mDiscContentDesc = discContentDesc;
- }
-
- @Override
- public String toString() {
- return "IconGroup(" + mName + ")";
- }
- }
-
- static class State {
- boolean connected;
- boolean enabled;
- boolean activityIn;
- boolean activityOut;
- int level;
- IconGroup iconGroup;
- int inetCondition;
- int rssi; // Only for logging.
-
- // Not used for comparison, just used for logging.
- long time;
-
- public void copyFrom(State state) {
- connected = state.connected;
- enabled = state.enabled;
- level = state.level;
- iconGroup = state.iconGroup;
- inetCondition = state.inetCondition;
- activityIn = state.activityIn;
- activityOut = state.activityOut;
- rssi = state.rssi;
- time = state.time;
- }
-
- @Override
- public String toString() {
- if (time != 0) {
- StringBuilder builder = new StringBuilder();
- toString(builder);
- return builder.toString();
- } else {
- return "Empty " + getClass().getSimpleName();
- }
- }
-
- protected void toString(StringBuilder builder) {
- builder.append("connected=").append(connected).append(',')
- .append("enabled=").append(enabled).append(',')
- .append("level=").append(level).append(',')
- .append("inetCondition=").append(inetCondition).append(',')
- .append("iconGroup=").append(iconGroup).append(',')
- .append("activityIn=").append(activityIn).append(',')
- .append("activityOut=").append(activityOut).append(',')
- .append("rssi=").append(rssi).append(',')
- .append("lastModified=").append(DateFormat.format("MM-dd hh:mm:ss", time));
- }
-
- @Override
- public boolean equals(Object o) {
- if (!o.getClass().equals(getClass())) {
- return false;
- }
- State other = (State) o;
- return other.connected == connected
- && other.enabled == enabled
- && other.level == level
- && other.inetCondition == inetCondition
- && other.iconGroup == iconGroup
- && other.activityIn == activityIn
- && other.activityOut == activityOut
- && other.rssi == rssi;
- }
- }
- }
-
public interface SignalCluster {
void setWifiIndicators(boolean visible, int strengthIcon, String contentDescription);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PreviewInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PreviewInflater.java
index 030cd6d..34068fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PreviewInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PreviewInflater.java
@@ -20,7 +20,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.os.UserHandle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
new file mode 100644
index 0000000..7d721c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import com.android.systemui.R;
+
+import android.annotation.NonNull;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+/**
+ * Host for the remote input.
+ */
+public class RemoteInputView extends FrameLayout implements View.OnClickListener {
+
+ private static final String TAG = "RemoteInput";
+
+ private RemoteEditText mEditText;
+ private ProgressBar mProgressBar;
+ private PendingIntent mPendingIntent;
+ private RemoteInput mRemoteInput;
+ private Notification.Action mAction;
+
+ public RemoteInputView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ mProgressBar = (ProgressBar) findViewById(R.id.remote_input_progress);
+
+ mEditText = (RemoteEditText) getChildAt(0);
+ mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+
+ // Check if this was the result of hitting the enter key
+ final boolean isSoftImeEvent = event == null
+ && (actionId == EditorInfo.IME_ACTION_DONE
+ || actionId == EditorInfo.IME_ACTION_NEXT
+ || actionId == EditorInfo.IME_ACTION_SEND);
+ final boolean isKeyboardEnterKey = event != null
+ && KeyEvent.isConfirmKey(event.getKeyCode())
+ && event.getAction() == KeyEvent.ACTION_DOWN;
+
+ if (isSoftImeEvent || isKeyboardEnterKey) {
+ sendRemoteInput();
+ return true;
+ }
+ return false;
+ }
+ });
+ mEditText.setOnClickListener(this);
+ mEditText.setInnerFocusable(false);
+ }
+
+ private void sendRemoteInput() {
+ Bundle results = new Bundle();
+ results.putString(mRemoteInput.getResultKey(), mEditText.getText().toString());
+ Intent fillInIntent = new Intent();
+ RemoteInput.addResultsToIntent(mAction.getRemoteInputs(), fillInIntent,
+ results);
+
+ mEditText.setEnabled(false);
+ mProgressBar.setVisibility(VISIBLE);
+
+ try {
+ mPendingIntent.send(mContext, 0, fillInIntent);
+ } catch (PendingIntent.CanceledException e) {
+ Log.i(TAG, "Unable to send remote input result", e);
+ }
+ }
+
+ public static RemoteInputView inflate(Context context, ViewGroup root,
+ Notification.Action action, RemoteInput remoteInput) {
+ RemoteInputView v = (RemoteInputView)
+ LayoutInflater.from(context).inflate(R.layout.remote_input, root, false);
+
+ v.mEditText.setHint(action.title);
+ v.mPendingIntent = action.actionIntent;
+ v.mRemoteInput = remoteInput;
+ v.mAction = action;
+
+ return v;
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v == mEditText) {
+ if (!mEditText.isFocusable()) {
+ mEditText.setInnerFocusable(true);
+ InputMethodManager imm = InputMethodManager.getInstance();
+ if (imm != null) {
+ imm.viewClicked(mEditText);
+ imm.showSoftInput(mEditText, 0);
+ }
+ }
+ }
+ }
+
+ /**
+ * An EditText that changes appearance based on whether it's focusable and becomes
+ * un-focusable whenever the user navigates away from it or it becomes invisible.
+ */
+ public static class RemoteEditText extends EditText {
+
+ private final Drawable mBackground;
+
+ public RemoteEditText(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mBackground = getBackground();
+ }
+
+ private void defocusIfNeeded() {
+ if (isFocusable() && isEnabled()) {
+ setInnerFocusable(false);
+ }
+ }
+
+ @Override
+ protected void onVisibilityChanged(View changedView, int visibility) {
+ super.onVisibilityChanged(changedView, visibility);
+
+ if (!isShown()) {
+ defocusIfNeeded();
+ }
+ }
+
+ @Override
+ protected void onFocusLost() {
+ super.onFocusLost();
+ defocusIfNeeded();
+ }
+
+ @Override
+ public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ defocusIfNeeded();
+ }
+ return super.onKeyPreIme(keyCode, event);
+ }
+
+
+ void setInnerFocusable(boolean focusable) {
+ setFocusableInTouchMode(focusable);
+ setFocusable(focusable);
+ setCursorVisible(focusable);
+
+ if (focusable) {
+ requestFocus();
+ setBackground(mBackground);
+ } else {
+ setBackground(null);
+ }
+
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
new file mode 100644
index 0000000..1d96c6b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.policy;
+
+import static com.android.systemui.statusbar.policy.NetworkControllerImpl.TAG;
+
+import android.content.Context;
+import android.text.format.DateFormat;
+import android.util.Log;
+
+import com.android.systemui.statusbar.policy.NetworkController.NetworkSignalChangedCallback;
+import com.android.systemui.statusbar.policy.NetworkControllerImpl.SignalCluster;
+
+import java.io.PrintWriter;
+import java.util.List;
+
+
+/**
+ * Common base class for handling signal for both wifi and mobile data.
+ */
+public abstract class SignalController<T extends SignalController.State,
+ I extends SignalController.IconGroup> {
+ // Save the previous SignalController.States of all SignalControllers for dumps.
+ static final boolean RECORD_HISTORY = true;
+ // If RECORD_HISTORY how many to save, must be a power of 2.
+ static final int HISTORY_SIZE = 64;
+
+ protected static final boolean DEBUG = NetworkControllerImpl.DEBUG;
+ protected static final boolean CHATTY = NetworkControllerImpl.CHATTY;
+
+ protected final String mTag;
+ protected final T mCurrentState;
+ protected final T mLastState;
+ protected final int mTransportType;
+ protected final Context mContext;
+ // The owner of the SignalController (i.e. NetworkController will maintain the following
+ // lists and call notifyListeners whenever the list has changed to ensure everyone
+ // is aware of current state.
+ protected final List<NetworkSignalChangedCallback> mSignalsChangedCallbacks;
+ protected final List<SignalCluster> mSignalClusters;
+ protected final NetworkControllerImpl mNetworkController;
+
+ // Save the previous HISTORY_SIZE states for logging.
+ private final State[] mHistory;
+ // Where to copy the next state into.
+ private int mHistoryIndex;
+
+ public SignalController(String tag, Context context, int type,
+ List<NetworkSignalChangedCallback> signalCallbacks,
+ List<SignalCluster> signalClusters, NetworkControllerImpl networkController) {
+ mTag = TAG + "." + tag;
+ mNetworkController = networkController;
+ mTransportType = type;
+ mContext = context;
+ mSignalsChangedCallbacks = signalCallbacks;
+ mSignalClusters = signalClusters;
+ mCurrentState = cleanState();
+ mLastState = cleanState();
+ if (RECORD_HISTORY) {
+ mHistory = new State[HISTORY_SIZE];
+ for (int i = 0; i < HISTORY_SIZE; i++) {
+ mHistory[i] = cleanState();
+ }
+ }
+ }
+
+ public T getState() {
+ return mCurrentState;
+ }
+
+ public int getTransportType() {
+ return mTransportType;
+ }
+
+ public void setInetCondition(int inetCondition) {
+ mCurrentState.inetCondition = inetCondition;
+ notifyListenersIfNecessary();
+ }
+
+ /**
+ * Used at the end of demo mode to clear out any ugly state that it has created.
+ * Since we haven't had any callbacks, then isDirty will not have been triggered,
+ * so we can just take the last good state directly from there.
+ *
+ * Used for demo mode.
+ */
+ public void resetLastState() {
+ mCurrentState.copyFrom(mLastState);
+ }
+
+ /**
+ * Determines if the state of this signal controller has changed and
+ * needs to trigger callbacks related to it.
+ */
+ public boolean isDirty() {
+ if (!mLastState.equals(mCurrentState)) {
+ if (DEBUG) {
+ Log.d(mTag, "Change in state from: " + mLastState + "\n"
+ + "\tto: " + mCurrentState);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public void saveLastState() {
+ if (RECORD_HISTORY) {
+ recordLastState();
+ }
+ // Updates the current time.
+ mCurrentState.time = System.currentTimeMillis();
+ mLastState.copyFrom(mCurrentState);
+ }
+
+ /**
+ * Gets the signal icon for QS based on current state of connected, enabled, and level.
+ */
+ public int getQsCurrentIconId() {
+ if (mCurrentState.connected) {
+ return getIcons().mQsIcons[mCurrentState.inetCondition][mCurrentState.level];
+ } else if (mCurrentState.enabled) {
+ return getIcons().mQsDiscState;
+ } else {
+ return getIcons().mQsNullState;
+ }
+ }
+
+ /**
+ * Gets the signal icon for SB based on current state of connected, enabled, and level.
+ */
+ public int getCurrentIconId() {
+ if (mCurrentState.connected) {
+ return getIcons().mSbIcons[mCurrentState.inetCondition][mCurrentState.level];
+ } else if (mCurrentState.enabled) {
+ return getIcons().mSbDiscState;
+ } else {
+ return getIcons().mSbNullState;
+ }
+ }
+
+ /**
+ * Gets the content description id for the signal based on current state of connected and
+ * level.
+ */
+ public int getContentDescription() {
+ if (mCurrentState.connected) {
+ return getIcons().mContentDesc[mCurrentState.level];
+ } else {
+ return getIcons().mDiscContentDesc;
+ }
+ }
+
+ public void notifyListenersIfNecessary() {
+ if (isDirty()) {
+ saveLastState();
+ notifyListeners();
+ }
+ }
+
+ /**
+ * Returns the resource if resId is not 0, and an empty string otherwise.
+ */
+ protected String getStringIfExists(int resId) {
+ return resId != 0 ? mContext.getString(resId) : "";
+ }
+
+ protected I getIcons() {
+ return (I) mCurrentState.iconGroup;
+ }
+
+ /**
+ * Saves the last state of any changes, so we can log the current
+ * and last value of any state data.
+ */
+ protected void recordLastState() {
+ mHistory[mHistoryIndex++ & (HISTORY_SIZE - 1)].copyFrom(mLastState);
+ }
+
+ public void dump(PrintWriter pw) {
+ pw.println(" - " + mTag + " -----");
+ pw.println(" Current State: " + mCurrentState);
+ if (RECORD_HISTORY) {
+ // Count up the states that actually contain time stamps, and only display those.
+ int size = 0;
+ for (int i = 0; i < HISTORY_SIZE; i++) {
+ if (mHistory[i].time != 0) size++;
+ }
+ // Print out the previous states in ordered number.
+ for (int i = mHistoryIndex + HISTORY_SIZE - 1;
+ i >= mHistoryIndex + HISTORY_SIZE - size; i--) {
+ pw.println(" Previous State(" + (mHistoryIndex + HISTORY_SIZE - i) + "): "
+ + mHistory[i & (HISTORY_SIZE - 1)]);
+ }
+ }
+ }
+
+ /**
+ * Trigger callbacks based on current state. The callbacks should be completely
+ * based on current state, and only need to be called in the scenario where
+ * mCurrentState != mLastState.
+ */
+ public abstract void notifyListeners();
+
+ /**
+ * Generate a blank T.
+ */
+ protected abstract T cleanState();
+
+ /*
+ * Holds icons for a given state. Arrays are generally indexed as inet
+ * state (full connectivity or not) first, and second dimension as
+ * signal strength.
+ */
+ static class IconGroup {
+ final int[][] mSbIcons;
+ final int[][] mQsIcons;
+ final int[] mContentDesc;
+ final int mSbNullState;
+ final int mQsNullState;
+ final int mSbDiscState;
+ final int mQsDiscState;
+ final int mDiscContentDesc;
+ // For logging.
+ final String mName;
+
+ public IconGroup(String name, int[][] sbIcons, int[][] qsIcons, int[] contentDesc,
+ int sbNullState, int qsNullState, int sbDiscState, int qsDiscState,
+ int discContentDesc) {
+ mName = name;
+ mSbIcons = sbIcons;
+ mQsIcons = qsIcons;
+ mContentDesc = contentDesc;
+ mSbNullState = sbNullState;
+ mQsNullState = qsNullState;
+ mSbDiscState = sbDiscState;
+ mQsDiscState = qsDiscState;
+ mDiscContentDesc = discContentDesc;
+ }
+
+ @Override
+ public String toString() {
+ return "IconGroup(" + mName + ")";
+ }
+ }
+
+ static class State {
+ boolean connected;
+ boolean enabled;
+ boolean activityIn;
+ boolean activityOut;
+ int level;
+ IconGroup iconGroup;
+ int inetCondition;
+ int rssi; // Only for logging.
+
+ // Not used for comparison, just used for logging.
+ long time;
+
+ public void copyFrom(State state) {
+ connected = state.connected;
+ enabled = state.enabled;
+ level = state.level;
+ iconGroup = state.iconGroup;
+ inetCondition = state.inetCondition;
+ activityIn = state.activityIn;
+ activityOut = state.activityOut;
+ rssi = state.rssi;
+ time = state.time;
+ }
+
+ @Override
+ public String toString() {
+ if (time != 0) {
+ StringBuilder builder = new StringBuilder();
+ toString(builder);
+ return builder.toString();
+ } else {
+ return "Empty " + getClass().getSimpleName();
+ }
+ }
+
+ protected void toString(StringBuilder builder) {
+ builder.append("connected=").append(connected).append(',')
+ .append("enabled=").append(enabled).append(',')
+ .append("level=").append(level).append(',')
+ .append("inetCondition=").append(inetCondition).append(',')
+ .append("iconGroup=").append(iconGroup).append(',')
+ .append("activityIn=").append(activityIn).append(',')
+ .append("activityOut=").append(activityOut).append(',')
+ .append("rssi=").append(rssi).append(',')
+ .append("lastModified=").append(DateFormat.format("MM-dd hh:mm:ss", time));
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!o.getClass().equals(getClass())) {
+ return false;
+ }
+ State other = (State) o;
+ return other.connected == connected
+ && other.enabled == enabled
+ && other.level == level
+ && other.inetCondition == inetCondition
+ && other.iconGroup == iconGroup
+ && other.activityIn == activityIn
+ && other.activityOut == activityOut
+ && other.rssi == rssi;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
index 4091619..d266ed8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
@@ -17,7 +17,7 @@
package com.android.systemui.statusbar.policy;
import com.android.systemui.R;
-import com.android.systemui.statusbar.policy.NetworkControllerImpl.MobileSignalController.MobileIconGroup;
+import com.android.systemui.statusbar.policy.MobileSignalController.MobileIconGroup;
class TelephonyIcons {
//***** Signal strength icons
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
new file mode 100644
index 0000000..a97ca50
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.policy;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Messenger;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.AsyncChannel;
+import com.android.systemui.statusbar.policy.NetworkController.NetworkSignalChangedCallback;
+import com.android.systemui.statusbar.policy.NetworkControllerImpl.SignalCluster;
+
+import java.util.List;
+import java.util.Objects;
+
+
+public class WifiSignalController extends
+ SignalController<WifiSignalController.WifiState, SignalController.IconGroup> {
+ private final WifiManager mWifiManager;
+ private final AsyncChannel mWifiChannel;
+ private final boolean mHasMobileData;
+
+ public WifiSignalController(Context context, boolean hasMobileData,
+ List<NetworkSignalChangedCallback> signalCallbacks,
+ List<SignalCluster> signalClusters, NetworkControllerImpl networkController) {
+ super("WifiSignalController", context, NetworkCapabilities.TRANSPORT_WIFI,
+ signalCallbacks, signalClusters, networkController);
+ mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ mHasMobileData = hasMobileData;
+ Handler handler = new WifiHandler();
+ mWifiChannel = new AsyncChannel();
+ Messenger wifiMessenger = mWifiManager.getWifiServiceMessenger();
+ if (wifiMessenger != null) {
+ mWifiChannel.connect(context, handler, wifiMessenger);
+ }
+ // WiFi only has one state.
+ mCurrentState.iconGroup = mLastState.iconGroup = new IconGroup(
+ "Wi-Fi Icons",
+ WifiIcons.WIFI_SIGNAL_STRENGTH,
+ WifiIcons.QS_WIFI_SIGNAL_STRENGTH,
+ AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH,
+ WifiIcons.WIFI_NO_NETWORK,
+ WifiIcons.QS_WIFI_NO_NETWORK,
+ WifiIcons.WIFI_NO_NETWORK,
+ WifiIcons.QS_WIFI_NO_NETWORK,
+ AccessibilityContentDescriptions.WIFI_NO_CONNECTION
+ );
+ }
+
+ @Override
+ protected WifiState cleanState() {
+ return new WifiState();
+ }
+
+ @Override
+ public void notifyListeners() {
+ // only show wifi in the cluster if connected or if wifi-only
+ boolean wifiVisible = mCurrentState.enabled
+ && (mCurrentState.connected || !mHasMobileData);
+ String wifiDesc = wifiVisible ? mCurrentState.ssid : null;
+ boolean ssidPresent = wifiVisible && mCurrentState.ssid != null;
+ String contentDescription = getStringIfExists(getContentDescription());
+ int length = mSignalsChangedCallbacks.size();
+ for (int i = 0; i < length; i++) {
+ mSignalsChangedCallbacks.get(i).onWifiSignalChanged(mCurrentState.enabled,
+ mCurrentState.connected, getQsCurrentIconId(),
+ ssidPresent && mCurrentState.activityIn,
+ ssidPresent && mCurrentState.activityOut, contentDescription, wifiDesc);
+ }
+
+ int signalClustersLength = mSignalClusters.size();
+ for (int i = 0; i < signalClustersLength; i++) {
+ mSignalClusters.get(i).setWifiIndicators(wifiVisible, getCurrentIconId(),
+ contentDescription);
+ }
+ }
+
+ /**
+ * Extract wifi state directly from broadcasts about changes in wifi state.
+ */
+ public void handleBroadcast(Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
+ mCurrentState.enabled = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
+ WifiManager.WIFI_STATE_UNKNOWN) == WifiManager.WIFI_STATE_ENABLED;
+ } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+ final NetworkInfo networkInfo = (NetworkInfo)
+ intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
+ mCurrentState.connected = networkInfo != null && networkInfo.isConnected();
+ // If Connected grab the signal strength and ssid.
+ if (mCurrentState.connected) {
+ // try getting it out of the intent first
+ WifiInfo info = intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO) != null
+ ? (WifiInfo) intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO)
+ : mWifiManager.getConnectionInfo();
+ if (info != null) {
+ mCurrentState.ssid = getSsid(info);
+ } else {
+ mCurrentState.ssid = null;
+ }
+ } else if (!mCurrentState.connected) {
+ mCurrentState.ssid = null;
+ }
+ } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
+ // Default to -200 as its below WifiManager.MIN_RSSI.
+ mCurrentState.rssi = intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200);
+ mCurrentState.level = WifiManager.calculateSignalLevel(
+ mCurrentState.rssi, WifiIcons.WIFI_LEVEL_COUNT);
+ }
+
+ notifyListenersIfNecessary();
+ }
+
+ private String getSsid(WifiInfo info) {
+ String ssid = info.getSSID();
+ if (ssid != null) {
+ return ssid;
+ }
+ // OK, it's not in the connectionInfo; we have to go hunting for it
+ List<WifiConfiguration> networks = mWifiManager.getConfiguredNetworks();
+ int length = networks.size();
+ for (int i = 0; i < length; i++) {
+ if (networks.get(i).networkId == info.getNetworkId()) {
+ return networks.get(i).SSID;
+ }
+ }
+ return null;
+ }
+
+ @VisibleForTesting
+ void setActivity(int wifiActivity) {
+ mCurrentState.activityIn = wifiActivity == WifiManager.DATA_ACTIVITY_INOUT
+ || wifiActivity == WifiManager.DATA_ACTIVITY_IN;
+ mCurrentState.activityOut = wifiActivity == WifiManager.DATA_ACTIVITY_INOUT
+ || wifiActivity == WifiManager.DATA_ACTIVITY_OUT;
+ notifyListenersIfNecessary();
+ }
+
+ /**
+ * Handler to receive the data activity on wifi.
+ */
+ private class WifiHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
+ if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
+ mWifiChannel.sendMessage(Message.obtain(this,
+ AsyncChannel.CMD_CHANNEL_FULL_CONNECTION));
+ } else {
+ Log.e(mTag, "Failed to connect to wifi");
+ }
+ break;
+ case WifiManager.DATA_ACTIVITY_NOTIFICATION:
+ setActivity(msg.arg1);
+ break;
+ default:
+ // Ignore
+ break;
+ }
+ }
+ }
+
+ static class WifiState extends SignalController.State {
+ String ssid;
+
+ @Override
+ public void copyFrom(State s) {
+ super.copyFrom(s);
+ WifiState state = (WifiState) s;
+ ssid = state.ssid;
+ }
+
+ @Override
+ protected void toString(StringBuilder builder) {
+ super.toString(builder);
+ builder.append(',').append("ssid=").append(ssid);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return super.equals(o)
+ && Objects.equals(((WifiState) o).ssid, ssid);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
index 600b750..0e21457 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
@@ -31,6 +31,7 @@ public interface ZenModeController {
void setUserId(int userId);
boolean isZenAvailable();
ComponentName getEffectsSuppressor();
+ boolean isCountdownConditionSupported();
public static class Callback {
public void onZenChanged(int zen) {}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index 37ed7d8..bea0c86 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.policy;
import android.app.AlarmManager;
-import android.app.INotificationManager;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -28,8 +27,6 @@ import android.content.IntentFilter;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
-import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.UserHandle;
import android.provider.Settings.Global;
import android.provider.Settings.Secure;
@@ -53,7 +50,7 @@ public class ZenModeControllerImpl implements ZenModeController {
private final Context mContext;
private final GlobalSetting mModeSetting;
private final GlobalSetting mConfigSetting;
- private final INotificationManager mNoMan;
+ private final NotificationManager mNoMan;
private final LinkedHashMap<Uri, Condition> mConditions = new LinkedHashMap<Uri, Condition>();
private final AlarmManager mAlarmManager;
private final SetupObserver mSetupObserver;
@@ -78,8 +75,7 @@ public class ZenModeControllerImpl implements ZenModeController {
};
mModeSetting.setListening(true);
mConfigSetting.setListening(true);
- mNoMan = INotificationManager.Stub.asInterface(
- ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+ mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
mSetupObserver = new SetupObserver(handler);
mSetupObserver.register();
@@ -113,11 +109,7 @@ public class ZenModeControllerImpl implements ZenModeController {
@Override
public void requestConditions(boolean request) {
mRequesting = request;
- try {
- mNoMan.requestZenModeConditions(mListener, request ? Condition.FLAG_RELEVANT_NOW : 0);
- } catch (RemoteException e) {
- // noop
- }
+ mNoMan.requestZenModeConditions(mListener, request ? Condition.FLAG_RELEVANT_NOW : 0);
if (!mRequesting) {
mConditions.clear();
}
@@ -125,24 +117,12 @@ public class ZenModeControllerImpl implements ZenModeController {
@Override
public void setExitCondition(Condition exitCondition) {
- try {
- mNoMan.setZenModeCondition(exitCondition);
- } catch (RemoteException e) {
- // noop
- }
+ mNoMan.setZenModeCondition(exitCondition);
}
@Override
public Condition getExitCondition() {
- try {
- final ZenModeConfig config = mNoMan.getZenModeConfig();
- if (config != null) {
- return config.exitCondition;
- }
- } catch (RemoteException e) {
- // noop
- }
- return null;
+ return mNoMan.getZenModeCondition();
}
@Override
@@ -169,6 +149,12 @@ public class ZenModeControllerImpl implements ZenModeController {
return NotificationManager.from(mContext).getEffectsSuppressor();
}
+ @Override
+ public boolean isCountdownConditionSupported() {
+ return NotificationManager.from(mContext)
+ .isSystemConditionProviderEnabled(ZenModeConfig.COUNTDOWN_PATH);
+ }
+
private void fireNextAlarmChanged() {
for (Callback cb : mCallbacks) {
cb.onNextAlarmChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
new file mode 100644
index 0000000..3c9e8cf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -0,0 +1,406 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.stack;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.ExpandableView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A container containing child notifications
+ */
+public class NotificationChildrenContainer extends ViewGroup {
+
+ private final int mChildPadding;
+ private final int mDividerHeight;
+ private final int mMaxNotificationHeight;
+ private final List<View> mDividers = new ArrayList<>();
+ private final List<ExpandableNotificationRow> mChildren = new ArrayList<>();
+ private final View mCollapseButton;
+ private final View mCollapseDivider;
+ private final int mCollapseButtonHeight;
+ private final int mNotificationAppearDistance;
+
+ public NotificationChildrenContainer(Context context) {
+ this(context, null);
+ }
+
+ public NotificationChildrenContainer(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ mChildPadding = getResources().getDimensionPixelSize(
+ R.dimen.notification_children_padding);
+ mDividerHeight = getResources().getDimensionPixelSize(
+ R.dimen.notification_children_divider_height);
+ mMaxNotificationHeight = getResources().getDimensionPixelSize(
+ R.dimen.notification_max_height);
+ mNotificationAppearDistance = getResources().getDimensionPixelSize(
+ R.dimen.notification_appear_distance);
+ LayoutInflater inflater = mContext.getSystemService(LayoutInflater.class);
+ mCollapseButton = inflater.inflate(R.layout.notification_collapse_button, this,
+ false);
+ mCollapseButtonHeight = getResources().getDimensionPixelSize(
+ R.dimen.notification_bottom_decor_height);
+ addView(mCollapseButton);
+ mCollapseDivider = inflateDivider();
+ addView(mCollapseDivider);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ int childCount = mChildren.size();
+ boolean firstChild = true;
+ for (int i = 0; i < childCount; i++) {
+ View child = mChildren.get(i);
+ boolean viewGone = child.getVisibility() == View.GONE;
+ if (i != 0) {
+ View divider = mDividers.get(i - 1);
+ int dividerVisibility = divider.getVisibility();
+ int newVisibility = viewGone ? INVISIBLE : VISIBLE;
+ if (dividerVisibility != newVisibility) {
+ divider.setVisibility(newVisibility);
+ }
+ }
+ if (viewGone) {
+ continue;
+ }
+ child.layout(0, 0, getWidth(), child.getMeasuredHeight());
+ if (!firstChild) {
+ mDividers.get(i - 1).layout(0, 0, getWidth(), mDividerHeight);
+ } else {
+ firstChild = false;
+ }
+ }
+ mCollapseButton.layout(0, 0, getWidth(), mCollapseButtonHeight);
+ mCollapseDivider.layout(0, mCollapseButtonHeight - mDividerHeight, getWidth(),
+ mCollapseButtonHeight);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int ownMaxHeight = mMaxNotificationHeight;
+ int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY;
+ boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST;
+ if (hasFixedHeight || isHeightLimited) {
+ int size = MeasureSpec.getSize(heightMeasureSpec);
+ ownMaxHeight = Math.min(ownMaxHeight, size);
+ }
+ int newHeightSpec = MeasureSpec.makeMeasureSpec(ownMaxHeight, MeasureSpec.AT_MOST);
+ int dividerHeightSpec = MeasureSpec.makeMeasureSpec(mDividerHeight, MeasureSpec.EXACTLY);
+ int collapseButtonHeightSpec = MeasureSpec.makeMeasureSpec(mCollapseButtonHeight,
+ MeasureSpec.EXACTLY);
+ mCollapseButton.measure(widthMeasureSpec, collapseButtonHeightSpec);
+ mCollapseDivider.measure(widthMeasureSpec, dividerHeightSpec);
+ int height = mCollapseButtonHeight;
+ int childCount = mChildren.size();
+ boolean firstChild = true;
+ for (int i = 0; i < childCount; i++) {
+ View child = mChildren.get(i);
+ if (child.getVisibility() == View.GONE) {
+ continue;
+ }
+ child.measure(widthMeasureSpec, newHeightSpec);
+ height += child.getMeasuredHeight();
+ if (!firstChild) {
+ // layout the divider
+ View divider = mDividers.get(i - 1);
+ divider.measure(widthMeasureSpec, dividerHeightSpec);
+ height += mChildPadding;
+ } else {
+ firstChild = false;
+ }
+ }
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ height = hasFixedHeight ? ownMaxHeight
+ : isHeightLimited ? Math.min(ownMaxHeight, height)
+ : height;
+ setMeasuredDimension(width, height);
+ }
+
+ /**
+ * Add a child notification to this view.
+ *
+ * @param row the row to add
+ * @param childIndex the index to add it at, if -1 it will be added at the end
+ */
+ public void addNotification(ExpandableNotificationRow row, int childIndex) {
+ int newIndex = childIndex < 0 ? mChildren.size() : childIndex;
+ mChildren.add(newIndex, row);
+ addView(row);
+ if (mChildren.size() != 1) {
+ View divider = inflateDivider();
+ addView(divider);
+ mDividers.add(Math.max(newIndex - 1, 0), divider);
+ }
+ // TODO: adapt background corners
+ // TODO: fix overdraw
+ }
+
+ public void removeNotification(ExpandableNotificationRow row) {
+ int childIndex = mChildren.indexOf(row);
+ mChildren.remove(row);
+ removeView(row);
+ if (!mDividers.isEmpty()) {
+ View divider = mDividers.remove(Math.max(childIndex - 1, 0));
+ removeView(divider);
+ }
+ row.setSystemChildExpanded(false);
+ // TODO: adapt background corners
+ }
+
+ private View inflateDivider() {
+ return LayoutInflater.from(mContext).inflate(
+ R.layout.notification_children_divider, this, false);
+ }
+
+ public List<ExpandableNotificationRow> getNotificationChildren() {
+ return mChildren;
+ }
+
+ /**
+ * Apply the order given in the list to the children.
+ *
+ * @param childOrder the new list order
+ * @return whether the list order has changed
+ */
+ public boolean applyChildOrder(List<ExpandableNotificationRow> childOrder) {
+ if (childOrder == null) {
+ return false;
+ }
+ boolean result = false;
+ for (int i = 0; i < mChildren.size() && i < childOrder.size(); i++) {
+ ExpandableNotificationRow child = mChildren.get(i);
+ ExpandableNotificationRow desiredChild = childOrder.get(i);
+ if (child != desiredChild) {
+ mChildren.remove(desiredChild);
+ mChildren.add(i, desiredChild);
+ result = true;
+ }
+ }
+
+ // Let's make the first child expanded!
+ boolean first = true;
+ for (int i = 0; i < childOrder.size(); i++) {
+ ExpandableNotificationRow child = childOrder.get(i);
+ child.setSystemChildExpanded(first);
+ first = false;
+ }
+ return result;
+ }
+
+ public int getIntrinsicHeight() {
+ int childCount = mChildren.size();
+ int intrinsicHeight = 0;
+ int visibleChildren = 0;
+ for (int i = 0; i < childCount; i++) {
+ ExpandableNotificationRow child = mChildren.get(i);
+ if (child.getVisibility() == View.GONE) {
+ continue;
+ }
+ intrinsicHeight += child.getIntrinsicHeight();
+ visibleChildren++;
+ }
+ if (visibleChildren > 0) {
+ intrinsicHeight += (visibleChildren - 1) * mDividerHeight;
+ }
+ return intrinsicHeight;
+ }
+
+ /**
+ * Update the state of all its children based on a linear layout algorithm.
+ *
+ * @param resultState the state to update
+ * @param parentState the state of the parent
+ */
+ public void getState(StackScrollState resultState, StackViewState parentState) {
+ int childCount = mChildren.size();
+ int yPosition = mCollapseButtonHeight;
+ boolean firstChild = true;
+ for (int i = 0; i < childCount; i++) {
+ ExpandableNotificationRow child = mChildren.get(i);
+ if (child.getVisibility() == View.GONE) {
+ continue;
+ }
+ if (!firstChild) {
+ // There's a divider
+ yPosition += mChildPadding;
+ } else {
+ firstChild = false;
+ }
+ StackViewState childState = resultState.getViewStateForView(child);
+ int intrinsicHeight = child.getIntrinsicHeight();
+ childState.yTranslation = yPosition;
+ childState.zTranslation = 0;
+ childState.height = intrinsicHeight;
+ childState.dimmed = parentState.dimmed;
+ childState.dark = parentState.dark;
+ childState.hideSensitive = parentState.hideSensitive;
+ childState.belowSpeedBump = parentState.belowSpeedBump;
+ childState.scale = parentState.scale;
+ childState.clipTopAmount = 0;
+ childState.topOverLap = 0;
+ childState.location = parentState.location;
+ yPosition += intrinsicHeight;
+ }
+ }
+
+ public void applyState(StackScrollState state) {
+ int childCount = mChildren.size();
+ boolean firstChild = true;
+ ViewState dividerState = new ViewState();
+ for (int i = 0; i < childCount; i++) {
+ ExpandableNotificationRow child = mChildren.get(i);
+ StackViewState viewState = state.getViewStateForView(child);
+ if (child.getVisibility() == View.GONE) {
+ continue;
+ }
+ if (!firstChild) {
+ // layout the divider
+ View divider = mDividers.get(i - 1);
+ dividerState.initFrom(divider);
+ dividerState.yTranslation = (int) (viewState.yTranslation
+ - (mChildPadding + mDividerHeight) / 2.0f);
+ dividerState.alpha = 1;
+ state.applyViewState(divider, dividerState);
+ } else {
+ firstChild = false;
+ }
+ state.applyState(child, viewState);
+ }
+ }
+
+ public void setCollapseClickListener(OnClickListener collapseClickListener) {
+ mCollapseButton.setOnClickListener(collapseClickListener);
+ }
+
+ /**
+ * This is called when the children expansion has changed and positions the children properly
+ * for an appear animation.
+ *
+ * @param state the new state we animate to
+ */
+ public void prepareExpansionChanged(StackScrollState state) {
+ int childCount = mChildren.size();
+ boolean firstChild = true;
+ StackViewState sourceState = new StackViewState();
+ ViewState dividerState = new ViewState();
+ for (int i = 0; i < childCount; i++) {
+ ExpandableNotificationRow child = mChildren.get(i);
+ StackViewState viewState = state.getViewStateForView(child);
+ if (child.getVisibility() == View.GONE) {
+ continue;
+ }
+ if (!firstChild) {
+ // layout the divider
+ View divider = mDividers.get(i - 1);
+ dividerState.initFrom(divider);
+ dividerState.yTranslation = viewState.yTranslation
+ - (mChildPadding + mDividerHeight) / 2.0f + mNotificationAppearDistance;
+ dividerState.alpha = 0;
+ state.applyViewState(divider, dividerState);
+ } else {
+ firstChild = false;
+ }
+ sourceState.copyFrom(viewState);
+ sourceState.alpha = 0;
+ sourceState.yTranslation += mNotificationAppearDistance;
+ state.applyState(child, sourceState);
+ }
+ mCollapseButton.setAlpha(0);
+ mCollapseDivider.setAlpha(0);
+ mCollapseDivider.setTranslationY(mNotificationAppearDistance / 4);
+ }
+
+ public void startAnimationToState(StackScrollState state, StackStateAnimator stateAnimator,
+ boolean withDelays, long baseDelay, long duration) {
+ int childCount = mChildren.size();
+ boolean firstChild = true;
+ ViewState dividerState = new ViewState();
+ int notGoneIndex = 0;
+ for (int i = 0; i < childCount; i++) {
+ ExpandableNotificationRow child = mChildren.get(i);
+ StackViewState viewState = state.getViewStateForView(child);
+ if (child.getVisibility() == View.GONE) {
+ continue;
+ }
+ int difference = Math.min(StackStateAnimator.DELAY_EFFECT_MAX_INDEX_DIFFERENCE_CHILDREN,
+ notGoneIndex + 1);
+ long delay = withDelays
+ ? difference * StackStateAnimator.ANIMATION_DELAY_PER_ELEMENT_EXPAND_CHILDREN
+ : 0;
+ delay += baseDelay;
+ if (!firstChild) {
+ // layout the divider
+ View divider = mDividers.get(i - 1);
+ dividerState.initFrom(divider);
+ dividerState.yTranslation = viewState.yTranslation
+ - (mChildPadding + mDividerHeight) / 2.0f;
+ dividerState.alpha = 1;
+ stateAnimator.startViewAnimations(divider, dividerState, delay, duration);
+ } else {
+ firstChild = false;
+ }
+ stateAnimator.startStackAnimations(child, viewState, state, -1, delay);
+ notGoneIndex++;
+ }
+ dividerState.initFrom(mCollapseButton);
+ dividerState.alpha = 1.0f;
+ stateAnimator.startViewAnimations(mCollapseButton, dividerState, baseDelay, duration);
+ dividerState.initFrom(mCollapseDivider);
+ dividerState.alpha = 1.0f;
+ dividerState.yTranslation = 0.0f;
+ stateAnimator.startViewAnimations(mCollapseDivider, dividerState, baseDelay, duration);
+ }
+
+ public ExpandableNotificationRow getViewAtPosition(float y) {
+ // find the view under the pointer, accounting for GONE views
+ final int count = mChildren.size();
+ for (int childIdx = 0; childIdx < count; childIdx++) {
+ ExpandableNotificationRow slidingChild = mChildren.get(childIdx);
+ float childTop = slidingChild.getTranslationY();
+ float top = childTop + slidingChild.getClipTopAmount();
+ float bottom = childTop + slidingChild.getActualHeight();
+ if (y >= top && y <= bottom) {
+ return slidingChild;
+ }
+ }
+ return null;
+ }
+
+ public void setTintColor(int color) {
+ ExpandableNotificationRow.applyTint(mCollapseDivider, color);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 6dcbed6..2eafd57 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -41,12 +41,13 @@ import com.android.systemui.statusbar.DismissView;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.ExpandableView;
+import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.SpeedBumpView;
import com.android.systemui.statusbar.StackScrollerDecorView;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
import com.android.systemui.statusbar.policy.ScrollAdapter;
-import com.android.systemui.statusbar.stack.StackScrollState.ViewState;
import java.util.ArrayList;
import java.util.HashSet;
@@ -56,7 +57,7 @@ import java.util.HashSet;
*/
public class NotificationStackScrollLayout extends ViewGroup
implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
- ExpandableView.OnHeightChangedListener {
+ ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener {
private static final String TAG = "NotificationStackScrollLayout";
private static final boolean DEBUG = false;
@@ -119,6 +120,7 @@ public class NotificationStackScrollLayout extends ViewGroup
*/
private StackScrollState mCurrentStackScrollState = new StackScrollState(this);
private AmbientState mAmbientState = new AmbientState();
+ private NotificationGroupManager mGroupManager;
private ArrayList<View> mChildrenToAddAnimated = new ArrayList<View>();
private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<View>();
private ArrayList<View> mSnappedBackChildren = new ArrayList<View>();
@@ -181,6 +183,7 @@ public class NotificationStackScrollLayout extends ViewGroup
private boolean mDontReportNextOverScroll;
private boolean mRequestViewResizeAnimationOnLayout;
private boolean mNeedViewResizeAnimation;
+ private View mExpandedGroupView;
private boolean mEverythingNeedsAnimation;
/**
@@ -214,6 +217,8 @@ public class NotificationStackScrollLayout extends ViewGroup
};
private PhoneStatusBar mPhoneStatusBar;
private int[] mTempInt2 = new int[2];
+ private boolean mGenerateChildOrderChangedEvent;
+ private boolean mRemoveAnimationEnabled;
public NotificationStackScrollLayout(Context context) {
this(context, null);
@@ -309,7 +314,7 @@ public class NotificationStackScrollLayout extends ViewGroup
private void notifyHeightChangeListener(ExpandableView view) {
if (mOnHeightChangedListener != null) {
- mOnHeightChangedListener.onHeightChanged(view);
+ mOnHeightChangedListener.onHeightChanged(view, false /* needsAnimation */);
}
}
@@ -329,6 +334,9 @@ public class NotificationStackScrollLayout extends ViewGroup
float centerX = getWidth() / 2.0f;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
+ if (child.getVisibility() == GONE) {
+ continue;
+ }
float width = child.getMeasuredWidth();
float height = child.getMeasuredHeight();
child.layout((int) (centerX - width / 2.0f),
@@ -339,16 +347,18 @@ public class NotificationStackScrollLayout extends ViewGroup
setMaxLayoutHeight(getHeight());
updateContentHeight();
clampScrollPosition();
- requestAnimationOnViewResize();
+ if (mRequestViewResizeAnimationOnLayout) {
+ requestAnimationOnViewResize();
+ mRequestViewResizeAnimationOnLayout = false;
+ }
requestChildrenUpdate();
}
private void requestAnimationOnViewResize() {
- if (mRequestViewResizeAnimationOnLayout && mIsExpanded && mAnimationsEnabled) {
+ if (mIsExpanded && mAnimationsEnabled) {
mNeedViewResizeAnimation = true;
mNeedsAnimation = true;
}
- mRequestViewResizeAnimationOnLayout = false;
}
public void updateSpeedBumpIndex(int newIndex) {
@@ -375,15 +385,15 @@ public class NotificationStackScrollLayout extends ViewGroup
* Returns the location the given child is currently rendered at.
*
* @param child the child to get the location for
- * @return one of {@link ViewState}'s <code>LOCATION_*</code> constants
+ * @return one of {@link StackViewState}'s <code>LOCATION_*</code> constants
*/
public int getChildLocation(View child) {
- ViewState childViewState = mCurrentStackScrollState.getViewStateForView(child);
+ StackViewState childViewState = mCurrentStackScrollState.getViewStateForView(child);
if (childViewState == null) {
- return ViewState.LOCATION_UNKNOWN;
+ return StackViewState.LOCATION_UNKNOWN;
}
if (childViewState.gone) {
- return ViewState.LOCATION_GONE;
+ return StackViewState.LOCATION_GONE;
}
return childViewState.location;
}
@@ -645,6 +655,10 @@ public class NotificationStackScrollLayout extends ViewGroup
int right = getWidth();
if (touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) {
+ if (slidingChild instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild;
+ return row.getViewAtPosition(touchY - childTop);
+ }
return slidingChild;
}
}
@@ -723,7 +737,6 @@ public class NotificationStackScrollLayout extends ViewGroup
}
public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) {
- child.setClipBounds(null);
mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration);
}
@@ -1544,6 +1557,14 @@ public class NotificationStackScrollLayout extends ViewGroup
@Override
protected void onViewRemoved(View child) {
super.onViewRemoved(child);
+ // we only call our internal methods if this is actually a removal and not just a
+ // notification which becomes a child notification
+ if (!isChildInGroup(child)) {
+ onViewRemovedInternal(child);
+ }
+ }
+
+ private void onViewRemovedInternal(View child) {
mStackScrollAlgorithm.notifyChildrenChanged(this);
if (mChangePositionInProgress) {
// This is only a position change, don't do anything special
@@ -1552,16 +1573,27 @@ public class NotificationStackScrollLayout extends ViewGroup
((ExpandableView) child).setOnHeightChangedListener(null);
mCurrentStackScrollState.removeViewStateForView(child);
updateScrollStateForRemovedChild(child);
- boolean animationGenerated = generateRemoveAnimation(child);
- if (animationGenerated && !mSwipedOutViews.contains(child)) {
- // Add this view to an overlay in order to ensure that it will still be temporary
- // drawn when removed
- getOverlay().add(child);
+ if (mRemoveAnimationEnabled) {
+ boolean animationGenerated = generateRemoveAnimation(child);
+ if (animationGenerated && !mSwipedOutViews.contains(child)) {
+ // Add this view to an overlay in order to ensure that it will still be temporary
+ // drawn when removed
+ getOverlay().add(child);
+ }
+ } else {
+ // TODO: handle this more cleanly when HEADS-up and the shade are merged
+ requestAnimateEverything();
}
updateAnimationState(false, child);
// Make sure the clipRect we might have set is removed
- child.setClipBounds(null);
+ ((ExpandableView) child).setClipTopOptimization(0);
+ }
+
+ private boolean isChildInGroup(View child) {
+ return child instanceof ExpandableNotificationRow
+ && mGroupManager.isChildInGroupWithSummary(
+ ((ExpandableNotificationRow) child).getStatusBarNotification());
}
/**
@@ -1571,7 +1603,7 @@ public class NotificationStackScrollLayout extends ViewGroup
* @return Whether an animation was generated.
*/
private boolean generateRemoveAnimation(View child) {
- if (mIsExpanded && mAnimationsEnabled) {
+ if (mIsExpanded && mAnimationsEnabled && !isChildInInvisibleGroup(child)) {
if (!mChildrenToAddAnimated.contains(child)) {
// Generate Animations
mChildrenToRemoveAnimated.add(child);
@@ -1587,6 +1619,23 @@ public class NotificationStackScrollLayout extends ViewGroup
}
/**
+ * @param child the child to query
+ * @return whether a view is not a top level child but a child notification and that group is
+ * not expanded
+ */
+ private boolean isChildInInvisibleGroup(View child) {
+ if (child instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ ExpandableNotificationRow groupSummary =
+ mGroupManager.getGroupSummary(row.getStatusBarNotification());
+ if (groupSummary != null && groupSummary != row) {
+ return !groupSummary.areChildrenExpanded();
+ }
+ }
+ return false;
+ }
+
+ /**
* Updates the scroll position when a child was removed
*
* @param removedChild the removed child
@@ -1634,6 +1683,10 @@ public class NotificationStackScrollLayout extends ViewGroup
@Override
protected void onViewAdded(View child) {
super.onViewAdded(child);
+ onViewAddedInternal(child);
+ }
+
+ private void onViewAddedInternal(View child) {
mStackScrollAlgorithm.notifyChildrenChanged(this);
((ExpandableView) child).setOnHeightChangedListener(this);
generateAddAnimation(child, false /* fromMoreCard */);
@@ -1646,6 +1699,14 @@ public class NotificationStackScrollLayout extends ViewGroup
}
}
+ public void notifyGroupChildRemoved(View row) {
+ onViewRemovedInternal(row);
+ }
+
+ public void notifyGroupChildAdded(View row) {
+ onViewAddedInternal(row);
+ }
+
public void setAnimationsEnabled(boolean animationsEnabled) {
mAnimationsEnabled = animationsEnabled;
updateNotificationAnimationStates();
@@ -1741,10 +1802,20 @@ public class NotificationStackScrollLayout extends ViewGroup
generateDarkEvent();
generateGoToFullShadeEvent();
generateViewResizeEvent();
+ generateGroupExpansionEvent();
generateAnimateEverythingEvent();
mNeedsAnimation = false;
}
+ private void generateGroupExpansionEvent() {
+ // Generate a group expansion/collapsing event if there is such a group at all
+ if (mExpandedGroupView != null) {
+ mAnimationEvents.add(new AnimationEvent(mExpandedGroupView,
+ AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED));
+ mExpandedGroupView = null;
+ }
+ }
+
private void generateViewResizeEvent() {
if (mNeedViewResizeAnimation) {
mAnimationEvents.add(
@@ -1791,6 +1862,11 @@ public class NotificationStackScrollLayout extends ViewGroup
AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
}
mChildrenChangingPositions.clear();
+ if (mGenerateChildOrderChangedEvent) {
+ mAnimationEvents.add(new AnimationEvent(null,
+ AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
+ mGenerateChildOrderChangedEvent = false;
+ }
}
private void generateChildAdditionEvents() {
@@ -2059,11 +2135,14 @@ public class NotificationStackScrollLayout extends ViewGroup
}
@Override
- public void onHeightChanged(ExpandableView view) {
+ public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
updateContentHeight();
updateScrollPositionOnExpandInBottom(view);
clampScrollPosition();
notifyHeightChangeListener(view);
+ if (needsAnimation) {
+ requestAnimationOnViewResize();
+ }
requestChildrenUpdate();
}
@@ -2338,6 +2417,20 @@ public class NotificationStackScrollLayout extends ViewGroup
public void setDismissAllInProgress(boolean dismissAllInProgress) {
mDismissAllInProgress = dismissAllInProgress;
mDismissView.setDismissAllInProgress(dismissAllInProgress);
+ if (dismissAllInProgress) {
+ disableClipOptimization();
+ }
+ }
+
+ private void disableClipOptimization() {
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ ExpandableView child = (ExpandableView) getChildAt(i);
+ if (child.getVisibility() == GONE) {
+ continue;
+ }
+ child.setClipTopOptimization(0);
+ }
}
public boolean isDismissViewNotGone() {
@@ -2392,28 +2485,97 @@ public class NotificationStackScrollLayout extends ViewGroup
this.mPhoneStatusBar = phoneStatusBar;
}
+ public void setGroupManager(NotificationGroupManager groupManager) {
+ this.mGroupManager = groupManager;
+ }
+
public void onGoToKeyguard() {
+ requestAnimateEverything();
+ }
+
+ private void requestAnimateEverything() {
if (mIsExpanded && mAnimationsEnabled) {
mEverythingNeedsAnimation = true;
+ mNeedsAnimation = true;
requestChildrenUpdate();
}
}
private boolean isBelowLastNotification(float touchX, float touchY) {
- ExpandableView lastChildNotGone = (ExpandableView) getLastChildNotGone();
- if (lastChildNotGone == null) {
- return touchY > mIntrinsicPadding;
- }
- 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));
+ int childCount = getChildCount();
+ for (int i = childCount - 1; i >= 0; i--) {
+ ExpandableView child = (ExpandableView) getChildAt(i);
+ if (child.getVisibility() != View.GONE) {
+ float childTop = child.getY();
+ if (childTop > touchY) {
+ // we are above a notification entirely let's abort
+ return false;
+ }
+ boolean belowChild = touchY > childTop + child.getActualHeight();
+ if (child == mDismissView) {
+ if(!belowChild && !mDismissView.isOnEmptySpace(touchX - mDismissView.getX(),
+ touchY - childTop)) {
+ // We clicked on the dismiss button
+ return false;
+ }
+ } else if (child == mEmptyShadeView) {
+ // We arrived at the empty shade view, for which we accept all clicks
+ return true;
+ } else if (!belowChild){
+ // We are on a child
+ return false;
+ }
+ }
+ }
+ return touchY > mIntrinsicPadding;
+ }
+
+ public void setRemoveAnimationEnabled(boolean enabled) {
+ mRemoveAnimationEnabled = enabled;
+ }
+
+ private void updateExpandButtons() {
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ if (child instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ row.updateExpandButton();
+ }
+ }
+ }
+
+ @Override
+ public void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded) {
+ boolean animated = mAnimationsEnabled && mIsExpanded;
+ if (animated) {
+ mExpandedGroupView = changedRow;
+ mNeedsAnimation = true;
+ }
+ changedRow.setChildrenExpanded(expanded, animated);
+ onHeightChanged(changedRow, false /* needsAnimation */);
+ }
+
+ @Override
+ public void onGroupsProhibitedChanged() {
+ updateExpandButtons();
+ }
+
+ @Override
+ public void onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group) {
+ for (NotificationData.Entry entry : group.children) {
+ ExpandableNotificationRow row = entry.row;
+ if (indexOfChild(row) != -1) {
+ removeView(row);
+ group.summary.row.addChildNotification(row);
+ }
+ }
+ }
+
+ public void generateChildOrderChangedEvent() {
+ if (mIsExpanded && mAnimationsEnabled) {
+ mGenerateChildOrderChangedEvent = true;
+ mNeedsAnimation = true;
+ requestChildrenUpdate();
}
}
@@ -2553,6 +2715,14 @@ public class NotificationStackScrollLayout extends ViewGroup
.animateY()
.animateZ(),
+ // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
+ new AnimationFilter()
+ .animateAlpha()
+ .animateHeight()
+ .animateTopInset()
+ .animateY()
+ .animateZ(),
+
// ANIMATION_TYPE_EVERYTHING
new AnimationFilter()
.animateAlpha()
@@ -2607,6 +2777,9 @@ public class NotificationStackScrollLayout extends ViewGroup
// ANIMATION_TYPE_VIEW_RESIZE
StackStateAnimator.ANIMATION_DURATION_STANDARD,
+ // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
+ StackStateAnimator.ANIMATION_DURATION_EXPAND_CLICKED,
+
// ANIMATION_TYPE_EVERYTHING
StackStateAnimator.ANIMATION_DURATION_STANDARD,
};
@@ -2624,7 +2797,8 @@ public class NotificationStackScrollLayout extends ViewGroup
static final int ANIMATION_TYPE_GO_TO_FULL_SHADE = 10;
static final int ANIMATION_TYPE_HIDE_SENSITIVE = 11;
static final int ANIMATION_TYPE_VIEW_RESIZE = 12;
- static final int ANIMATION_TYPE_EVERYTHING = 13;
+ static final int ANIMATION_TYPE_GROUP_EXPANSION_CHANGED = 13;
+ static final int ANIMATION_TYPE_EVERYTHING = 14;
static final int DARK_ANIMATION_ORIGIN_INDEX_ABOVE = -1;
static final int DARK_ANIMATION_ORIGIN_INDEX_BELOW = -2;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index ddc4251..e7bf47b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -27,6 +27,7 @@ import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.ExpandableView;
import java.util.ArrayList;
+import java.util.List;
/**
* The Algorithm of the {@link com.android.systemui.statusbar.stack
@@ -171,6 +172,19 @@ public class StackScrollAlgorithm {
updateDimmedActivatedHideSensitive(ambientState, resultState, algorithmState);
updateClipping(resultState, algorithmState);
updateSpeedBumpState(resultState, algorithmState, ambientState.getSpeedBumpIndex());
+ getNotificationChildrenStates(resultState, algorithmState);
+ }
+
+ private void getNotificationChildrenStates(StackScrollState resultState,
+ StackScrollAlgorithmState algorithmState) {
+ int childCount = algorithmState.visibleChildren.size();
+ for (int i = 0; i < childCount; i++) {
+ ExpandableView v = algorithmState.visibleChildren.get(i);
+ if (v instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+ row.getChildrenStates(resultState);
+ }
+ }
}
private void updateSpeedBumpState(StackScrollState resultState,
@@ -178,7 +192,7 @@ public class StackScrollAlgorithm {
int childCount = algorithmState.visibleChildren.size();
for (int i = 0; i < childCount; i++) {
View child = algorithmState.visibleChildren.get(i);
- StackScrollState.ViewState childViewState = resultState.getViewStateForView(child);
+ StackViewState childViewState = resultState.getViewStateForView(child);
// The speed bump can also be gone, so equality needs to be taken when comparing
// indices.
@@ -194,7 +208,7 @@ public class StackScrollAlgorithm {
int childCount = algorithmState.visibleChildren.size();
for (int i = 0; i < childCount; i++) {
ExpandableView child = algorithmState.visibleChildren.get(i);
- StackScrollState.ViewState state = resultState.getViewStateForView(child);
+ StackViewState state = resultState.getViewStateForView(child);
float newYTranslation = state.yTranslation + state.height * (1f - state.scale) / 2f;
float newHeight = state.height * state.scale;
// apply clipping and shadow
@@ -242,8 +256,8 @@ public class StackScrollAlgorithm {
* @param backgroundHeight the desired background height. The shadows of the view will be
* based on this height and the content will be clipped from the top
*/
- private void updateChildClippingAndBackground(StackScrollState.ViewState state,
- float realHeight, float clipHeight, float backgroundHeight) {
+ private void updateChildClippingAndBackground(StackViewState state, float realHeight,
+ float clipHeight, float backgroundHeight) {
if (realHeight > clipHeight) {
// Rather overlap than create a hole.
state.topOverLap = (int) Math.floor((realHeight - clipHeight) / state.scale);
@@ -270,7 +284,7 @@ public class StackScrollAlgorithm {
int childCount = algorithmState.visibleChildren.size();
for (int i = 0; i < childCount; i++) {
View child = algorithmState.visibleChildren.get(i);
- StackScrollState.ViewState childViewState = resultState.getViewStateForView(child);
+ StackViewState childViewState = resultState.getViewStateForView(child);
childViewState.dimmed = dimmed;
childViewState.dark = dark;
childViewState.hideSensitive = hideSensitive;
@@ -297,14 +311,14 @@ public class StackScrollAlgorithm {
if (!draggedViews.contains(nextChild)) {
// only if the view is not dragged itself we modify its state to be fully
// visible
- StackScrollState.ViewState viewState = resultState.getViewStateForView(
+ StackViewState viewState = resultState.getViewStateForView(
nextChild);
// The child below the dragged one must be fully visible
viewState.alpha = 1;
}
// Lets set the alpha to the one it currently has, as its currently being dragged
- StackScrollState.ViewState viewState = resultState.getViewStateForView(draggedView);
+ StackViewState viewState = resultState.getViewStateForView(draggedView);
// The dragged child should keep the set alpha
viewState.alpha = draggedView.getAlpha();
}
@@ -320,12 +334,31 @@ public class StackScrollAlgorithm {
int childCount = hostView.getChildCount();
state.visibleChildren.clear();
state.visibleChildren.ensureCapacity(childCount);
+ int notGoneIndex = 0;
for (int i = 0; i < childCount; i++) {
ExpandableView v = (ExpandableView) hostView.getChildAt(i);
if (v.getVisibility() != View.GONE) {
- StackScrollState.ViewState viewState = resultState.getViewStateForView(v);
- viewState.notGoneIndex = state.visibleChildren.size();
+ StackViewState viewState = resultState.getViewStateForView(v);
+ viewState.notGoneIndex = notGoneIndex;
state.visibleChildren.add(v);
+ notGoneIndex++;
+
+ // handle the notgoneIndex for the children as well
+ if (v instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+ List<ExpandableNotificationRow> children =
+ row.getNotificationChildren();
+ if (row.areChildrenExpanded() && children != null) {
+ for (ExpandableNotificationRow childRow : children) {
+ if (childRow.getVisibility() != View.GONE) {
+ StackViewState childState
+ = resultState.getViewStateForView(childRow);
+ childState.notGoneIndex = notGoneIndex;
+ notGoneIndex++;
+ }
+ }
+ }
+ }
}
}
}
@@ -355,8 +388,8 @@ public class StackScrollAlgorithm {
int numberOfElementsCompletelyIn = (int) algorithmState.itemsInTopStack;
for (int i = 0; i < childCount; i++) {
ExpandableView child = algorithmState.visibleChildren.get(i);
- StackScrollState.ViewState childViewState = resultState.getViewStateForView(child);
- childViewState.location = StackScrollState.ViewState.LOCATION_UNKNOWN;
+ StackViewState childViewState = resultState.getViewStateForView(child);
+ childViewState.location = StackViewState.LOCATION_UNKNOWN;
int childHeight = getMaxAllowedChildHeight(child);
float yPositionInScrollViewAfterElement = yPositionInScrollView
+ childHeight
@@ -413,7 +446,7 @@ public class StackScrollAlgorithm {
} else {
// Case 3:
// We are in the regular scroll area.
- childViewState.location = StackScrollState.ViewState.LOCATION_MAIN_AREA;
+ childViewState.location = StackViewState.LOCATION_MAIN_AREA;
clampYTranslation(childViewState, childHeight);
}
@@ -427,9 +460,9 @@ public class StackScrollAlgorithm {
bottomPeekStart - mCollapseSecondCardPadding
- childViewState.yTranslation, mCollapsedSize);
}
- childViewState.location = StackScrollState.ViewState.LOCATION_FIRST_CARD;
+ childViewState.location = StackViewState.LOCATION_FIRST_CARD;
}
- if (childViewState.location == StackScrollState.ViewState.LOCATION_UNKNOWN) {
+ if (childViewState.location == StackViewState.LOCATION_UNKNOWN) {
Log.wtf(LOG_TAG, "Failed to assign location for child " + i);
}
currentYPosition = childViewState.yTranslation + childHeight + mPaddingBetweenElements;
@@ -445,7 +478,7 @@ public class StackScrollAlgorithm {
* @param childViewState the view state of the child
* @param childHeight the height of this child
*/
- private void clampYTranslation(StackScrollState.ViewState childViewState, int childHeight) {
+ private void clampYTranslation(StackViewState childViewState, int childHeight) {
clampPositionToBottomStackStart(childViewState, childHeight);
clampPositionToTopStackEnd(childViewState, childHeight);
}
@@ -457,7 +490,7 @@ public class StackScrollAlgorithm {
* @param childViewState the view state of the child
* @param childHeight the height of this child
*/
- private void clampPositionToBottomStackStart(StackScrollState.ViewState childViewState,
+ private void clampPositionToBottomStackStart(StackViewState childViewState,
int childHeight) {
childViewState.yTranslation = Math.min(childViewState.yTranslation,
mInnerHeight - mBottomStackPeekSize - mCollapseSecondCardPadding - childHeight);
@@ -470,7 +503,7 @@ public class StackScrollAlgorithm {
* @param childViewState the view state of the child
* @param childHeight the height of this child
*/
- private void clampPositionToTopStackEnd(StackScrollState.ViewState childViewState,
+ private void clampPositionToTopStackEnd(StackViewState childViewState,
int childHeight) {
childViewState.yTranslation = Math.max(childViewState.yTranslation,
mCollapsedSize - childHeight);
@@ -489,7 +522,7 @@ public class StackScrollAlgorithm {
private void updateStateForChildTransitioningInBottom(StackScrollAlgorithmState algorithmState,
float transitioningPositionStart, float bottomPeakStart, float currentYPosition,
- StackScrollState.ViewState childViewState, int childHeight) {
+ StackViewState childViewState, int childHeight) {
// This is the transitioning element on top of bottom stack, calculate how far we are in.
algorithmState.partialInBottom = 1.0f - (
@@ -510,11 +543,11 @@ public class StackScrollAlgorithm {
// We want at least to be at the end of the top stack when collapsing
clampPositionToTopStackEnd(childViewState, newHeight);
- childViewState.location = StackScrollState.ViewState.LOCATION_MAIN_AREA;
+ childViewState.location = StackViewState.LOCATION_MAIN_AREA;
}
private void updateStateForChildFullyInBottomStack(StackScrollAlgorithmState algorithmState,
- float transitioningPositionStart, StackScrollState.ViewState childViewState,
+ float transitioningPositionStart, StackViewState childViewState,
int childHeight) {
float currentYPosition;
@@ -524,7 +557,7 @@ public class StackScrollAlgorithm {
currentYPosition = transitioningPositionStart
+ mBottomStackIndentationFunctor.getValue(algorithmState.itemsInBottomStack)
- mPaddingBetweenElements;
- childViewState.location = StackScrollState.ViewState.LOCATION_BOTTOM_STACK_PEEKING;
+ childViewState.location = StackViewState.LOCATION_BOTTOM_STACK_PEEKING;
} else {
// we are fully inside the stack
if (algorithmState.itemsInBottomStack > MAX_ITEMS_IN_BOTTOM_STACK + 2) {
@@ -533,7 +566,7 @@ public class StackScrollAlgorithm {
> MAX_ITEMS_IN_BOTTOM_STACK + 1) {
childViewState.alpha = 1.0f - algorithmState.partialInBottom;
}
- childViewState.location = StackScrollState.ViewState.LOCATION_BOTTOM_STACK_HIDDEN;
+ childViewState.location = StackViewState.LOCATION_BOTTOM_STACK_HIDDEN;
currentYPosition = mInnerHeight;
}
childViewState.yTranslation = currentYPosition - childHeight;
@@ -542,7 +575,7 @@ public class StackScrollAlgorithm {
private void updateStateForTopStackChild(StackScrollAlgorithmState algorithmState,
int numberOfElementsCompletelyIn, int i, int childHeight,
- StackScrollState.ViewState childViewState, float scrollOffset) {
+ StackViewState childViewState, float scrollOffset) {
// First we calculate the index relative to the current stack window of size at most
@@ -574,7 +607,7 @@ public class StackScrollAlgorithm {
- mTopStackIndentationFunctor.getValue(numItemsBefore);
childViewState.yTranslation = currentChildEndY - childHeight;
}
- childViewState.location = StackScrollState.ViewState.LOCATION_TOP_STACK_PEEKING;
+ childViewState.location = StackViewState.LOCATION_TOP_STACK_PEEKING;
} else {
if (paddedIndex == -1) {
childViewState.alpha = 1.0f - algorithmState.partialInTop;
@@ -583,7 +616,7 @@ public class StackScrollAlgorithm {
childViewState.alpha = 0.0f;
}
childViewState.yTranslation = mCollapsedSize - childHeight;
- childViewState.location = StackScrollState.ViewState.LOCATION_TOP_STACK_HIDDEN;
+ childViewState.location = StackViewState.LOCATION_TOP_STACK_HIDDEN;
}
@@ -605,7 +638,7 @@ public class StackScrollAlgorithm {
// find the number of elements in the top stack.
for (int i = 0; i < childCount; i++) {
ExpandableView child = algorithmState.visibleChildren.get(i);
- StackScrollState.ViewState childViewState = resultState.getViewStateForView(child);
+ StackViewState childViewState = resultState.getViewStateForView(child);
int childHeight = getMaxAllowedChildHeight(child);
float yPositionInScrollViewAfterElement = yPositionInScrollView
+ childHeight
@@ -676,7 +709,7 @@ public class StackScrollAlgorithm {
int childCount = algorithmState.visibleChildren.size();
for (int i = 0; i < childCount; i++) {
View child = algorithmState.visibleChildren.get(i);
- StackScrollState.ViewState childViewState = resultState.getViewStateForView(child);
+ StackViewState childViewState = resultState.getViewStateForView(child);
if (i < algorithmState.itemsInTopStack) {
float stackIndex = algorithmState.itemsInTopStack - i;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
index 0b1ce8f..feae590 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.stack;
-import android.graphics.Rect;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -24,10 +23,12 @@ import android.view.ViewGroup;
import com.android.systemui.R;
import com.android.systemui.statusbar.DismissView;
import com.android.systemui.statusbar.EmptyShadeView;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.SpeedBumpView;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
/**
@@ -39,13 +40,12 @@ public class StackScrollState {
private static final String CHILD_NOT_FOUND_TAG = "StackScrollStateNoSuchChild";
private final ViewGroup mHostView;
- private Map<ExpandableView, ViewState> mStateMap;
- private final Rect mClipRect = new Rect();
+ private Map<ExpandableView, StackViewState> mStateMap;
private final int mClearAllTopPadding;
public StackScrollState(ViewGroup hostView) {
mHostView = hostView;
- mStateMap = new HashMap<ExpandableView, ViewState>();
+ mStateMap = new HashMap<ExpandableView, StackViewState>();
mClearAllTopPadding = hostView.getContext().getResources().getDimensionPixelSize(
R.dimen.clear_all_padding_top);
}
@@ -58,20 +58,36 @@ public class StackScrollState {
int numChildren = mHostView.getChildCount();
for (int i = 0; i < numChildren; i++) {
ExpandableView child = (ExpandableView) mHostView.getChildAt(i);
- ViewState viewState = mStateMap.get(child);
- if (viewState == null) {
- viewState = new ViewState();
- mStateMap.put(child, viewState);
+ resetViewState(child);
+
+ // handling reset for child notifications
+ if (child instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ List<ExpandableNotificationRow> children =
+ row.getNotificationChildren();
+ if (row.areChildrenExpanded() && children != null) {
+ for (ExpandableNotificationRow childRow : children) {
+ resetViewState(childRow);
+ }
+ }
}
- // initialize with the default values of the view
- viewState.height = child.getIntrinsicHeight();
- viewState.gone = child.getVisibility() == View.GONE;
- viewState.alpha = 1;
- viewState.notGoneIndex = -1;
}
}
- public ViewState getViewStateForView(View requestedView) {
+ private void resetViewState(ExpandableView view) {
+ StackViewState viewState = mStateMap.get(view);
+ if (viewState == null) {
+ viewState = new StackViewState();
+ mStateMap.put(view, viewState);
+ }
+ // initialize with the default values of the view
+ viewState.height = view.getIntrinsicHeight();
+ viewState.gone = view.getVisibility() == View.GONE;
+ viewState.alpha = 1;
+ viewState.notGoneIndex = -1;
+ }
+
+ public StackViewState getViewStateForView(View requestedView) {
return mStateMap.get(requestedView);
}
@@ -87,126 +103,139 @@ public class StackScrollState {
int numChildren = mHostView.getChildCount();
for (int i = 0; i < numChildren; i++) {
ExpandableView child = (ExpandableView) mHostView.getChildAt(i);
- ViewState state = mStateMap.get(child);
- if (state == null) {
- Log.wtf(CHILD_NOT_FOUND_TAG, "No child state was found when applying this state " +
- "to the hostView");
+ StackViewState state = mStateMap.get(child);
+ if (!applyState(child, state)) {
continue;
}
- if (!state.gone) {
- float alpha = child.getAlpha();
- float yTranslation = child.getTranslationY();
- float xTranslation = child.getTranslationX();
- float zTranslation = child.getTranslationZ();
- float scale = child.getScaleX();
- int height = child.getActualHeight();
- float newAlpha = state.alpha;
- float newYTranslation = state.yTranslation;
- float newZTranslation = state.zTranslation;
- float newScale = state.scale;
- int newHeight = state.height;
- boolean becomesInvisible = newAlpha == 0.0f;
- if (alpha != newAlpha && xTranslation == 0) {
- // apply layer type
- boolean becomesFullyVisible = newAlpha == 1.0f;
- boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible;
- int layerType = child.getLayerType();
- int newLayerType = newLayerTypeIsHardware
- ? View.LAYER_TYPE_HARDWARE
- : View.LAYER_TYPE_NONE;
- if (layerType != newLayerType) {
- child.setLayerType(newLayerType, null);
- }
-
- // apply alpha
- child.setAlpha(newAlpha);
- }
-
- // apply visibility
- int oldVisibility = child.getVisibility();
- int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE;
- if (newVisibility != oldVisibility) {
- child.setVisibility(newVisibility);
- }
-
- // apply yTranslation
- if (yTranslation != newYTranslation) {
- child.setTranslationY(newYTranslation);
- }
+ if(child instanceof SpeedBumpView) {
+ performSpeedBumpAnimation(i, (SpeedBumpView) child, state, 0);
+ } else if (child instanceof DismissView) {
+ DismissView dismissView = (DismissView) child;
+ boolean visible = state.topOverLap < mClearAllTopPadding;
+ dismissView.performVisibilityAnimation(visible && !dismissView.willBeGone());
+ } else if (child instanceof EmptyShadeView) {
+ EmptyShadeView emptyShadeView = (EmptyShadeView) child;
+ boolean visible = state.topOverLap <= 0;
+ emptyShadeView.performVisibilityAnimation(
+ visible && !emptyShadeView.willBeGone());
+ }
+ }
+ }
- // apply zTranslation
- if (zTranslation != newZTranslation) {
- child.setTranslationZ(newZTranslation);
- }
+ /**
+ * Applies a {@link StackViewState} to an {@link ExpandableView}.
+ *
+ * @return whether the state was applied correctly
+ */
+ public boolean applyState(ExpandableView view, StackViewState state) {
+ if (state == null) {
+ Log.wtf(CHILD_NOT_FOUND_TAG, "No child state was found when applying this state " +
+ "to the hostView");
+ return false;
+ }
+ if (state.gone) {
+ return false;
+ }
+ applyViewState(view, state);
- // apply scale
- if (scale != newScale) {
- child.setScaleX(newScale);
- child.setScaleY(newScale);
- }
+ int height = view.getActualHeight();
+ int newHeight = state.height;
- // apply height
- if (height != newHeight) {
- child.setActualHeight(newHeight, false /* notifyListeners */);
- }
+ // apply height
+ if (height != newHeight) {
+ view.setActualHeight(newHeight, false /* notifyListeners */);
+ }
- // apply dimming
- child.setDimmed(state.dimmed, false /* animate */);
+ // apply dimming
+ view.setDimmed(state.dimmed, false /* animate */);
- // apply dark
- child.setDark(state.dark, false /* animate */, 0 /* delay */);
+ // apply dark
+ view.setDark(state.dark, false /* animate */, 0 /* delay */);
- // apply hiding sensitive
- child.setHideSensitive(
- state.hideSensitive, false /* animated */, 0 /* delay */, 0 /* duration */);
+ // apply hiding sensitive
+ view.setHideSensitive(
+ state.hideSensitive, false /* animated */, 0 /* delay */, 0 /* duration */);
- // apply speed bump state
- child.setBelowSpeedBump(state.belowSpeedBump);
+ // apply speed bump state
+ view.setBelowSpeedBump(state.belowSpeedBump);
- // apply clipping
- float oldClipTopAmount = child.getClipTopAmount();
- if (oldClipTopAmount != state.clipTopAmount) {
- child.setClipTopAmount(state.clipTopAmount);
- }
- updateChildClip(child, newHeight, state.topOverLap);
-
- if(child instanceof SpeedBumpView) {
- performSpeedBumpAnimation(i, (SpeedBumpView) child, state, 0);
- } else if (child instanceof DismissView) {
- DismissView dismissView = (DismissView) child;
- boolean visible = state.topOverLap < mClearAllTopPadding;
- dismissView.performVisibilityAnimation(visible && !dismissView.willBeGone());
- } else if (child instanceof EmptyShadeView) {
- EmptyShadeView emptyShadeView = (EmptyShadeView) child;
- boolean visible = state.topOverLap <= 0;
- emptyShadeView.performVisibilityAnimation(
- visible && !emptyShadeView.willBeGone());
- }
- }
+ // apply clipping
+ float oldClipTopAmount = view.getClipTopAmount();
+ if (oldClipTopAmount != state.clipTopAmount) {
+ view.setClipTopAmount(state.clipTopAmount);
}
+ float oldClipTopOptimization = view.getClipTopOptimization();
+ if (oldClipTopOptimization != state.topOverLap) {
+ view.setClipTopOptimization(state.topOverLap);
+ }
+ if (view instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ row.applyChildrenState(this);
+ }
+ return true;
}
/**
- * Updates the clipping of a view
- *
- * @param child the view to update
- * @param height the currently applied height of the view
- * @param clipInset how much should this view be clipped from the top
+ * Applies a {@link ViewState} to a normal view.
*/
- private void updateChildClip(View child, int height, int clipInset) {
- mClipRect.set(0,
- clipInset,
- child.getWidth(),
- height);
- child.setClipBounds(mClipRect);
+ public void applyViewState(View view, ViewState state) {
+ float alpha = view.getAlpha();
+ float yTranslation = view.getTranslationY();
+ float xTranslation = view.getTranslationX();
+ float zTranslation = view.getTranslationZ();
+ float scale = view.getScaleX();
+ float newAlpha = state.alpha;
+ float newYTranslation = state.yTranslation;
+ float newZTranslation = state.zTranslation;
+ float newScale = state.scale;
+ boolean becomesInvisible = newAlpha == 0.0f;
+ if (alpha != newAlpha && xTranslation == 0) {
+ // apply layer type
+ boolean becomesFullyVisible = newAlpha == 1.0f;
+ boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible
+ && view.hasOverlappingRendering();
+ int layerType = view.getLayerType();
+ int newLayerType = newLayerTypeIsHardware
+ ? View.LAYER_TYPE_HARDWARE
+ : View.LAYER_TYPE_NONE;
+ if (layerType != newLayerType) {
+ view.setLayerType(newLayerType, null);
+ }
+
+ // apply alpha
+ view.setAlpha(newAlpha);
+ }
+
+ // apply visibility
+ int oldVisibility = view.getVisibility();
+ int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE;
+ if (newVisibility != oldVisibility) {
+ view.setVisibility(newVisibility);
+ }
+
+ // apply yTranslation
+ if (yTranslation != newYTranslation) {
+ view.setTranslationY(newYTranslation);
+ }
+
+ // apply zTranslation
+ if (zTranslation != newZTranslation) {
+ view.setTranslationZ(newZTranslation);
+ }
+
+ // apply scale
+ if (scale != newScale) {
+ view.setScaleX(newScale);
+ view.setScaleY(newScale);
+ }
}
- public void performSpeedBumpAnimation(int i, SpeedBumpView speedBump, ViewState state,
+ public void performSpeedBumpAnimation(int i, SpeedBumpView speedBump, StackViewState state,
long delay) {
View nextChild = getNextChildNotGone(i);
if (nextChild != null) {
float lineEnd = state.yTranslation + state.height / 2;
- ViewState nextState = getViewStateForView(nextChild);
+ StackViewState nextState = getViewStateForView(nextChild);
boolean startIsAboveNext = nextState.yTranslation > lineEnd;
speedBump.animateDivider(startIsAboveNext, delay, null /* onFinishedRunnable */);
}
@@ -223,53 +252,4 @@ public class StackScrollState {
return null;
}
- public static class ViewState {
-
- // These are flags such that we can create masks for filtering.
-
- public static final int LOCATION_UNKNOWN = 0x00;
- public static final int LOCATION_FIRST_CARD = 0x01;
- public static final int LOCATION_TOP_STACK_HIDDEN = 0x02;
- public static final int LOCATION_TOP_STACK_PEEKING = 0x04;
- public static final int LOCATION_MAIN_AREA = 0x08;
- public static final int LOCATION_BOTTOM_STACK_PEEKING = 0x10;
- public static final int LOCATION_BOTTOM_STACK_HIDDEN = 0x20;
- /** The view isn't layouted at all. */
- public static final int LOCATION_GONE = 0x40;
-
- float alpha;
- float yTranslation;
- float zTranslation;
- int height;
- boolean gone;
- float scale;
- boolean dimmed;
- boolean dark;
- boolean hideSensitive;
- boolean belowSpeedBump;
-
- /**
- * The amount which the view should be clipped from the top. This is calculated to
- * perceive consistent shadows.
- */
- int clipTopAmount;
-
- /**
- * How much does the child overlap with the previous view on the top? Can be used for
- * a clipping optimization
- */
- int topOverLap;
-
- /**
- * The index of the view, only accounting for views not equal to GONE
- */
- int notGoneIndex;
-
- /**
- * The location this view is currently rendered at.
- *
- * <p>See <code>LOCATION_</code> flags.</p>
- */
- int location;
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
index b027787..b249fbf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
@@ -26,6 +26,7 @@ import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import com.android.systemui.R;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.SpeedBumpView;
@@ -42,12 +43,15 @@ public class StackStateAnimator {
public static final int ANIMATION_DURATION_STANDARD = 360;
public static final int ANIMATION_DURATION_GO_TO_FULL_SHADE = 448;
public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464;
+ public static final int ANIMATION_DURATION_EXPAND_CLICKED = 360;
public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220;
public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80;
+ public static final int ANIMATION_DELAY_PER_ELEMENT_EXPAND_CHILDREN = 54;
public static final int ANIMATION_DELAY_PER_ELEMENT_MANUAL = 32;
public static final int ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE = 48;
public static final int ANIMATION_DELAY_PER_ELEMENT_DARK = 24;
- private static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE = 2;
+ public static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE = 2;
+ public static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE_CHILDREN = 3;
private static final int TAG_ANIMATOR_TRANSLATION_Y = R.id.translation_y_animator_tag;
private static final int TAG_ANIMATOR_TRANSLATION_Z = R.id.translation_z_animator_tag;
@@ -85,6 +89,7 @@ public class StackStateAnimator {
private ValueAnimator mTopOverScrollAnimator;
private ValueAnimator mBottomOverScrollAnimator;
+ private ExpandableNotificationRow mChildExpandingView;
public StackStateAnimator(NotificationStackScrollLayout hostLayout) {
mHostLayout = hostLayout;
@@ -113,13 +118,13 @@ public class StackStateAnimator {
for (int i = 0; i < childCount; i++) {
final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
- StackScrollState.ViewState viewState = finalState.getViewStateForView(child);
+ StackViewState viewState = finalState.getViewStateForView(child);
if (viewState == null || child.getVisibility() == View.GONE) {
continue;
}
- child.setClipBounds(null);
- startAnimations(child, viewState, finalState, i);
+ child.setClipTopOptimization(0);
+ startStackAnimations(child, viewState, finalState, i, -1 /* fixedDelay */);
}
if (!isRunning()) {
// no child has preformed any animation, lets finish
@@ -127,6 +132,7 @@ public class StackStateAnimator {
}
mNewEvents.clear();
mNewAddChildren.clear();
+ mChildExpandingView = null;
}
private int findLastNotAddedIndex(StackScrollState finalState) {
@@ -134,7 +140,7 @@ public class StackStateAnimator {
for (int i = childCount - 1; i >= 0; i--) {
final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
- StackScrollState.ViewState viewState = finalState.getViewStateForView(child);
+ StackViewState viewState = finalState.getViewStateForView(child);
if (viewState == null || child.getVisibility() == View.GONE) {
continue;
}
@@ -145,18 +151,29 @@ public class StackStateAnimator {
return -1;
}
+
/**
- * Start an animation to the given viewState
+ * Start an animation to the given {@link StackViewState}.
+ *
+ * @param child the child to start the animation on
+ * @param viewState the {@link StackViewState} of the view to animate to
+ * @param finalState the final state after the animation
+ * @param i the index of the view; only relevant if the view is the speed bump and is
+ * ignored otherwise
+ * @param fixedDelay a fixed delay if desired or -1 if the delay should be calculated
*/
- private void startAnimations(final ExpandableView child, StackScrollState.ViewState viewState,
- StackScrollState finalState, int i) {
- int childVisibility = child.getVisibility();
- boolean wasVisible = childVisibility == View.VISIBLE;
+ public void startStackAnimations(final ExpandableView child, StackViewState viewState,
+ StackScrollState finalState, int i, long fixedDelay) {
final float alpha = viewState.alpha;
- if (!wasVisible && alpha != 0 && !viewState.gone) {
- child.setVisibility(View.VISIBLE);
+ boolean wasAdded = mNewAddChildren.contains(child);
+ long duration = mCurrentLength;
+ if (wasAdded && mAnimationFilter.hasGoToFullShadeEvent) {
+ child.setTranslationY(child.getTranslationY() + mGoToFullShadeAppearingTranslation);
+ float longerDurationFactor = viewState.notGoneIndex - mCurrentLastNotAddedIndex;
+ longerDurationFactor = (float) Math.pow(longerDurationFactor, 0.7f);
+ duration = ANIMATION_DURATION_APPEAR_DISAPPEAR + 50 +
+ (long) (100 * longerDurationFactor);
}
-
boolean yTranslationChanging = child.getTranslationY() != viewState.yTranslation;
boolean zTranslationChanging = child.getTranslationZ() != viewState.zTranslation;
boolean scaleChanging = child.getScaleX() != viewState.scale;
@@ -164,94 +181,40 @@ public class StackStateAnimator {
boolean heightChanging = viewState.height != child.getActualHeight();
boolean darkChanging = viewState.dark != child.isDark();
boolean topInsetChanging = viewState.clipTopAmount != child.getClipTopAmount();
- boolean wasAdded = mNewAddChildren.contains(child);
boolean hasDelays = mAnimationFilter.hasDelays;
boolean isDelayRelevant = yTranslationChanging || zTranslationChanging || scaleChanging ||
alphaChanging || heightChanging || topInsetChanging || darkChanging;
- boolean noAnimation = wasAdded;
long delay = 0;
- long duration = mCurrentLength;
- if (hasDelays && isDelayRelevant || wasAdded) {
+ if (fixedDelay != -1) {
+ delay = fixedDelay;
+ } else if (hasDelays && isDelayRelevant || wasAdded) {
delay = mCurrentAdditionalDelay + calculateChildAnimationDelay(viewState, finalState);
}
- if (wasAdded && mAnimationFilter.hasGoToFullShadeEvent) {
- child.setTranslationY(child.getTranslationY() + mGoToFullShadeAppearingTranslation);
- yTranslationChanging = true;
- float longerDurationFactor = viewState.notGoneIndex - mCurrentLastNotAddedIndex;
- longerDurationFactor = (float) Math.pow(longerDurationFactor, 0.7f);
- duration = ANIMATION_DURATION_APPEAR_DISAPPEAR + 50 +
- (long) (100 * longerDurationFactor);
- }
-
- // start translationY animation
- if (yTranslationChanging) {
- if (noAnimation && !mAnimationFilter.hasGoToFullShadeEvent) {
- child.setTranslationY(viewState.yTranslation);
- } else {
- startYTranslationAnimation(child, viewState, duration, delay);
- }
- }
-
- // start translationZ animation
- if (zTranslationChanging) {
- if (noAnimation) {
- child.setTranslationZ(viewState.zTranslation);
- } else {
- startZTranslationAnimation(child, viewState, duration, delay);
- }
- }
-
- // start scale animation
- if (scaleChanging) {
- if (noAnimation) {
- child.setScaleX(viewState.scale);
- child.setScaleY(viewState.scale);
- } else {
- startScaleAnimation(child, viewState, duration);
- }
- }
-
- // start alpha animation
- if (alphaChanging && child.getTranslationX() == 0) {
- if (noAnimation) {
- child.setAlpha(viewState.alpha);
- } else {
- startAlphaAnimation(child, viewState, duration, delay);
- }
- }
+ startViewAnimations(child, viewState, delay, duration);
// start height animation
if (heightChanging && child.getActualHeight() != 0) {
- if (noAnimation) {
- child.setActualHeight(viewState.height, false);
- } else {
- startHeightAnimation(child, viewState, duration, delay);
- }
+ startHeightAnimation(child, viewState, duration, delay);
}
// start top inset animation
if (topInsetChanging) {
- if (noAnimation) {
- child.setClipTopAmount(viewState.clipTopAmount);
- } else {
- startInsetAnimation(child, viewState, duration, delay);
- }
+ startInsetAnimation(child, viewState, duration, delay);
}
// start dimmed animation
- child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed && !wasAdded
- && !noAnimation);
+ child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed);
// start dark animation
- child.setDark(viewState.dark, mAnimationFilter.animateDark && !noAnimation, delay);
+ child.setDark(viewState.dark, mAnimationFilter.animateDark, delay);
// apply speed bump state
child.setBelowSpeedBump(viewState.belowSpeedBump);
// start hiding sensitive animation
- child.setHideSensitive(viewState.hideSensitive, mAnimationFilter.animateHideSensitive &&
- !wasAdded && !noAnimation, delay, duration);
+ child.setHideSensitive(viewState.hideSensitive, mAnimationFilter.animateHideSensitive,
+ delay, duration);
if (wasAdded) {
child.performAddAnimation(delay, mCurrentLength);
@@ -259,10 +222,55 @@ public class StackStateAnimator {
if (child instanceof SpeedBumpView) {
finalState.performSpeedBumpAnimation(i, (SpeedBumpView) child, viewState,
delay + duration);
+ } else if (child instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ row.startChildAnimation(finalState, this, child == mChildExpandingView, delay,
+ duration);
}
}
- private long calculateChildAnimationDelay(StackScrollState.ViewState viewState,
+ /**
+ * Start an animation to a new {@link ViewState}.
+ *
+ * @param child the child to start the animation on
+ * @param viewState the {@link StackViewState} of the view to animate to
+ * @param delay a fixed delay
+ * @param duration the duration of the animation
+ */
+ public void startViewAnimations(View child, ViewState viewState, long delay, long duration) {
+ boolean wasVisible = child.getVisibility() == View.VISIBLE;
+ final float alpha = viewState.alpha;
+ if (!wasVisible && alpha != 0 && !viewState.gone) {
+ child.setVisibility(View.VISIBLE);
+ }
+ boolean yTranslationChanging = child.getTranslationY() != viewState.yTranslation;
+ boolean zTranslationChanging = child.getTranslationZ() != viewState.zTranslation;
+ boolean scaleChanging = child.getScaleX() != viewState.scale;
+ float childAlpha = child.getVisibility() == View.INVISIBLE ? 0.0f : child.getAlpha();
+ boolean alphaChanging = viewState.alpha != childAlpha;
+
+ // start translationY animation
+ if (yTranslationChanging) {
+ startYTranslationAnimation(child, viewState, duration, delay);
+ }
+
+ // start translationZ animation
+ if (zTranslationChanging) {
+ startZTranslationAnimation(child, viewState, duration, delay);
+ }
+
+ // start scale animation
+ if (scaleChanging) {
+ startScaleAnimation(child, viewState, duration);
+ }
+
+ // start alpha animation
+ if (alphaChanging && child.getTranslationX() == 0) {
+ startAlphaAnimation(child, viewState, duration, delay);
+ }
+ }
+
+ private long calculateChildAnimationDelay(StackViewState viewState,
StackScrollState finalState) {
if (mAnimationFilter.hasDarkEvent) {
return calculateDelayDark(viewState);
@@ -314,7 +322,7 @@ public class StackStateAnimator {
return minDelay;
}
- private long calculateDelayDark(StackScrollState.ViewState viewState) {
+ private long calculateDelayDark(StackViewState viewState) {
int referenceIndex;
if (mAnimationFilter.darkAnimationOriginIndex ==
NotificationStackScrollLayout.AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE) {
@@ -328,14 +336,14 @@ public class StackStateAnimator {
return Math.abs(referenceIndex - viewState.notGoneIndex) * ANIMATION_DELAY_PER_ELEMENT_DARK;
}
- private long calculateDelayGoToFullShade(StackScrollState.ViewState viewState) {
+ private long calculateDelayGoToFullShade(StackViewState viewState) {
float index = viewState.notGoneIndex;
index = (float) Math.pow(index, 0.7f);
return (long) (index * ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE);
}
private void startHeightAnimation(final ExpandableView child,
- StackScrollState.ViewState viewState, long duration, long delay) {
+ StackViewState viewState, long duration, long delay) {
Integer previousStartValue = getChildTag(child, TAG_START_HEIGHT);
Integer previousEndValue = getChildTag(child, TAG_END_HEIGHT);
int newEndValue = viewState.height;
@@ -394,7 +402,7 @@ public class StackStateAnimator {
}
private void startInsetAnimation(final ExpandableView child,
- StackScrollState.ViewState viewState, long duration, long delay) {
+ StackViewState viewState, long duration, long delay) {
Integer previousStartValue = getChildTag(child, TAG_START_TOP_INSET);
Integer previousEndValue = getChildTag(child, TAG_END_TOP_INSET);
int newEndValue = viewState.clipTopAmount;
@@ -451,8 +459,8 @@ public class StackStateAnimator {
child.setTag(TAG_END_TOP_INSET, newEndValue);
}
- private void startAlphaAnimation(final ExpandableView child,
- final StackScrollState.ViewState viewState, long duration, long delay) {
+ private void startAlphaAnimation(final View child,
+ final ViewState viewState, long duration, long delay) {
Float previousStartValue = getChildTag(child,TAG_START_ALPHA);
Float previousEndValue = getChildTag(child,TAG_END_ALPHA);
final float newEndValue = viewState.alpha;
@@ -525,8 +533,8 @@ public class StackStateAnimator {
child.setTag(TAG_END_ALPHA, newEndValue);
}
- private void startZTranslationAnimation(final ExpandableView child,
- final StackScrollState.ViewState viewState, long duration, long delay) {
+ private void startZTranslationAnimation(final View child,
+ final ViewState viewState, long duration, long delay) {
Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Z);
Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Z);
float newEndValue = viewState.zTranslation;
@@ -577,8 +585,8 @@ public class StackStateAnimator {
child.setTag(TAG_END_TRANSLATION_Z, newEndValue);
}
- private void startYTranslationAnimation(final ExpandableView child,
- StackScrollState.ViewState viewState, long duration, long delay) {
+ private void startYTranslationAnimation(final View child,
+ ViewState viewState, long duration, long delay) {
Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Y);
Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Y);
float newEndValue = viewState.yTranslation;
@@ -630,8 +638,8 @@ public class StackStateAnimator {
child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
}
- private void startScaleAnimation(final ExpandableView child,
- StackScrollState.ViewState viewState, long duration) {
+ private void startScaleAnimation(final View child,
+ ViewState viewState, long duration) {
Float previousStartValue = getChildTag(child, TAG_START_SCALE);
Float previousEndValue = getChildTag(child, TAG_END_SCALE);
float newEndValue = viewState.scale;
@@ -765,7 +773,7 @@ public class StackStateAnimator {
NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD) {
// This item is added, initialize it's properties.
- StackScrollState.ViewState viewState = finalState
+ StackViewState viewState = finalState
.getViewStateForView(changingView);
if (viewState == null) {
// The position for this child was never generated, let's continue.
@@ -776,10 +784,7 @@ public class StackStateAnimator {
finalState.removeViewStateForView(changingView);
continue;
}
- changingView.setAlpha(viewState.alpha);
- changingView.setTranslationY(viewState.yTranslation);
- changingView.setTranslationZ(viewState.zTranslation);
- changingView.setActualHeight(viewState.height, false);
+ finalState.applyState(changingView, viewState);
mNewAddChildren.add(changingView);
} else if (event.animationType ==
@@ -791,7 +796,7 @@ public class StackStateAnimator {
// Find the amount to translate up. This is needed in order to understand the
// direction of the remove animation (either downwards or upwards)
- StackScrollState.ViewState viewState = finalState
+ StackViewState viewState = finalState
.getViewStateForView(event.viewAfterChangingView);
int actualHeight = changingView.getActualHeight();
// upwards by default
@@ -813,11 +818,16 @@ public class StackStateAnimator {
mHostLayout.getOverlay().remove(changingView);
}
});
- } else if (event.animationType ==
+ } else if (event.animationType ==
NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) {
// A race condition can trigger the view to be added to the overlay even though
// it is swiped out. So let's remove it
mHostLayout.getOverlay().remove(changingView);
+ } else if (event.animationType == NotificationStackScrollLayout
+ .AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) event.changingView;
+ row.prepareExpansionChanged(finalState);
+ mChildExpandingView = row;
}
mNewEvents.add(event);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackViewState.java
new file mode 100644
index 0000000..55ef440
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackViewState.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.stack;
+
+import android.view.View;
+
+import com.android.systemui.statusbar.ExpandableView;
+
+/**
+* A state of an expandable view
+*/
+public class StackViewState extends ViewState {
+
+ // These are flags such that we can create masks for filtering.
+
+ public static final int LOCATION_UNKNOWN = 0x00;
+ public static final int LOCATION_FIRST_CARD = 0x01;
+ public static final int LOCATION_TOP_STACK_HIDDEN = 0x02;
+ public static final int LOCATION_TOP_STACK_PEEKING = 0x04;
+ public static final int LOCATION_MAIN_AREA = 0x08;
+ public static final int LOCATION_BOTTOM_STACK_PEEKING = 0x10;
+ public static final int LOCATION_BOTTOM_STACK_HIDDEN = 0x20;
+ /** The view isn't layouted at all. */
+ public static final int LOCATION_GONE = 0x40;
+
+ public int height;
+ public boolean dimmed;
+ public boolean dark;
+ public boolean hideSensitive;
+ public boolean belowSpeedBump;
+
+ /**
+ * The amount which the view should be clipped from the top. This is calculated to
+ * perceive consistent shadows.
+ */
+ public int clipTopAmount;
+
+ /**
+ * How much does the child overlap with the previous view on the top? Can be used for
+ * a clipping optimization
+ */
+ public int topOverLap;
+
+ /**
+ * The index of the view, only accounting for views not equal to GONE
+ */
+ public int notGoneIndex;
+
+ /**
+ * The location this view is currently rendered at.
+ *
+ * <p>See <code>LOCATION_</code> flags.</p>
+ */
+ public int location;
+
+ @Override
+ public void copyFrom(ViewState viewState) {
+ super.copyFrom(viewState);
+ if (viewState instanceof StackViewState) {
+ StackViewState svs = (StackViewState) viewState;
+ height = svs.height;
+ dimmed = svs.dimmed;
+ dark = svs.dark;
+ hideSensitive = svs.hideSensitive;
+ belowSpeedBump = svs.belowSpeedBump;
+ clipTopAmount = svs.clipTopAmount;
+ topOverLap = svs.topOverLap;
+ notGoneIndex = svs.notGoneIndex;
+ location = svs.location;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
new file mode 100644
index 0000000..3e538df
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.stack;
+
+import android.view.View;
+
+/**
+ * A state of a view. This can be used to apply a set of view properties to a view with
+ * {@link com.android.systemui.statusbar.stack.StackScrollState} or start animations with
+ * {@link com.android.systemui.statusbar.stack.StackStateAnimator}.
+*/
+public class ViewState {
+
+ public float alpha;
+ public float yTranslation;
+ public float zTranslation;
+ public boolean gone;
+ public float scale;
+
+ public void copyFrom(ViewState viewState) {
+ alpha = viewState.alpha;
+ yTranslation = viewState.yTranslation;
+ zTranslation = viewState.zTranslation;
+ gone = viewState.gone;
+ scale = viewState.scale;
+ }
+
+ public void initFrom(View view) {
+ alpha = view.getVisibility() == View.INVISIBLE ? 0.0f : view.getAlpha();
+ yTranslation = view.getTranslationY();
+ zTranslation = view.getTranslationZ();
+ gone = view.getVisibility() == View.GONE;
+ scale = view.getScaleX();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index 08732e5..c272e48 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -26,6 +26,7 @@ import android.view.WindowManager;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.statusbar.ActivatableNotificationView;
import com.android.systemui.statusbar.BaseStatusBar;
+import com.android.systemui.statusbar.NotificationData;
/*
* Status bar implementation for "large screen" products that mostly present no on-screen nav
@@ -47,7 +48,8 @@ public class TvStatusBar extends BaseStatusBar {
}
@Override
- public void addNotification(StatusBarNotification notification, RankingMap ranking) {
+ public void addNotification(StatusBarNotification notification, RankingMap ranking,
+ NotificationData.Entry entry) {
}
@Override
@@ -104,16 +106,6 @@ public class TvStatusBar extends BaseStatusBar {
}
@Override
- protected WindowManager.LayoutParams getSearchLayoutParams(
- LayoutParams layoutParams) {
- return null;
- }
-
- @Override
- protected void haltTicker() {
- }
-
- @Override
protected void setAreThereNotifications() {
}
@@ -122,15 +114,7 @@ public class TvStatusBar extends BaseStatusBar {
}
@Override
- protected void tick(StatusBarNotification n, boolean firstTime) {
- }
-
- @Override
- protected void updateExpandedViewPos(int expandedPosition) {
- }
-
- @Override
- protected boolean shouldDisableNavbarGestures() {
+ public boolean shouldDisableNavbarGestures() {
return true;
}
@@ -139,7 +123,7 @@ public class TvStatusBar extends BaseStatusBar {
}
@Override
- public void resetHeadsUpDecayTimer() {
+ public void scheduleHeadsUpDecay(long delay) {
}
@Override
@@ -182,4 +166,16 @@ public class TvStatusBar extends BaseStatusBar {
@Override
public void showScreenPinningRequest() {
}
+
+ @Override
+ public void appTransitionPending() {
+ }
+
+ @Override
+ public void appTransitionCancelled() {
+ }
+
+ @Override
+ public void appTransitionStarting(long startTime, long duration) {
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
index f804736..5771d22 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
@@ -327,7 +327,7 @@ public class StorageNotification extends SystemUI {
pi = PendingIntent.getBroadcastAsUser(mContext, 0, intent, 0,
UserHandle.CURRENT);
}
- mUsbStorageNotification.color = mContext.getResources().getColor(
+ mUsbStorageNotification.color = mContext.getColor(
com.android.internal.R.color.system_notification_accent_color);
mUsbStorageNotification.setLatestEventInfo(mContext, title, message, pi);
mUsbStorageNotification.visibility = Notification.VISIBILITY_PUBLIC;
@@ -422,7 +422,7 @@ public class StorageNotification extends SystemUI {
}
mMediaStorageNotification.icon = icon;
- mMediaStorageNotification.color = mContext.getResources().getColor(
+ mMediaStorageNotification.color = mContext.getColor(
com.android.internal.R.color.system_notification_accent_color);
mMediaStorageNotification.setLatestEventInfo(mContext, title, message, pi);
mMediaStorageNotification.visibility = Notification.VISIBILITY_PUBLIC;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPanel.java b/packages/SystemUI/src/com/android/systemui/volume/D.java
index 272c321..db7c853 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/D.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,8 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar;
+package com.android.systemui.volume;
-public interface StatusBarPanel {
- public boolean isInContentArea(int x, int y);
+import android.util.Log;
+
+class D {
+ public static boolean BUG = Log.isLoggable("volume", Log.DEBUG);
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/Events.java b/packages/SystemUI/src/com/android/systemui/volume/Events.java
new file mode 100644
index 0000000..12dca94
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/Events.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume;
+
+import android.media.AudioManager;
+import android.media.AudioSystem;
+import android.provider.Settings.Global;
+import android.util.Log;
+
+import com.android.systemui.volume.VolumeDialogController.State;
+
+import java.util.Arrays;
+
+/**
+ * Interesting events related to the volume.
+ */
+public class Events {
+ private static final String TAG = Util.logTag(Events.class);
+
+ public static final int EVENT_SHOW_DIALOG = 0; // (reason|int) (keyguard|bool)
+ public static final int EVENT_DISMISS_DIALOG = 1; // (reason|int)
+ public static final int EVENT_ACTIVE_STREAM_CHANGED = 2; // (stream|int)
+ public static final int EVENT_EXPAND = 3; // (expand|bool)
+ public static final int EVENT_KEY = 4;
+ public static final int EVENT_COLLECTION_STARTED = 5;
+ public static final int EVENT_COLLECTION_STOPPED = 6;
+ public static final int EVENT_ICON_CLICK = 7; // (stream|int) (icon_state|int)
+ public static final int EVENT_SETTINGS_CLICK = 8;
+ public static final int EVENT_TOUCH_LEVEL_CHANGED = 9; // (stream|int) (level|int)
+ public static final int EVENT_LEVEL_CHANGED = 10; // (stream|int) (level|int)
+ public static final int EVENT_INTERNAL_RINGER_MODE_CHANGED = 11; // (mode|int)
+ public static final int EVENT_EXTERNAL_RINGER_MODE_CHANGED = 12; // (mode|int)
+ public static final int EVENT_ZEN_MODE_CHANGED = 13; // (mode|int)
+ public static final int EVENT_SUPPRESSOR_CHANGED = 14; // (component|string) (name|string)
+ public static final int EVENT_MUTE_CHANGED = 15; // (stream|int) (muted|bool)
+
+ private static final String[] EVENT_TAGS = {
+ "show_dialog",
+ "dismiss_dialog",
+ "active_stream_changed",
+ "expand",
+ "key",
+ "collection_started",
+ "collection_stopped",
+ "icon_click",
+ "settings_click",
+ "touch_level_changed",
+ "level_changed",
+ "internal_ringer_mode_changed",
+ "external_ringer_mode_changed",
+ "zen_mode_changed",
+ "suppressor_changed",
+ "mute_changed",
+ };
+
+ public static final int DISMISS_REASON_UNKNOWN = 0;
+ public static final int DISMISS_REASON_TOUCH_OUTSIDE = 1;
+ public static final int DISMISS_REASON_VOLUME_CONTROLLER = 2;
+ public static final int DISMISS_REASON_TIMEOUT = 3;
+ public static final int DISMISS_REASON_SCREEN_OFF = 4;
+ public static final int DISMISS_REASON_SETTINGS_CLICKED = 5;
+ public static final int DISMISS_REASON_DONE_CLICKED = 6;
+ public static final String[] DISMISS_REASONS = {
+ "unknown",
+ "touch_outside",
+ "volume_controller",
+ "timeout",
+ "screen_off",
+ "settings_clicked",
+ "done_clicked",
+ };
+
+ public static final int SHOW_REASON_UNKNOWN = 0;
+ public static final int SHOW_REASON_VOLUME_CHANGED = 1;
+ public static final int SHOW_REASON_REMOTE_VOLUME_CHANGED = 2;
+ public static final String[] SHOW_REASONS = {
+ "unknown",
+ "volume_changed",
+ "remote_volume_changed"
+ };
+
+ public static final int ICON_STATE_UNKNOWN = 0;
+ public static final int ICON_STATE_UNMUTE = 1;
+ public static final int ICON_STATE_MUTE = 2;
+ public static final int ICON_STATE_VIBRATE = 3;
+
+ public static Callback sCallback;
+
+ public static void writeEvent(int tag, Object... list) {
+ final long time = System.currentTimeMillis();
+ final StringBuilder sb = new StringBuilder("writeEvent ").append(EVENT_TAGS[tag]);
+ if (list != null && list.length > 0) {
+ sb.append(" ");
+ switch (tag) {
+ case EVENT_SHOW_DIALOG:
+ sb.append(SHOW_REASONS[(Integer) list[0]]).append(" keyguard=").append(list[1]);
+ break;
+ case EVENT_EXPAND:
+ sb.append(list[0]);
+ break;
+ case EVENT_DISMISS_DIALOG:
+ sb.append(DISMISS_REASONS[(Integer) list[0]]);
+ break;
+ case EVENT_ACTIVE_STREAM_CHANGED:
+ sb.append(AudioSystem.streamToString((Integer) list[0]));
+ break;
+ case EVENT_ICON_CLICK:
+ sb.append(AudioSystem.streamToString((Integer) list[0])).append(' ')
+ .append(iconStateToString((Integer) list[1]));
+ break;
+ case EVENT_TOUCH_LEVEL_CHANGED:
+ case EVENT_LEVEL_CHANGED:
+ case EVENT_MUTE_CHANGED:
+ sb.append(AudioSystem.streamToString((Integer) list[0])).append(' ')
+ .append(list[1]);
+ break;
+ case EVENT_INTERNAL_RINGER_MODE_CHANGED:
+ case EVENT_EXTERNAL_RINGER_MODE_CHANGED:
+ sb.append(ringerModeToString((Integer) list[0]));
+ break;
+ case EVENT_ZEN_MODE_CHANGED:
+ sb.append(zenModeToString((Integer) list[0]));
+ break;
+ case EVENT_SUPPRESSOR_CHANGED:
+ sb.append(list[0]).append(' ').append(list[1]);
+ break;
+ default:
+ sb.append(Arrays.asList(list));
+ break;
+ }
+ }
+ Log.i(TAG, sb.toString());
+ if (sCallback != null) {
+ sCallback.writeEvent(time, tag, list);
+ }
+ }
+
+ public static void writeState(long time, State state) {
+ if (sCallback != null) {
+ sCallback.writeState(time, state);
+ }
+ }
+
+ private static String iconStateToString(int iconState) {
+ switch (iconState) {
+ case ICON_STATE_UNMUTE: return "unmute";
+ case ICON_STATE_MUTE: return "mute";
+ case ICON_STATE_VIBRATE: return "vibrate";
+ default: return "unknown_state_" + iconState;
+ }
+ }
+
+ private static String ringerModeToString(int ringerMode) {
+ switch (ringerMode) {
+ case AudioManager.RINGER_MODE_SILENT: return "silent";
+ case AudioManager.RINGER_MODE_VIBRATE: return "vibrate";
+ case AudioManager.RINGER_MODE_NORMAL: return "normal";
+ default: return "unknown";
+ }
+ }
+
+ private static String zenModeToString(int zenMode) {
+ switch (zenMode) {
+ case Global.ZEN_MODE_OFF: return "off";
+ case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: return "important_interruptions";
+ case Global.ZEN_MODE_ALARMS: return "alarms";
+ case Global.ZEN_MODE_NO_INTERRUPTIONS: return "no_interruptions";
+ default: return "unknown";
+ }
+ }
+
+ public interface Callback {
+ void writeEvent(long time, int tag, Object[] list);
+ void writeState(long time, State state);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java b/packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java
new file mode 100644
index 0000000..712ea27
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.media.IRemoteVolumeController;
+import android.media.MediaMetadata;
+import android.media.session.ISessionController;
+import android.media.session.MediaController;
+import android.media.session.MediaController.PlaybackInfo;
+import android.media.session.MediaSession.QueueItem;
+import android.media.session.MediaSession.Token;
+import android.media.session.MediaSessionManager;
+import android.media.session.MediaSessionManager.OnActiveSessionsChangedListener;
+import android.media.session.PlaybackState;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Convenience client for all media session updates. Provides a callback interface for events
+ * related to remote media sessions.
+ */
+public class MediaSessions {
+ private static final String TAG = Util.logTag(MediaSessions.class);
+
+ private static final boolean USE_SERVICE_LABEL = false;
+
+ private final Context mContext;
+ private final H mHandler;
+ private final MediaSessionManager mMgr;
+ private final Map<Token, MediaControllerRecord> mRecords = new HashMap<>();
+ private final Callbacks mCallbacks;
+
+ private boolean mInit;
+
+ public MediaSessions(Context context, Looper looper, Callbacks callbacks) {
+ mContext = context;
+ mHandler = new H(looper);
+ mMgr = (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
+ mCallbacks = callbacks;
+ }
+
+ public void dump(PrintWriter writer) {
+ writer.println(getClass().getSimpleName() + " state:");
+ writer.print(" mInit: "); writer.println(mInit);
+ writer.print(" mRecords.size: "); writer.println(mRecords.size());
+ int i = 0;
+ for (MediaControllerRecord r : mRecords.values()) {
+ dump(++i, writer, r.controller);
+ }
+ }
+
+ public void init() {
+ if (D.BUG) Log.d(TAG, "init");
+ // will throw if no permission
+ mMgr.addOnActiveSessionsChangedListener(mSessionsListener, null, mHandler);
+ mInit = true;
+ postUpdateSessions();
+ mMgr.setRemoteVolumeController(mRvc);
+ }
+
+ protected void postUpdateSessions() {
+ if (!mInit) return;
+ mHandler.sendEmptyMessage(H.UPDATE_SESSIONS);
+ }
+
+ public void destroy() {
+ if (D.BUG) Log.d(TAG, "destroy");
+ mInit = false;
+ mMgr.removeOnActiveSessionsChangedListener(mSessionsListener);
+ }
+
+ public void setVolume(Token token, int level) {
+ final MediaControllerRecord r = mRecords.get(token);
+ if (r == null) {
+ Log.w(TAG, "setVolume: No record found for token " + token);
+ return;
+ }
+ if (D.BUG) Log.d(TAG, "Setting level to " + level);
+ r.controller.setVolumeTo(level, 0);
+ }
+
+ private void onRemoteVolumeChangedH(ISessionController session, int flags) {
+ final MediaController controller = new MediaController(mContext, session);
+ if (D.BUG) Log.d(TAG, "remoteVolumeChangedH " + controller.getPackageName() + " "
+ + Util.audioManagerFlagsToString(flags));
+ final Token token = controller.getSessionToken();
+ mCallbacks.onRemoteVolumeChanged(token, flags);
+ }
+
+ private void onUpdateRemoteControllerH(ISessionController session) {
+ final MediaController controller = session != null ? new MediaController(mContext, session)
+ : null;
+ final String pkg = controller != null ? controller.getPackageName() : null;
+ if (D.BUG) Log.d(TAG, "updateRemoteControllerH " + pkg);
+ // this may be our only indication that a remote session is changed, refresh
+ postUpdateSessions();
+ }
+
+ protected void onActiveSessionsUpdatedH(List<MediaController> controllers) {
+ if (D.BUG) Log.d(TAG, "onActiveSessionsUpdatedH n=" + controllers.size());
+ final Set<Token> toRemove = new HashSet<Token>(mRecords.keySet());
+ for (MediaController controller : controllers) {
+ final Token token = controller.getSessionToken();
+ final PlaybackInfo pi = controller.getPlaybackInfo();
+ toRemove.remove(token);
+ if (!mRecords.containsKey(token)) {
+ final MediaControllerRecord r = new MediaControllerRecord(controller);
+ r.name = getControllerName(controller);
+ mRecords.put(token, r);
+ controller.registerCallback(r, mHandler);
+ }
+ final MediaControllerRecord r = mRecords.get(token);
+ final boolean remote = isRemote(pi);
+ if (remote) {
+ updateRemoteH(token, r.name, pi);
+ r.sentRemote = true;
+ }
+ }
+ for (Token t : toRemove) {
+ final MediaControllerRecord r = mRecords.get(t);
+ r.controller.unregisterCallback(r);
+ mRecords.remove(t);
+ if (D.BUG) Log.d(TAG, "Removing " + r.name + " sentRemote=" + r.sentRemote);
+ if (r.sentRemote) {
+ mCallbacks.onRemoteRemoved(t);
+ r.sentRemote = false;
+ }
+ }
+ }
+
+ private static boolean isRemote(PlaybackInfo pi) {
+ return pi != null && pi.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE;
+ }
+
+ protected String getControllerName(MediaController controller) {
+ final PackageManager pm = mContext.getPackageManager();
+ final String pkg = controller.getPackageName();
+ try {
+ if (USE_SERVICE_LABEL) {
+ final List<ResolveInfo> ris = pm.queryIntentServices(
+ new Intent("android.media.MediaRouteProviderService").setPackage(pkg), 0);
+ if (ris != null) {
+ for (ResolveInfo ri : ris) {
+ if (ri.serviceInfo == null) continue;
+ if (pkg.equals(ri.serviceInfo.packageName)) {
+ final String serviceLabel =
+ Objects.toString(ri.serviceInfo.loadLabel(pm), "").trim();
+ if (serviceLabel.length() > 0) {
+ return serviceLabel;
+ }
+ }
+ }
+ }
+ }
+ final ApplicationInfo ai = pm.getApplicationInfo(pkg, 0);
+ final String appLabel = Objects.toString(ai.loadLabel(pm), "").trim();
+ if (appLabel.length() > 0) {
+ return appLabel;
+ }
+ } catch (NameNotFoundException e) { }
+ return pkg;
+ }
+
+ private void updateRemoteH(Token token, String name, PlaybackInfo pi) {
+ if (mCallbacks != null) {
+ mCallbacks.onRemoteUpdate(token, name, pi);
+ }
+ }
+
+ private static void dump(int n, PrintWriter writer, MediaController c) {
+ writer.println(" Controller " + n + ": " + c.getPackageName());
+ final Bundle extras = c.getExtras();
+ final long flags = c.getFlags();
+ final MediaMetadata mm = c.getMetadata();
+ final PlaybackInfo pi = c.getPlaybackInfo();
+ final PlaybackState playbackState = c.getPlaybackState();
+ final List<QueueItem> queue = c.getQueue();
+ final CharSequence queueTitle = c.getQueueTitle();
+ final int ratingType = c.getRatingType();
+ final PendingIntent sessionActivity = c.getSessionActivity();
+
+ writer.println(" PlaybackState: " + Util.playbackStateToString(playbackState));
+ writer.println(" PlaybackInfo: " + Util.playbackInfoToString(pi));
+ if (mm != null) {
+ writer.println(" MediaMetadata.desc=" + mm.getDescription());
+ }
+ writer.println(" RatingType: " + ratingType);
+ writer.println(" Flags: " + flags);
+ if (extras != null) {
+ writer.println(" Extras:");
+ for (String key : extras.keySet()) {
+ writer.println(" " + key + "=" + extras.get(key));
+ }
+ }
+ if (queueTitle != null) {
+ writer.println(" QueueTitle: " + queueTitle);
+ }
+ if (queue != null && !queue.isEmpty()) {
+ writer.println(" Queue:");
+ for (QueueItem qi : queue) {
+ writer.println(" " + qi);
+ }
+ }
+ if (pi != null) {
+ writer.println(" sessionActivity: " + sessionActivity);
+ }
+ }
+
+ public static void dumpMediaSessions(Context context) {
+ final MediaSessionManager mgr = (MediaSessionManager) context
+ .getSystemService(Context.MEDIA_SESSION_SERVICE);
+ try {
+ final List<MediaController> controllers = mgr.getActiveSessions(null);
+ final int N = controllers.size();
+ if (D.BUG) Log.d(TAG, N + " controllers");
+ for (int i = 0; i < N; i++) {
+ final StringWriter sw = new StringWriter();
+ final PrintWriter pw = new PrintWriter(sw, true);
+ dump(i + 1, pw, controllers.get(i));
+ if (D.BUG) Log.d(TAG, sw.toString());
+ }
+ } catch (SecurityException e) {
+ Log.w(TAG, "Not allowed to get sessions", e);
+ }
+ }
+
+ private final class MediaControllerRecord extends MediaController.Callback {
+ private final MediaController controller;
+
+ private boolean sentRemote;
+ private String name;
+
+ private MediaControllerRecord(MediaController controller) {
+ this.controller = controller;
+ }
+
+ private String cb(String method) {
+ return method + " " + controller.getPackageName() + " ";
+ }
+
+ @Override
+ public void onAudioInfoChanged(PlaybackInfo info) {
+ if (D.BUG) Log.d(TAG, cb("onAudioInfoChanged") + Util.playbackInfoToString(info)
+ + " sentRemote=" + sentRemote);
+ final boolean remote = isRemote(info);
+ if (!remote && sentRemote) {
+ mCallbacks.onRemoteRemoved(controller.getSessionToken());
+ sentRemote = false;
+ } else if (remote) {
+ updateRemoteH(controller.getSessionToken(), name, info);
+ sentRemote = true;
+ }
+ }
+
+ @Override
+ public void onExtrasChanged(Bundle extras) {
+ if (D.BUG) Log.d(TAG, cb("onExtrasChanged") + extras);
+ }
+
+ @Override
+ public void onMetadataChanged(MediaMetadata metadata) {
+ if (D.BUG) Log.d(TAG, cb("onMetadataChanged") + Util.mediaMetadataToString(metadata));
+ }
+
+ @Override
+ public void onPlaybackStateChanged(PlaybackState state) {
+ if (D.BUG) Log.d(TAG, cb("onPlaybackStateChanged") + Util.playbackStateToString(state));
+ }
+
+ @Override
+ public void onQueueChanged(List<QueueItem> queue) {
+ if (D.BUG) Log.d(TAG, cb("onQueueChanged") + queue);
+ }
+
+ @Override
+ public void onQueueTitleChanged(CharSequence title) {
+ if (D.BUG) Log.d(TAG, cb("onQueueTitleChanged") + title);
+ }
+
+ @Override
+ public void onSessionDestroyed() {
+ if (D.BUG) Log.d(TAG, cb("onSessionDestroyed"));
+ }
+
+ @Override
+ public void onSessionEvent(String event, Bundle extras) {
+ if (D.BUG) Log.d(TAG, cb("onSessionEvent") + "event=" + event + " extras=" + extras);
+ }
+ }
+
+ private final OnActiveSessionsChangedListener mSessionsListener =
+ new OnActiveSessionsChangedListener() {
+ @Override
+ public void onActiveSessionsChanged(List<MediaController> controllers) {
+ onActiveSessionsUpdatedH(controllers);
+ }
+ };
+
+ private final IRemoteVolumeController mRvc = new IRemoteVolumeController.Stub() {
+ @Override
+ public void remoteVolumeChanged(ISessionController session, int flags)
+ throws RemoteException {
+ mHandler.obtainMessage(H.REMOTE_VOLUME_CHANGED, flags, 0, session).sendToTarget();
+ }
+
+ @Override
+ public void updateRemoteController(final ISessionController session)
+ throws RemoteException {
+ mHandler.obtainMessage(H.UPDATE_REMOTE_CONTROLLER, session).sendToTarget();
+ }
+ };
+
+ private final class H extends Handler {
+ private static final int UPDATE_SESSIONS = 1;
+ private static final int REMOTE_VOLUME_CHANGED = 2;
+ private static final int UPDATE_REMOTE_CONTROLLER = 3;
+
+ private H(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case UPDATE_SESSIONS:
+ onActiveSessionsUpdatedH(mMgr.getActiveSessions(null));
+ break;
+ case REMOTE_VOLUME_CHANGED:
+ onRemoteVolumeChangedH((ISessionController) msg.obj, msg.arg1);
+ break;
+ case UPDATE_REMOTE_CONTROLLER:
+ onUpdateRemoteControllerH((ISessionController) msg.obj);
+ break;
+ }
+ }
+ }
+
+ public interface Callbacks {
+ void onRemoteUpdate(Token token, String name, PlaybackInfo pi);
+ void onRemoteRemoved(Token t);
+ void onRemoteVolumeChanged(Token token, int flags);
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/Prefs.java b/packages/SystemUI/src/com/android/systemui/volume/Prefs.java
new file mode 100644
index 0000000..58bc9f4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/Prefs.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.preference.PreferenceManager;
+
+/**
+ * Configuration for the volume dialog + related policy.
+ */
+public class Prefs {
+
+ public static final String PREF_ENABLE_PROTOTYPE = "pref_enable_prototype"; // not persistent
+ public static final String PREF_SHOW_ALARMS = "pref_show_alarms";
+ public static final String PREF_SHOW_SYSTEM = "pref_show_system";
+ public static final String PREF_SHOW_HEADERS = "pref_show_headers";
+ public static final String PREF_SHOW_FAKE_REMOTE_1 = "pref_show_fake_remote_1";
+ public static final String PREF_SHOW_FAKE_REMOTE_2 = "pref_show_fake_remote_2";
+ public static final String PREF_SHOW_FOOTER = "pref_show_footer";
+ public static final String PREF_ZEN_FOOTER = "pref_zen_footer";
+ public static final String PREF_ENABLE_AUTOMUTE = "pref_enable_automute";
+ public static final String PREF_ENABLE_SILENT_MODE = "pref_enable_silent_mode";
+ public static final String PREF_DEBUG_LOGGING = "pref_debug_logging";
+ public static final String PREF_SEND_LOGS = "pref_send_logs";
+ public static final String PREF_ADJUST_SYSTEM = "pref_adjust_system";
+ public static final String PREF_ADJUST_VOICE_CALLS = "pref_adjust_voice_calls";
+ public static final String PREF_ADJUST_BLUETOOTH_SCO = "pref_adjust_bluetooth_sco";
+ public static final String PREF_ADJUST_MEDIA = "pref_adjust_media";
+ public static final String PREF_ADJUST_ALARMS = "pref_adjust_alarms";
+ public static final String PREF_ADJUST_NOTIFICATION = "pref_adjust_notification";
+
+ public static final boolean DEFAULT_SHOW_HEADERS = true;
+ public static final boolean DEFAULT_SHOW_FOOTER = true;
+ public static final boolean DEFAULT_ENABLE_AUTOMUTE = true;
+ public static final boolean DEFAULT_ENABLE_SILENT_MODE = true;
+ public static final boolean DEFAULT_ZEN_FOOTER = true;
+
+ public static void unregisterCallbacks(Context c, OnSharedPreferenceChangeListener listener) {
+ prefs(c).unregisterOnSharedPreferenceChangeListener(listener);
+ }
+
+ public static void registerCallbacks(Context c, OnSharedPreferenceChangeListener listener) {
+ prefs(c).registerOnSharedPreferenceChangeListener(listener);
+ }
+
+ private static SharedPreferences prefs(Context context) {
+ return PreferenceManager.getDefaultSharedPreferences(context);
+ }
+
+ public static boolean get(Context context, String key, boolean def) {
+ return prefs(context).getBoolean(key, def);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/SafetyWarningDialog.java b/packages/SystemUI/src/com/android/systemui/volume/SafetyWarningDialog.java
new file mode 100644
index 0000000..04640a2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/SafetyWarningDialog.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.WindowManager;
+
+import com.android.systemui.statusbar.phone.SystemUIDialog;
+
+abstract public class SafetyWarningDialog extends SystemUIDialog
+ implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener {
+
+ private static final String TAG = Util.logTag(SafetyWarningDialog.class);
+
+ private static final int KEY_CONFIRM_ALLOWED_AFTER = 1000; // milliseconds
+
+ private final Context mContext;
+ private final AudioManager mAudioManager;
+
+ private long mShowTime;
+ private boolean mNewVolumeUp;
+
+ public SafetyWarningDialog(Context context, AudioManager audioManager) {
+ super(context);
+ mContext = context;
+ mAudioManager = audioManager;
+
+ getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
+ setMessage(mContext.getString(com.android.internal.R.string.safe_media_volume_warning));
+ setButton(DialogInterface.BUTTON_POSITIVE,
+ mContext.getString(com.android.internal.R.string.yes), this);
+ setButton(DialogInterface.BUTTON_NEGATIVE,
+ mContext.getString(com.android.internal.R.string.no), (OnClickListener) null);
+ setOnDismissListener(this);
+
+ final IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+ context.registerReceiver(mReceiver, filter);
+ }
+
+ abstract protected void cleanUp();
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_VOLUME_UP && event.getRepeatCount() == 0) {
+ mNewVolumeUp = true;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_VOLUME_UP && mNewVolumeUp
+ && (System.currentTimeMillis() - mShowTime) > KEY_CONFIRM_ALLOWED_AFTER) {
+ if (D.BUG) Log.d(TAG, "Confirmed warning via VOLUME_UP");
+ mAudioManager.disableSafeMediaVolume();
+ dismiss();
+ }
+ return super.onKeyUp(keyCode, event);
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ mAudioManager.disableSafeMediaVolume();
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ mShowTime = System.currentTimeMillis();
+ }
+
+ @Override
+ public void onDismiss(DialogInterface unused) {
+ mContext.unregisterReceiver(mReceiver);
+ cleanUp();
+ }
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
+ if (D.BUG) Log.d(TAG, "Received ACTION_CLOSE_SYSTEM_DIALOGS");
+ cancel();
+ cleanUp();
+ }
+ }
+ };
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/volume/SegmentedButtons.java b/packages/SystemUI/src/com/android/systemui/volume/SegmentedButtons.java
index 2f02f7c..4f20ac7 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/SegmentedButtons.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/SegmentedButtons.java
@@ -60,17 +60,14 @@ public class SegmentedButtons extends LinearLayout {
final Object tag = c.getTag();
final boolean selected = Objects.equals(mSelectedValue, tag);
c.setSelected(selected);
- c.getCompoundDrawables()[1].setTint(mContext.getResources().getColor(selected
- ? R.color.segmented_button_selected : R.color.segmented_button_unselected));
}
fireOnSelected();
}
- public void addButton(int labelResId, int iconResId, Object value) {
+ public void addButton(int labelResId, 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/SpTexts.java b/packages/SystemUI/src/com/android/systemui/volume/SpTexts.java
new file mode 100644
index 0000000..d8e53db
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/SpTexts.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.ArrayMap;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.View.OnAttachStateChangeListener;
+import android.widget.TextView;
+
+/**
+ * Capture initial sp values for registered textviews, and update properly when configuration
+ * changes.
+ */
+public class SpTexts {
+
+ private final Context mContext;
+ private final ArrayMap<TextView, Integer> mTexts = new ArrayMap<>();
+
+ public SpTexts(Context context) {
+ mContext = context;
+ }
+
+ public int add(final TextView text) {
+ if (text == null) return 0;
+ final Resources res = mContext.getResources();
+ final float fontScale = res.getConfiguration().fontScale;
+ final float density = res.getDisplayMetrics().density;
+ final float px = text.getTextSize();
+ final int sp = (int)(px / fontScale / density);
+ mTexts.put(text, sp);
+ text.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ }
+
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ setTextSizeH(text, sp);
+ }
+ });
+ return sp;
+ }
+
+ public void update() {
+ if (mTexts.isEmpty()) return;
+ mTexts.keyAt(0).post(mUpdateAll);
+ }
+
+ private void setTextSizeH(TextView text, int sp) {
+ text.setTextSize(TypedValue.COMPLEX_UNIT_SP, sp);
+ }
+
+ private final Runnable mUpdateAll = new Runnable() {
+ @Override
+ public void run() {
+ for (int i = 0; i < mTexts.size(); i++) {
+ setTextSizeH(mTexts.keyAt(i), mTexts.valueAt(i));
+ }
+ }
+ };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/Util.java b/packages/SystemUI/src/com/android/systemui/volume/Util.java
new file mode 100644
index 0000000..78baf67
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/Util.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume;
+
+import android.media.AudioManager;
+import android.media.MediaMetadata;
+import android.media.VolumeProvider;
+import android.media.session.MediaController.PlaybackInfo;
+import android.media.session.PlaybackState;
+import android.service.notification.ZenModeConfig.DowntimeInfo;
+import android.view.View;
+import android.widget.TextView;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * Static helpers for the volume dialog.
+ */
+class Util {
+
+ // Note: currently not shown (only used in the text footer)
+ private static final SimpleDateFormat HMMAA = new SimpleDateFormat("h:mm aa", Locale.US);
+
+ private static int[] AUDIO_MANAGER_FLAGS = new int[] {
+ AudioManager.FLAG_SHOW_UI,
+ AudioManager.FLAG_VIBRATE,
+ AudioManager.FLAG_PLAY_SOUND,
+ AudioManager.FLAG_ALLOW_RINGER_MODES,
+ AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE,
+ AudioManager.FLAG_SHOW_VIBRATE_HINT,
+ AudioManager.FLAG_SHOW_SILENT_HINT,
+ AudioManager.FLAG_FROM_KEY,
+ AudioManager.FLAG_SHOW_UI_WARNINGS,
+ };
+
+ private static String[] AUDIO_MANAGER_FLAG_NAMES = new String[] {
+ "SHOW_UI",
+ "VIBRATE",
+ "PLAY_SOUND",
+ "ALLOW_RINGER_MODES",
+ "REMOVE_SOUND_AND_VIBRATE",
+ "SHOW_VIBRATE_HINT",
+ "SHOW_SILENT_HINT",
+ "FROM_KEY",
+ "SHOW_UI_WARNINGS",
+ };
+
+ public static String logTag(Class<?> c) {
+ final String tag = "vol." + c.getSimpleName();
+ return tag.length() < 23 ? tag : tag.substring(0, 23);
+ }
+
+ public static String ringerModeToString(int ringerMode) {
+ switch (ringerMode) {
+ case AudioManager.RINGER_MODE_SILENT: return "RINGER_MODE_SILENT";
+ case AudioManager.RINGER_MODE_VIBRATE: return "RINGER_MODE_VIBRATE";
+ case AudioManager.RINGER_MODE_NORMAL: return "RINGER_MODE_NORMAL";
+ default: return "RINGER_MODE_UNKNOWN_" + ringerMode;
+ }
+ }
+
+ public static String mediaMetadataToString(MediaMetadata metadata) {
+ return metadata.getDescription().toString();
+ }
+
+ public static String playbackInfoToString(PlaybackInfo info) {
+ if (info == null) return null;
+ final String type = playbackInfoTypeToString(info.getPlaybackType());
+ final String vc = volumeProviderControlToString(info.getVolumeControl());
+ return String.format("PlaybackInfo[vol=%s,max=%s,type=%s,vc=%s],atts=%s",
+ info.getCurrentVolume(), info.getMaxVolume(), type, vc, info.getAudioAttributes());
+ }
+
+ public static String playbackInfoTypeToString(int type) {
+ switch (type) {
+ case PlaybackInfo.PLAYBACK_TYPE_LOCAL: return "LOCAL";
+ case PlaybackInfo.PLAYBACK_TYPE_REMOTE: return "REMOTE";
+ default: return "UNKNOWN_" + type;
+ }
+ }
+
+ public static String playbackStateStateToString(int state) {
+ switch (state) {
+ case PlaybackState.STATE_NONE: return "STATE_NONE";
+ case PlaybackState.STATE_STOPPED: return "STATE_STOPPED";
+ case PlaybackState.STATE_PAUSED: return "STATE_PAUSED";
+ case PlaybackState.STATE_PLAYING: return "STATE_PLAYING";
+ default: return "UNKNOWN_" + state;
+ }
+ }
+
+ public static String volumeProviderControlToString(int control) {
+ switch (control) {
+ case VolumeProvider.VOLUME_CONTROL_ABSOLUTE: return "VOLUME_CONTROL_ABSOLUTE";
+ case VolumeProvider.VOLUME_CONTROL_FIXED: return "VOLUME_CONTROL_FIXED";
+ case VolumeProvider.VOLUME_CONTROL_RELATIVE: return "VOLUME_CONTROL_RELATIVE";
+ default: return "VOLUME_CONTROL_UNKNOWN_" + control;
+ }
+ }
+
+ public static String playbackStateToString(PlaybackState playbackState) {
+ if (playbackState == null) return null;
+ return playbackStateStateToString(playbackState.getState()) + " " + playbackState;
+ }
+
+ public static String audioManagerFlagsToString(int value) {
+ return bitFieldToString(value, AUDIO_MANAGER_FLAGS, AUDIO_MANAGER_FLAG_NAMES);
+ }
+
+ private static String bitFieldToString(int value, int[] values, String[] names) {
+ if (value == 0) return "";
+ final StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < values.length; i++) {
+ if ((value & values[i]) != 0) {
+ if (sb.length() > 0) sb.append(',');
+ sb.append(names[i]);
+ }
+ value &= ~values[i];
+ }
+ if (value != 0) {
+ if (sb.length() > 0) sb.append(',');
+ sb.append("UNKNOWN_").append(value);
+ }
+ return sb.toString();
+ }
+
+ public static String getShortTime(long millis) {
+ return HMMAA.format(new Date(millis));
+ }
+
+ public static String getShortTime(DowntimeInfo info) {
+ return ((info.endHour + 1) % 12) + ":" + (info.endMinute < 10 ? " " : "") + info.endMinute;
+ }
+
+ public static void setText(TextView tv, CharSequence text) {
+ if (Objects.equals(tv.getText(), text)) return;
+ tv.setText(text);
+ }
+
+ public static final void setVisOrGone(View v, boolean vis) {
+ if (v == null || (v.getVisibility() == View.VISIBLE) == vis) return;
+ v.setVisibility(vis ? View.VISIBLE : View.GONE);
+ }
+
+ public static final void setVisOrInvis(View v, boolean vis) {
+ if (v == null || (v.getVisibility() == View.VISIBLE) == vis) return;
+ v.setVisibility(vis ? View.VISIBLE : View.INVISIBLE);
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java
index e3f8f3d..1f0ee57 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java
@@ -16,10 +16,18 @@
package com.android.systemui.volume;
+import android.content.res.Configuration;
+
import com.android.systemui.DemoMode;
import com.android.systemui.statusbar.policy.ZenModeController;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
public interface VolumeComponent extends DemoMode {
ZenModeController getZenController();
void dismissNow();
+ void onConfigurationChanged(Configuration newConfig);
+ void dump(FileDescriptor fd, PrintWriter pw, String[] args);
+ void register();
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
new file mode 100644
index 0000000..d8b3965
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
@@ -0,0 +1,1069 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume;
+
+import android.animation.LayoutTransition;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
+import android.app.Dialog;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
+import android.media.AudioSystem;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.provider.Settings.Global;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.DowntimeInfo;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.SparseBooleanArray;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLayoutChangeListener;
+import android.view.View.OnTouchListener;
+import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.volume.VolumeDialogController.State;
+import com.android.systemui.volume.VolumeDialogController.StreamState;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Visual presentation of the volume dialog.
+ *
+ * A client of VolumeDialogController and its state model.
+ *
+ * Methods ending in "H" must be called on the (ui) handler.
+ */
+public class VolumeDialog {
+ private static final String TAG = Util.logTag(VolumeDialog.class);
+
+ private static final long USER_ATTEMPT_GRACE_PERIOD = 1000;
+ private static final int WAIT_FOR_RIPPLE = 200;
+ private static final int UPDATE_ANIMATION_DURATION = 80;
+
+ private final Context mContext;
+ private final H mHandler = new H();
+ private final VolumeDialogController mController;
+
+ private final CustomDialog mDialog;
+ private final ViewGroup mDialogView;
+ private final ViewGroup mDialogContentView;
+ private final ImageButton mExpandButton;
+ private final TextView mFootlineText;
+ private final Button mFootlineAction;
+ private final View mSettingsButton;
+ private final View mFooter;
+ private final List<VolumeRow> mRows = new ArrayList<VolumeRow>();
+ private final SpTexts mSpTexts;
+ private final SparseBooleanArray mDynamic = new SparseBooleanArray();
+ private final KeyguardManager mKeyguard;
+ private final int mExpandButtonAnimationDuration;
+ private final View mTextFooter;
+ private final ZenFooter mZenFooter;
+ private final LayoutTransition mLayoutTransition;
+ private final Object mSafetyWarningLock = new Object();
+
+ private boolean mShowing;
+ private boolean mExpanded;
+ private int mActiveStream;
+ private boolean mShowHeaders = Prefs.DEFAULT_SHOW_HEADERS;
+ private boolean mShowFooter = Prefs.DEFAULT_SHOW_FOOTER;
+ private boolean mShowZenFooter = Prefs.DEFAULT_ZEN_FOOTER;
+ private boolean mAutomute = Prefs.DEFAULT_ENABLE_AUTOMUTE;
+ private boolean mSilentMode = Prefs.DEFAULT_ENABLE_SILENT_MODE;
+ private State mState;
+ private int mExpandButtonRes;
+ private boolean mExpanding;
+ private SafetyWarningDialog mSafetyWarning;
+
+ public VolumeDialog(Context context, VolumeDialogController controller,
+ ZenModeController zenModeController) {
+ mContext = context;
+ mController = controller;
+ mSpTexts = new SpTexts(mContext);
+ mKeyguard = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
+
+ mDialog = new CustomDialog(mContext);
+
+ final Window window = mDialog.getWindow();
+ window.requestFeature(Window.FEATURE_NO_TITLE);
+ window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+ window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+ window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+ | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+ mDialog.setCanceledOnTouchOutside(true);
+ final Resources res = mContext.getResources();
+ final WindowManager.LayoutParams lp = window.getAttributes();
+ lp.type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
+ lp.format = PixelFormat.TRANSLUCENT;
+ lp.setTitle(VolumeDialog.class.getSimpleName());
+ lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
+ lp.windowAnimations = R.style.VolumeDialogAnimations;
+ lp.y = res.getDimensionPixelSize(R.dimen.volume_offset_top);
+ lp.gravity = Gravity.TOP;
+ window.setAttributes(lp);
+
+ mDialog.setContentView(R.layout.volume_dialog);
+ mDialogView = (ViewGroup) mDialog.findViewById(R.id.volume_dialog);
+ mDialogContentView = (ViewGroup) mDialog.findViewById(R.id.volume_dialog_content);
+ mExpandButton = (ImageButton) mDialogView.findViewById(R.id.volume_expand_button);
+ mExpandButton.setOnClickListener(mClickExpand);
+ updateWindowWidthH();
+ updateExpandButtonH();
+ mLayoutTransition = new LayoutTransition();
+ mLayoutTransition.setDuration(new ValueAnimator().getDuration() / 2);
+ mLayoutTransition.disableTransitionType(LayoutTransition.DISAPPEARING);
+ mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
+ mDialogContentView.setLayoutTransition(mLayoutTransition);
+
+ addRow(AudioManager.STREAM_RING,
+ R.drawable.ic_volume_ringer, R.drawable.ic_volume_ringer_mute, true);
+ addRow(AudioManager.STREAM_MUSIC,
+ R.drawable.ic_volume_media, R.drawable.ic_volume_media_mute, true);
+ addRow(AudioManager.STREAM_ALARM,
+ R.drawable.ic_volume_alarm, R.drawable.ic_volume_alarm_mute, false);
+ addRow(AudioManager.STREAM_VOICE_CALL,
+ R.drawable.ic_volume_voice, R.drawable.ic_volume_voice, false);
+ addRow(AudioManager.STREAM_BLUETOOTH_SCO,
+ R.drawable.ic_volume_bt_sco, R.drawable.ic_volume_bt_sco, false);
+ addRow(AudioManager.STREAM_SYSTEM,
+ R.drawable.ic_volume_system, R.drawable.ic_volume_system_mute, false);
+
+ mTextFooter = mDialog.findViewById(R.id.volume_text_footer);
+ mFootlineText = (TextView) mDialog.findViewById(R.id.volume_footline_text);
+ mSpTexts.add(mFootlineText);
+ mFootlineAction = (Button) mDialog.findViewById(R.id.volume_footline_action_button);
+ mSpTexts.add(mFootlineAction);
+ mFooter = mDialog.findViewById(R.id.volume_footer);
+ mSettingsButton = mDialog.findViewById(R.id.volume_settings_button);
+ mSettingsButton.setOnClickListener(mClickSettings);
+ mExpandButtonAnimationDuration = res.getInteger(R.integer.volume_expand_animation_duration);
+ mZenFooter = (ZenFooter) mDialog.findViewById(R.id.volume_zen_footer);
+ mZenFooter.init(zenModeController, mZenFooterCallback);
+
+ controller.addCallback(mControllerCallbackH, mHandler);
+ controller.getState();
+ }
+
+ private void updateWindowWidthH() {
+ final ViewGroup.LayoutParams lp = mDialogView.getLayoutParams();
+ final DisplayMetrics dm = mContext.getResources().getDisplayMetrics();
+ if (D.BUG) Log.d(TAG, "updateWindowWidth dm.w=" + dm.widthPixels);
+ int w = dm.widthPixels;
+ final int max = mContext.getResources()
+ .getDimensionPixelSize(R.dimen.standard_notification_panel_width);
+ if (w > max) {
+ w = max;
+ }
+ w -= mContext.getResources().getDimensionPixelSize(R.dimen.notification_side_padding) * 2;
+ lp.width = w;
+ mDialogView.setLayoutParams(lp);
+ }
+
+ public void setStreamImportant(int stream, boolean important) {
+ mHandler.obtainMessage(H.SET_STREAM_IMPORTANT, stream, important ? 1 : 0).sendToTarget();
+ }
+
+ public void setShowHeaders(boolean showHeaders) {
+ if (showHeaders == mShowHeaders) return;
+ mShowHeaders = showHeaders;
+ mHandler.sendEmptyMessage(H.RECHECK_ALL);
+ }
+
+ public void setShowFooter(boolean show) {
+ if (mShowFooter == show) return;
+ mShowFooter = show;
+ mHandler.sendEmptyMessage(H.RECHECK_ALL);
+ }
+
+ public void setZenFooter(boolean zen) {
+ if (mShowZenFooter == zen) return;
+ mShowZenFooter = zen;
+ mHandler.sendEmptyMessage(H.RECHECK_ALL);
+ }
+
+ public void setAutomute(boolean automute) {
+ if (mAutomute == automute) return;
+ mAutomute = automute;
+ mHandler.sendEmptyMessage(H.RECHECK_ALL);
+ }
+
+ public void setSilentMode(boolean silentMode) {
+ if (mSilentMode == silentMode) return;
+ mSilentMode = silentMode;
+ mHandler.sendEmptyMessage(H.RECHECK_ALL);
+ }
+
+ private void addRow(int stream, int iconRes, int iconMuteRes, boolean important) {
+ final VolumeRow row = initRow(stream, iconRes, iconMuteRes, important);
+ if (!mRows.isEmpty()) {
+ final View v = new View(mContext);
+ final int h = mContext.getResources()
+ .getDimensionPixelSize(R.dimen.volume_slider_interspacing);
+ final LinearLayout.LayoutParams lp =
+ new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, h);
+ mDialogContentView.addView(v, mDialogContentView.getChildCount() - 1, lp);
+ row.space = v;
+ }
+ row.settingsButton.addOnLayoutChangeListener(new OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ if (D.BUG) Log.d(TAG, "onLayoutChange"
+ + " old=" + new Rect(oldLeft, oldTop, oldRight, oldBottom).toShortString()
+ + " new=" + new Rect(left,top,right,bottom).toShortString());
+ if (oldLeft != left || oldTop != top) {
+ for (int i = 0; i < mDialogContentView.getChildCount(); i++) {
+ final View c = mDialogContentView.getChildAt(i);
+ if (!c.isShown()) continue;
+ if (c == row.view) {
+ repositionExpandAnim(row);
+ }
+ return;
+ }
+ }
+ }
+ });
+ // add new row just before the footer
+ mDialogContentView.addView(row.view, mDialogContentView.getChildCount() - 1);
+ mRows.add(row);
+ }
+
+ private boolean isAttached() {
+ return mDialogContentView != null && mDialogContentView.isAttachedToWindow();
+ }
+
+ private VolumeRow getActiveRow() {
+ for (VolumeRow row : mRows) {
+ if (row.stream == mActiveStream) {
+ return row;
+ }
+ }
+ return mRows.get(0);
+ }
+
+ private VolumeRow findRow(int stream) {
+ for (VolumeRow row : mRows) {
+ if (row.stream == stream) return row;
+ }
+ return null;
+ }
+
+ private void repositionExpandAnim(VolumeRow row) {
+ final int[] loc = new int[2];
+ row.settingsButton.getLocationInWindow(loc);
+ final MarginLayoutParams mlp = (MarginLayoutParams) mDialogView.getLayoutParams();
+ final int x = loc[0] - mlp.leftMargin;
+ final int y = loc[1] - mlp.topMargin;
+ if (D.BUG) Log.d(TAG, "repositionExpandAnim x=" + x + " y=" + y);
+ mExpandButton.setTranslationX(x);
+ mExpandButton.setTranslationY(y);
+ }
+
+ public void dump(PrintWriter writer) {
+ writer.println(VolumeDialog.class.getSimpleName() + " state:");
+ writer.print(" mShowing: "); writer.println(mShowing);
+ writer.print(" mExpanded: "); writer.println(mExpanded);
+ writer.print(" mExpanding: "); writer.println(mExpanding);
+ writer.print(" mActiveStream: "); writer.println(mActiveStream);
+ writer.print(" mDynamic: "); writer.println(mDynamic);
+ writer.print(" mShowHeaders: "); writer.println(mShowHeaders);
+ writer.print(" mShowFooter: "); writer.println(mShowFooter);
+ writer.print(" mAutomute: "); writer.println(mAutomute);
+ writer.print(" mSilentMode: "); writer.println(mSilentMode);
+ }
+
+ private static int getImpliedLevel(SeekBar seekBar, int progress) {
+ final int m = seekBar.getMax();
+ final int n = m / 100 - 1;
+ final int level = progress == 0 ? 0
+ : progress == m ? (m / 100) : (1 + (int)((progress / (float) m) * n));
+ return level;
+ }
+
+ @SuppressLint("InflateParams")
+ private VolumeRow initRow(final int stream, int iconRes, int iconMuteRes, boolean important) {
+ final VolumeRow row = new VolumeRow();
+ row.stream = stream;
+ row.iconRes = iconRes;
+ row.iconMuteRes = iconMuteRes;
+ row.important = important;
+ row.view = mDialog.getLayoutInflater().inflate(R.layout.volume_dialog_row, null);
+ row.view.setTag(row);
+ row.header = (TextView) row.view.findViewById(R.id.volume_row_header);
+ mSpTexts.add(row.header);
+ row.slider = (SeekBar) row.view.findViewById(R.id.volume_row_slider);
+ row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row));
+
+ // forward events above the slider into the slider
+ row.view.setOnTouchListener(new OnTouchListener() {
+ private final Rect mSliderHitRect = new Rect();
+ private boolean mDragging;
+
+ @SuppressLint("ClickableViewAccessibility")
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ row.slider.getHitRect(mSliderHitRect);
+ if (!mDragging && event.getActionMasked() == MotionEvent.ACTION_DOWN
+ && event.getY() < mSliderHitRect.top) {
+ mDragging = true;
+ }
+ if (mDragging) {
+ event.offsetLocation(-mSliderHitRect.left, -mSliderHitRect.top);
+ row.slider.dispatchTouchEvent(event);
+ if (event.getActionMasked() == MotionEvent.ACTION_UP
+ || event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
+ mDragging = false;
+ }
+ return true;
+ }
+ return false;
+ }
+ });
+ row.icon = (ImageButton) row.view.findViewById(R.id.volume_row_icon);
+ row.icon.setImageResource(iconRes);
+ row.icon.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Events.writeEvent(Events.EVENT_ICON_CLICK, row.stream, row.iconState);
+ mController.setActiveStream(row.stream);
+ if (row.stream == AudioManager.STREAM_RING) {
+ final boolean hasVibrator = mController.hasVibrator();
+ if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) {
+ if (hasVibrator) {
+ mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, false);
+ } else {
+ final boolean wasZero = row.ss.level == 0;
+ mController.setStreamVolume(stream, wasZero ? row.lastAudibleLevel : 0);
+ }
+ } else {
+ mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false);
+ if (row.ss.level == 0) {
+ mController.setStreamVolume(stream, 1);
+ }
+ }
+ } else {
+ if (mAutomute && !row.ss.muteSupported) {
+ final boolean vmute = row.ss.level == 0;
+ mController.setStreamVolume(stream, vmute ? row.lastAudibleLevel : 0);
+ } else {
+ final boolean mute = !row.ss.muted;
+ mController.setStreamMute(stream, mute);
+ if (mAutomute) {
+ if (!mute && row.ss.level == 0) {
+ mController.setStreamVolume(stream, 1);
+ }
+ }
+ }
+ }
+ row.userAttempt = 0; // reset the grace period, slider should update immediately
+ }
+ });
+ row.settingsButton = (ImageButton) row.view.findViewById(R.id.volume_settings_button);
+ row.settingsButton.setOnClickListener(mClickSettings);
+ return row;
+ }
+
+ public void destroy() {
+ mController.removeCallback(mControllerCallbackH);
+ }
+
+ public void show(int reason) {
+ mHandler.obtainMessage(H.SHOW, reason, 0).sendToTarget();
+ }
+
+ public void dismiss(int reason) {
+ mHandler.obtainMessage(H.DISMISS, reason, 0).sendToTarget();
+ }
+
+ protected void onSettingsClickedH() {
+ // hook for subclasses
+ }
+
+ protected void onZenSettingsClickedH() {
+ // hook for subclasses
+ }
+
+ private void showH(int reason) {
+ mHandler.removeMessages(H.SHOW);
+ mHandler.removeMessages(H.DISMISS);
+ rescheduleTimeoutH();
+ if (mShowing) return;
+ mShowing = true;
+ mDialog.show();
+ Events.writeEvent(Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked());
+ mController.notifyVisible(true);
+ }
+
+ protected void rescheduleTimeoutH() {
+ mHandler.removeMessages(H.DISMISS);
+ final int timeout = computeTimeoutH();
+ if (D.BUG) Log.d(TAG, "rescheduleTimeout " + timeout);
+ mHandler.sendMessageDelayed(mHandler
+ .obtainMessage(H.DISMISS, Events.DISMISS_REASON_TIMEOUT, 0), timeout);
+ mController.userActivity();
+ }
+
+ private int computeTimeoutH() {
+ if (mZenFooter != null && mZenFooter.isFooterExpanded()) return 10000;
+ if (mSafetyWarning != null) return 5000;
+ if (mExpanded || mExpanding) return 5000;
+ if (mActiveStream == AudioManager.STREAM_MUSIC) return 1500;
+ return 3000;
+ }
+
+ protected void dismissH(int reason) {
+ mHandler.removeMessages(H.DISMISS);
+ mHandler.removeMessages(H.SHOW);
+ if (!mShowing) return;
+ mShowing = false;
+ mDialog.dismiss();
+ Events.writeEvent(Events.EVENT_DISMISS_DIALOG, reason);
+ setExpandedH(false);
+ mController.notifyVisible(false);
+ synchronized (mSafetyWarningLock) {
+ if (mSafetyWarning != null) {
+ if (D.BUG) Log.d(TAG, "SafetyWarning dismissed");
+ mSafetyWarning.dismiss();
+ }
+ }
+ }
+
+ private void setExpandedH(boolean expanded) {
+ if (mExpanded == expanded) return;
+ mExpanded = expanded;
+ mExpanding = isAttached();
+ if (D.BUG) Log.d(TAG, "setExpandedH " + expanded);
+ updateRowsH();
+ if (mExpanding) {
+ final Drawable d = mExpandButton.getDrawable();
+ if (d instanceof AnimatedVectorDrawable) {
+ // workaround to reset drawable
+ final AnimatedVectorDrawable avd = (AnimatedVectorDrawable) d.getConstantState()
+ .newDrawable();
+ mExpandButton.setImageDrawable(avd);
+ avd.start();
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mExpanding = false;
+ updateExpandButtonH();
+ rescheduleTimeoutH();
+ }
+ }, mExpandButtonAnimationDuration);
+ }
+ }
+ rescheduleTimeoutH();
+ }
+
+ private void updateExpandButtonH() {
+ mExpandButton.setClickable(!mExpanding);
+ if (mExpanding && isAttached()) return;
+ final int res = mExpanded ? R.drawable.ic_volume_collapse_animation
+ : R.drawable.ic_volume_expand_animation;
+ if (res == mExpandButtonRes) return;
+ mExpandButtonRes = res;
+ mExpandButton.setImageResource(res);
+ }
+
+ private boolean isVisibleH(VolumeRow row, boolean isActive) {
+ return mExpanded && row.view.getVisibility() == View.VISIBLE
+ || (mExpanded && (row.important || isActive))
+ || !mExpanded && isActive;
+ }
+
+ private void updateRowsH() {
+ final VolumeRow activeRow = getActiveRow();
+ updateFooterH();
+ updateExpandButtonH();
+ final boolean footerVisible = mFooter.getVisibility() == View.VISIBLE;
+ if (!mShowing) {
+ trimObsoleteH();
+ }
+ // first, find the last visible row
+ VolumeRow lastVisible = null;
+ for (VolumeRow row : mRows) {
+ final boolean isActive = row == activeRow;
+ if (isVisibleH(row, isActive)) {
+ lastVisible = row;
+ }
+ }
+ // apply changes to all rows
+ for (VolumeRow row : mRows) {
+ final boolean isActive = row == activeRow;
+ final boolean visible = isVisibleH(row, isActive);
+ Util.setVisOrGone(row.view, visible);
+ Util.setVisOrGone(row.space, visible && mExpanded);
+ final int expandButtonRes = mExpanded ? R.drawable.ic_volume_settings : 0;
+ if (expandButtonRes != row.cachedExpandButtonRes) {
+ row.cachedExpandButtonRes = expandButtonRes;
+ if (expandButtonRes == 0) {
+ row.settingsButton.setImageDrawable(null);
+ } else {
+ row.settingsButton.setImageResource(expandButtonRes);
+ }
+ }
+ Util.setVisOrInvis(row.settingsButton,
+ mExpanded && (!footerVisible && row == lastVisible));
+ row.header.setAlpha(mExpanded && isActive ? 1 : 0.5f);
+ }
+ }
+
+ private void trimObsoleteH() {
+ for (int i = mRows.size() -1; i >= 0; i--) {
+ final VolumeRow row = mRows.get(i);
+ if (row.ss == null || !row.ss.dynamic) continue;
+ if (!mDynamic.get(row.stream)) {
+ mRows.remove(i);
+ mDialogContentView.removeView(row.view);
+ mDialogContentView.removeView(row.space);
+ }
+ }
+ }
+
+ private void onStateChangedH(State state) {
+ mState = state;
+ mDynamic.clear();
+ // add any new dynamic rows
+ for (int i = 0; i < state.states.size(); i++) {
+ final int stream = state.states.keyAt(i);
+ final StreamState ss = state.states.valueAt(i);
+ if (!ss.dynamic) continue;
+ mDynamic.put(stream, true);
+ if (findRow(stream) == null) {
+ addRow(stream, R.drawable.ic_volume_remote, R.drawable.ic_volume_remote_mute, true);
+ }
+ }
+
+ if (mActiveStream != state.activeStream) {
+ mActiveStream = state.activeStream;
+ updateRowsH();
+ rescheduleTimeoutH();
+ }
+ for (VolumeRow row : mRows) {
+ updateVolumeRowH(row);
+ }
+ updateFooterH();
+ }
+
+ private void updateTextFooterH() {
+ final boolean zen = mState.zenMode != Global.ZEN_MODE_OFF;
+ final boolean wasVisible = mFooter.getVisibility() == View.VISIBLE;
+ Util.setVisOrGone(mTextFooter, mExpanded && mShowFooter && (zen || mShowing && wasVisible));
+ if (mTextFooter.getVisibility() == View.VISIBLE) {
+ String text = null;
+ String action = null;
+ if (mState.exitCondition != null) {
+ final long countdown = ZenModeConfig.tryParseCountdownConditionId(mState
+ .exitCondition.id);
+ if (countdown != 0) {
+ text = mContext.getString(R.string.volume_dnd_ends_at,
+ Util.getShortTime(countdown));
+ action = mContext.getString(R.string.volume_end_now);
+ } else {
+ final DowntimeInfo info = ZenModeConfig.tryParseDowntimeConditionId(mState.
+ exitCondition.id);
+ if (info != null) {
+ text = mContext.getString(R.string.volume_dnd_ends_at,
+ Util.getShortTime(info));
+ action = mContext.getString(R.string.volume_end_now);
+ }
+ }
+ }
+ if (text == null) {
+ text = mContext.getString(R.string.volume_dnd_is_on);
+ }
+ if (action == null) {
+ action = mContext.getString(R.string.volume_turn_off);
+ }
+ Util.setText(mFootlineText, text);
+ Util.setText(mFootlineAction, action);
+ mFootlineAction.setOnClickListener(mTurnOffDnd);
+ }
+ Util.setVisOrGone(mFootlineText, zen);
+ Util.setVisOrGone(mFootlineAction, zen);
+ }
+
+ private void updateFooterH() {
+ if (!mShowFooter) {
+ Util.setVisOrGone(mFooter, false);
+ return;
+ }
+ if (mShowZenFooter) {
+ Util.setVisOrGone(mTextFooter, false);
+ final boolean ringActive = mActiveStream == AudioManager.STREAM_RING;
+ Util.setVisOrGone(mZenFooter, mZenFooter.isZen() && ringActive
+ || mShowing && (mExpanded || mZenFooter.getVisibility() == View.VISIBLE));
+ mZenFooter.update();
+ } else {
+ Util.setVisOrGone(mZenFooter, false);
+ updateTextFooterH();
+ }
+ }
+
+ private void updateVolumeRowH(VolumeRow row) {
+ if (mState == null) return;
+ final StreamState ss = mState.states.get(row.stream);
+ if (ss == null) return;
+ row.ss = ss;
+ if (ss.level > 0) {
+ row.lastAudibleLevel = ss.level;
+ }
+ final boolean isRingStream = row.stream == AudioManager.STREAM_RING;
+ final boolean isSystemStream = row.stream == AudioManager.STREAM_SYSTEM;
+ final boolean isRingVibrate = isRingStream
+ && mState.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE;
+ final boolean isNoned = (isRingStream || isSystemStream)
+ && mState.zenMode == Global.ZEN_MODE_NO_INTERRUPTIONS;
+ final boolean isLimited = isRingStream
+ && mState.zenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+
+ // update slider max
+ final int max = ss.levelMax * 100;
+ if (max != row.slider.getMax()) {
+ row.slider.setMax(max);
+ }
+
+ // update header visible
+ if (row.cachedShowHeaders != mShowHeaders) {
+ row.cachedShowHeaders = mShowHeaders;
+ Util.setVisOrGone(row.header, mShowHeaders);
+ }
+
+ // update header text
+ final String text;
+ if (isNoned) {
+ text = mContext.getString(R.string.volume_stream_muted_dnd, ss.name);
+ } else if (isRingVibrate && isLimited) {
+ text = mContext.getString(R.string.volume_stream_vibrate_dnd, ss.name);
+ } else if (isRingVibrate) {
+ text = mContext.getString(R.string.volume_stream_vibrate, ss.name);
+ } else if (ss.muted || mAutomute && ss.level == 0) {
+ text = mContext.getString(R.string.volume_stream_muted, ss.name);
+ } else if (isLimited) {
+ text = mContext.getString(R.string.volume_stream_limited_dnd, ss.name);
+ } else {
+ text = ss.name;
+ }
+ Util.setText(row.header, text);
+
+ // update icon
+ final boolean iconEnabled = mAutomute || ss.muteSupported;
+ row.icon.setEnabled(iconEnabled);
+ row.icon.setAlpha(iconEnabled ? 1 : 0.5f);
+ final int iconRes =
+ isRingVibrate ? R.drawable.ic_volume_ringer_vibrate
+ : ss.routedToBluetooth ?
+ (ss.muted ? R.drawable.ic_volume_bt_mute : R.drawable.ic_volume_bt)
+ : mAutomute && ss.level == 0 ? row.iconMuteRes
+ : (ss.muted ? row.iconMuteRes : row.iconRes);
+ if (iconRes != row.cachedIconRes) {
+ if (row.cachedIconRes != 0 && isRingVibrate) {
+ mController.vibrate();
+ }
+ row.cachedIconRes = iconRes;
+ row.icon.setImageResource(iconRes);
+ }
+ row.iconState =
+ iconRes == R.drawable.ic_volume_ringer_vibrate ? Events.ICON_STATE_VIBRATE
+ : (iconRes == R.drawable.ic_volume_bt_mute || iconRes == row.iconMuteRes)
+ ? Events.ICON_STATE_MUTE
+ : (iconRes == R.drawable.ic_volume_bt || iconRes == row.iconRes)
+ ? Events.ICON_STATE_UNMUTE
+ : Events.ICON_STATE_UNKNOWN;
+
+ // update slider
+ updateVolumeRowSliderH(row);
+ }
+
+ private void updateVolumeRowSliderH(VolumeRow row) {
+ if (row.tracking) {
+ return; // don't update if user is sliding
+ }
+ final int progress = row.slider.getProgress();
+ final int level = getImpliedLevel(row.slider, progress);
+ final boolean rowVisible = row.view.getVisibility() == View.VISIBLE;
+ final boolean inGracePeriod = (SystemClock.uptimeMillis() - row.userAttempt)
+ < USER_ATTEMPT_GRACE_PERIOD;
+ mHandler.removeMessages(H.RECHECK, row);
+ if (mShowing && rowVisible && inGracePeriod) {
+ if (D.BUG) Log.d(TAG, "inGracePeriod");
+ mHandler.sendMessageAtTime(mHandler.obtainMessage(H.RECHECK, row),
+ row.userAttempt + USER_ATTEMPT_GRACE_PERIOD);
+ return; // don't update if visible and in grace period
+ }
+ final int vlevel = row.ss.muted ? 0 : row.ss.level;
+ if (vlevel == level) {
+ if (mShowing && rowVisible) {
+ return; // don't clamp if visible
+ }
+ }
+ final int newProgress = vlevel * 100;
+ if (progress != newProgress) {
+ if (mShowing && rowVisible) {
+ // animate!
+ if (row.anim != null && row.anim.isRunning()
+ && row.animTargetProgress == newProgress) {
+ return; // already animating to the target progress
+ }
+ // start/update animation
+ if (row.anim == null) {
+ row.anim = ObjectAnimator.ofInt(row.slider, "progress", progress, newProgress);
+ row.anim.setInterpolator(new DecelerateInterpolator());
+ } else {
+ row.anim.cancel();
+ row.anim.setIntValues(progress, newProgress);
+ }
+ row.animTargetProgress = newProgress;
+ row.anim.setDuration(UPDATE_ANIMATION_DURATION);
+ row.anim.start();
+ } else {
+ // update slider directly to clamped value
+ if (row.anim != null) {
+ row.anim.cancel();
+ }
+ row.slider.setProgress(newProgress);
+ }
+ if (mAutomute) {
+ if (vlevel == 0 && !row.ss.muted && row.stream == AudioManager.STREAM_MUSIC) {
+ mController.setStreamMute(row.stream, true);
+ }
+ }
+ }
+ }
+
+ private void recheckH(VolumeRow row) {
+ if (row == null) {
+ if (D.BUG) Log.d(TAG, "recheckH ALL");
+ trimObsoleteH();
+ for (VolumeRow r : mRows) {
+ updateVolumeRowH(r);
+ }
+ } else {
+ if (D.BUG) Log.d(TAG, "recheckH " + row.stream);
+ updateVolumeRowH(row);
+ }
+ }
+
+ private void setStreamImportantH(int stream, boolean important) {
+ for (VolumeRow row : mRows) {
+ if (row.stream == stream) {
+ row.important = important;
+ return;
+ }
+ }
+ }
+
+ private void showSafetyWarningH(int flags) {
+ if ((flags & (AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_SHOW_UI_WARNINGS)) != 0
+ || mShowing) {
+ synchronized (mSafetyWarningLock) {
+ if (mSafetyWarning != null) {
+ return;
+ }
+ mSafetyWarning = new SafetyWarningDialog(mContext, mController.getAudioManager()) {
+ @Override
+ protected void cleanUp() {
+ synchronized (mSafetyWarningLock) {
+ mSafetyWarning = null;
+ }
+ recheckH(null);
+ }
+ };
+ mSafetyWarning.show();
+ }
+ recheckH(null);
+ }
+ rescheduleTimeoutH();
+ }
+
+ private final VolumeDialogController.Callbacks mControllerCallbackH
+ = new VolumeDialogController.Callbacks() {
+ @Override
+ public void onShowRequested(int reason) {
+ showH(reason);
+ }
+
+ @Override
+ public void onDismissRequested(int reason) {
+ dismissH(reason);
+ }
+
+ @Override
+ public void onScreenOff() {
+ dismissH(Events.DISMISS_REASON_SCREEN_OFF);
+ }
+
+ @Override
+ public void onStateChanged(State state) {
+ onStateChangedH(state);
+ }
+
+ @Override
+ public void onLayoutDirectionChanged(int layoutDirection) {
+ mDialogView.setLayoutDirection(layoutDirection);
+ }
+
+ @Override
+ public void onConfigurationChanged() {
+ updateWindowWidthH();
+ mSpTexts.update();
+ }
+
+ @Override
+ public void onShowVibrateHint() {
+ if (mSilentMode) {
+ mController.setRingerMode(AudioManager.RINGER_MODE_SILENT, false);
+ }
+ }
+
+ @Override
+ public void onShowSilentHint() {
+ if (mSilentMode) {
+ mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false);
+ }
+ }
+
+ @Override
+ public void onShowSafetyWarning(int flags) {
+ showSafetyWarningH(flags);
+ }
+ };
+
+ private final OnClickListener mClickExpand = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mExpanding) return;
+ final boolean newExpand = !mExpanded;
+ Events.writeEvent(Events.EVENT_EXPAND, v);
+ setExpandedH(newExpand);
+ }
+ };
+
+ private final OnClickListener mClickSettings = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mSettingsButton.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ Events.writeEvent(Events.EVENT_SETTINGS_CLICK);
+ onSettingsClickedH();
+ }
+ }, WAIT_FOR_RIPPLE);
+ }
+ };
+
+ private final View.OnClickListener mTurnOffDnd = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mSettingsButton.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mController.setZenMode(Global.ZEN_MODE_OFF);
+ }
+ }, WAIT_FOR_RIPPLE);
+ }
+ };
+
+ private final ZenFooter.Callback mZenFooterCallback = new ZenFooter.Callback() {
+ @Override
+ public void onFooterExpanded() {
+ mHandler.sendEmptyMessage(H.RESCHEDULE_TIMEOUT);
+ }
+
+ @Override
+ public void onSettingsClicked() {
+ dismiss(Events.DISMISS_REASON_SETTINGS_CLICKED);
+ onZenSettingsClickedH();
+ }
+
+ @Override
+ public void onDoneClicked() {
+ dismiss(Events.DISMISS_REASON_DONE_CLICKED);
+ }
+ };
+
+ private final class H extends Handler {
+ private static final int SHOW = 1;
+ private static final int DISMISS = 2;
+ private static final int RECHECK = 3;
+ private static final int RECHECK_ALL = 4;
+ private static final int SET_STREAM_IMPORTANT = 5;
+ private static final int RESCHEDULE_TIMEOUT = 6;
+
+ public H() {
+ super(Looper.getMainLooper());
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case SHOW: showH(msg.arg1); break;
+ case DISMISS: dismissH(msg.arg1); break;
+ case RECHECK: recheckH((VolumeRow) msg.obj); break;
+ case RECHECK_ALL: recheckH(null); break;
+ case SET_STREAM_IMPORTANT: setStreamImportantH(msg.arg1, msg.arg2 != 0); break;
+ case RESCHEDULE_TIMEOUT: rescheduleTimeoutH(); break;
+ }
+ }
+ }
+
+ private final class CustomDialog extends Dialog {
+ public CustomDialog(Context context) {
+ super(context);
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ rescheduleTimeoutH();
+ return super.dispatchTouchEvent(ev);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ mHandler.sendEmptyMessage(H.RECHECK_ALL);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (isShowing()) {
+ if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
+ dismissH(Events.DISMISS_REASON_TOUCH_OUTSIDE);
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ private final class VolumeSeekBarChangeListener implements OnSeekBarChangeListener {
+ private final VolumeRow mRow;
+
+ private VolumeSeekBarChangeListener(VolumeRow row) {
+ mRow = row;
+ }
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (mRow.ss == null) return;
+ if (D.BUG) Log.d(TAG, AudioSystem.streamToString(mRow.stream)
+ + " onProgressChanged " + progress + " fromUser=" + fromUser);
+ if (!fromUser) return;
+ if (mRow.ss.levelMin > 0) {
+ final int minProgress = mRow.ss.levelMin * 100;
+ if (progress < minProgress) {
+ seekBar.setProgress(minProgress);
+ }
+ }
+ final int userLevel = getImpliedLevel(seekBar, progress);
+ if (mRow.ss.level != userLevel || mRow.ss.muted && userLevel > 0) {
+ mRow.userAttempt = SystemClock.uptimeMillis();
+ if (mAutomute) {
+ if (mRow.stream != AudioManager.STREAM_RING) {
+ if (userLevel > 0 && mRow.ss.muted) {
+ mController.setStreamMute(mRow.stream, false);
+ }
+ if (userLevel == 0 && mRow.ss.muteSupported && !mRow.ss.muted) {
+ mController.setStreamMute(mRow.stream, true);
+ }
+ }
+ }
+ if (mRow.requestedLevel != userLevel) {
+ mController.setStreamVolume(mRow.stream, userLevel);
+ mRow.requestedLevel = userLevel;
+ Events.writeEvent(Events.EVENT_TOUCH_LEVEL_CHANGED, mRow.stream, userLevel);
+ }
+ }
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ if (D.BUG) Log.d(TAG, "onStartTrackingTouch"+ " " + mRow.stream);
+ mController.setActiveStream(mRow.stream);
+ mRow.tracking = true;
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ if (D.BUG) Log.d(TAG, "onStopTrackingTouch"+ " " + mRow.stream);
+ mRow.tracking = false;
+ mRow.userAttempt = SystemClock.uptimeMillis();
+ int userLevel = getImpliedLevel(seekBar, seekBar.getProgress());
+ if (mRow.ss.level != userLevel) {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(H.RECHECK, mRow),
+ USER_ATTEMPT_GRACE_PERIOD);
+ }
+ }
+ }
+
+ private static class VolumeRow {
+ private View view;
+ private View space;
+ private TextView header;
+ private ImageButton icon;
+ private SeekBar slider;
+ private ImageButton settingsButton;
+ private int stream;
+ private StreamState ss;
+ private long userAttempt; // last user-driven slider change
+ private boolean tracking; // tracking slider touch
+ private int requestedLevel;
+ private int iconRes;
+ private int iconMuteRes;
+ private boolean important;
+ private int cachedIconRes;
+ private int iconState; // from Events
+ private boolean cachedShowHeaders = Prefs.DEFAULT_SHOW_HEADERS;
+ private int cachedExpandButtonRes;
+ private ObjectAnimator anim; // slider progress animation for non-touch-related updates
+ private int animTargetProgress;
+ private int lastAudibleLevel = 1;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
new file mode 100644
index 0000000..741e498
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.media.AudioManager;
+import android.media.VolumePolicy;
+import android.os.Bundle;
+import android.os.Handler;
+
+import com.android.systemui.SystemUI;
+import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.qs.tiles.DndTile;
+import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.statusbar.policy.ZenModeController;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Implementation of VolumeComponent backed by the new volume dialog.
+ */
+public class VolumeDialogComponent implements VolumeComponent {
+ private final SystemUI mSysui;
+ private final Context mContext;
+ private final VolumeDialogController mController;
+ private final ZenModeController mZenModeController;
+ private final VolumeDialog mDialog;
+
+ public VolumeDialogComponent(SystemUI sysui, Context context, Handler handler,
+ ZenModeController zen) {
+ mSysui = sysui;
+ mContext = context;
+ mController = new VolumeDialogController(context, null) {
+ @Override
+ protected void onUserActivityW() {
+ sendUserActivity();
+ }
+ };
+ mZenModeController = zen;
+ mDialog = new VolumeDialog(context, mController, zen) {
+ @Override
+ protected void onZenSettingsClickedH() {
+ startZenSettings();
+ }
+ };
+ applyConfiguration();
+ }
+
+ private void sendUserActivity() {
+ final KeyguardViewMediator kvm = mSysui.getComponent(KeyguardViewMediator.class);
+ if (kvm != null) {
+ kvm.userActivity();
+ }
+ }
+
+ private void applyConfiguration() {
+ mDialog.setStreamImportant(AudioManager.STREAM_ALARM, true);
+ mDialog.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
+ mDialog.setShowHeaders(false);
+ mDialog.setShowFooter(true);
+ mDialog.setZenFooter(true);
+ mDialog.setAutomute(true);
+ mDialog.setSilentMode(false);
+ mController.setVolumePolicy(VolumePolicy.DEFAULT);
+ mController.showDndTile(false);
+ }
+
+ @Override
+ public ZenModeController getZenController() {
+ return mZenModeController;
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ // noop
+ }
+
+ @Override
+ public void dismissNow() {
+ mController.dismiss();
+ }
+
+ @Override
+ public void dispatchDemoCommand(String command, Bundle args) {
+ // noop
+ }
+
+ @Override
+ public void register() {
+ mController.register();
+ DndTile.setCombinedIcon(mContext, true);
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mController.dump(fd, pw, args);
+ mDialog.dump(pw);
+ }
+
+ private void startZenSettings() {
+ mSysui.getComponent(PhoneStatusBar.class).startActivityDismissingKeyguard(
+ ZenModePanel.ZEN_SETTINGS, true /* onlyProvisioned */, true /* dismissShade */);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
new file mode 100644
index 0000000..a3d9377
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
@@ -0,0 +1,988 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume;
+
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.database.ContentObserver;
+import android.media.AudioManager;
+import android.media.AudioSystem;
+import android.media.IVolumeController;
+import android.media.VolumePolicy;
+import android.media.session.MediaController.PlaybackInfo;
+import android.media.session.MediaSession.Token;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.Vibrator;
+import android.provider.Settings;
+import android.service.notification.Condition;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.systemui.R;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Source of truth for all state / events related to the volume dialog. No presentation.
+ *
+ * All work done on a dedicated background worker thread & associated worker.
+ *
+ * Methods ending in "W" must be called on the worker thread.
+ */
+public class VolumeDialogController {
+ private static final String TAG = Util.logTag(VolumeDialogController.class);
+
+ private static final int DYNAMIC_STREAM_START_INDEX = 100;
+ private static final int VIBRATE_HINT_DURATION = 50;
+
+ private static final int[] STREAMS = {
+ AudioSystem.STREAM_ALARM,
+ AudioSystem.STREAM_BLUETOOTH_SCO,
+ AudioSystem.STREAM_DTMF,
+ AudioSystem.STREAM_MUSIC,
+ AudioSystem.STREAM_NOTIFICATION,
+ AudioSystem.STREAM_RING,
+ AudioSystem.STREAM_SYSTEM,
+ AudioSystem.STREAM_SYSTEM_ENFORCED,
+ AudioSystem.STREAM_TTS,
+ AudioSystem.STREAM_VOICE_CALL,
+ };
+
+ private final HandlerThread mWorkerThread;
+ private final W mWorker;
+ private final Context mContext;
+ private final AudioManager mAudio;
+ private final NotificationManager mNoMan;
+ private final ComponentName mComponent;
+ private final SettingObserver mObserver;
+ private final Receiver mReceiver = new Receiver();
+ private final MediaSessions mMediaSessions;
+ private final VC mVolumeController = new VC();
+ private final C mCallbacks = new C();
+ private final State mState = new State();
+ private final String[] mStreamTitles;
+ private final MediaSessionsCallbacks mMediaSessionsCallbacksW = new MediaSessionsCallbacks();
+ private final Vibrator mVibrator;
+ private final boolean mHasVibrator;
+
+ private boolean mEnabled;
+ private boolean mDestroyed;
+ private VolumePolicy mVolumePolicy = new VolumePolicy(true, true, false, 400);
+ private boolean mShowDndTile = false;
+
+ public VolumeDialogController(Context context, ComponentName component) {
+ mContext = context.getApplicationContext();
+ Events.writeEvent(Events.EVENT_COLLECTION_STARTED);
+ mComponent = component;
+ mWorkerThread = new HandlerThread(VolumeDialogController.class.getSimpleName());
+ mWorkerThread.start();
+ mWorker = new W(mWorkerThread.getLooper());
+ mMediaSessions = createMediaSessions(mContext, mWorkerThread.getLooper(),
+ mMediaSessionsCallbacksW);
+ mAudio = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ mNoMan = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ mObserver = new SettingObserver(mWorker);
+ mObserver.init();
+ mReceiver.init();
+ mStreamTitles = mContext.getResources().getStringArray(R.array.volume_stream_titles);
+ mVibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
+ mHasVibrator = mVibrator != null && mVibrator.hasVibrator();
+ }
+
+ public AudioManager getAudioManager() {
+ return mAudio;
+ }
+
+ public void dismiss() {
+ mCallbacks.onDismissRequested(Events.DISMISS_REASON_VOLUME_CONTROLLER);
+ }
+
+ public void register() {
+ try {
+ mAudio.setVolumeController(mVolumeController);
+ } catch (SecurityException e) {
+ Log.w(TAG, "Unable to set the volume controller", e);
+ return;
+ }
+ setVolumePolicy(mVolumePolicy);
+ showDndTile(mShowDndTile);
+ try {
+ mMediaSessions.init();
+ } catch (SecurityException e) {
+ Log.w(TAG, "No access to media sessions", e);
+ }
+ }
+
+ public void setVolumePolicy(VolumePolicy policy) {
+ mVolumePolicy = policy;
+ try {
+ mAudio.setVolumePolicy(mVolumePolicy);
+ } catch (NoSuchMethodError e) {
+ Log.w(TAG, "No volume policy api");
+ }
+ }
+
+ protected MediaSessions createMediaSessions(Context context, Looper looper,
+ MediaSessions.Callbacks callbacks) {
+ return new MediaSessions(context, looper, callbacks);
+ }
+
+ public void destroy() {
+ if (D.BUG) Log.d(TAG, "destroy");
+ if (mDestroyed) return;
+ mDestroyed = true;
+ Events.writeEvent(Events.EVENT_COLLECTION_STOPPED);
+ mMediaSessions.destroy();
+ mObserver.destroy();
+ mReceiver.destroy();
+ mWorkerThread.quitSafely();
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println(VolumeDialogController.class.getSimpleName() + " state:");
+ pw.print(" mEnabled: "); pw.println(mEnabled);
+ pw.print(" mDestroyed: "); pw.println(mDestroyed);
+ pw.print(" mVolumePolicy: "); pw.println(mVolumePolicy);
+ pw.print(" mEnabled: "); pw.println(mEnabled);
+ pw.print(" mShowDndTile: "); pw.println(mShowDndTile);
+ pw.print(" mHasVibrator: "); pw.println(mHasVibrator);
+ pw.print(" mRemoteStreams: "); pw.println(mMediaSessionsCallbacksW.mRemoteStreams
+ .values());
+ pw.println();
+ mMediaSessions.dump(pw);
+ }
+
+ public void addCallback(Callbacks callback, Handler handler) {
+ mCallbacks.add(callback, handler);
+ }
+
+ public void removeCallback(Callbacks callback) {
+ mCallbacks.remove(callback);
+ }
+
+ public void getState() {
+ if (mDestroyed) return;
+ mWorker.sendEmptyMessage(W.GET_STATE);
+ }
+
+ public void notifyVisible(boolean visible) {
+ if (mDestroyed) return;
+ mWorker.obtainMessage(W.NOTIFY_VISIBLE, visible ? 1 : 0, 0).sendToTarget();
+ }
+
+ public void userActivity() {
+ if (mDestroyed) return;
+ mWorker.removeMessages(W.USER_ACTIVITY);
+ mWorker.sendEmptyMessage(W.USER_ACTIVITY);
+ }
+
+ public void setRingerMode(int value, boolean external) {
+ if (mDestroyed) return;
+ mWorker.obtainMessage(W.SET_RINGER_MODE, value, external ? 1 : 0).sendToTarget();
+ }
+
+ public void setZenMode(int value) {
+ if (mDestroyed) return;
+ mWorker.obtainMessage(W.SET_ZEN_MODE, value, 0).sendToTarget();
+ }
+
+ public void setExitCondition(Condition condition) {
+ if (mDestroyed) return;
+ mWorker.obtainMessage(W.SET_EXIT_CONDITION, condition).sendToTarget();
+ }
+
+ public void setStreamMute(int stream, boolean mute) {
+ if (mDestroyed) return;
+ mWorker.obtainMessage(W.SET_STREAM_MUTE, stream, mute ? 1 : 0).sendToTarget();
+ }
+
+ public void setStreamVolume(int stream, int level) {
+ if (mDestroyed) return;
+ mWorker.obtainMessage(W.SET_STREAM_VOLUME, stream, level).sendToTarget();
+ }
+
+ public void setActiveStream(int stream) {
+ if (mDestroyed) return;
+ mWorker.obtainMessage(W.SET_ACTIVE_STREAM, stream, 0).sendToTarget();
+ }
+
+ public void vibrate() {
+ if (mHasVibrator) {
+ mVibrator.vibrate(VIBRATE_HINT_DURATION);
+ }
+ }
+
+ public boolean hasVibrator() {
+ return mHasVibrator;
+ }
+
+ private void onNotifyVisibleW(boolean visible) {
+ if (mDestroyed) return;
+ mAudio.notifyVolumeControllerVisible(mVolumeController, visible);
+ if (!visible) {
+ if (updateActiveStreamW(-1)) {
+ mCallbacks.onStateChanged(mState);
+ }
+ }
+ }
+
+ protected void onUserActivityW() {
+ // hook for subclasses
+ }
+
+ private void onShowSafetyWarningW(int flags) {
+ mCallbacks.onShowSafetyWarning(flags);
+ }
+
+ private boolean checkRoutedToBluetoothW(int stream) {
+ boolean changed = false;
+ if (stream == AudioManager.STREAM_MUSIC) {
+ final boolean routedToBluetooth =
+ (mAudio.getDevicesForStream(AudioManager.STREAM_MUSIC) &
+ (AudioManager.DEVICE_OUT_BLUETOOTH_A2DP |
+ AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
+ AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER)) != 0;
+ changed |= updateStreamRoutedToBluetoothW(stream, routedToBluetooth);
+ }
+ return changed;
+ }
+
+ private void onVolumeChangedW(int stream, int flags) {
+ final boolean showUI = (flags & AudioManager.FLAG_SHOW_UI) != 0;
+ final boolean fromKey = (flags & AudioManager.FLAG_FROM_KEY) != 0;
+ final boolean showVibrateHint = (flags & AudioManager.FLAG_SHOW_VIBRATE_HINT) != 0;
+ final boolean showSilentHint = (flags & AudioManager.FLAG_SHOW_SILENT_HINT) != 0;
+ boolean changed = false;
+ if (showUI) {
+ changed |= updateActiveStreamW(stream);
+ }
+ changed |= updateStreamLevelW(stream, mAudio.getLastAudibleStreamVolume(stream));
+ changed |= checkRoutedToBluetoothW(showUI ? AudioManager.STREAM_MUSIC : stream);
+ if (changed) {
+ mCallbacks.onStateChanged(mState);
+ }
+ if (showUI) {
+ mCallbacks.onShowRequested(Events.SHOW_REASON_VOLUME_CHANGED);
+ }
+ if (showVibrateHint) {
+ mCallbacks.onShowVibrateHint();
+ }
+ if (showSilentHint) {
+ mCallbacks.onShowSilentHint();
+ }
+ if (changed && fromKey) {
+ Events.writeEvent(Events.EVENT_KEY);
+ }
+ }
+
+ private boolean updateActiveStreamW(int activeStream) {
+ if (activeStream == mState.activeStream) return false;
+ mState.activeStream = activeStream;
+ Events.writeEvent(Events.EVENT_ACTIVE_STREAM_CHANGED, activeStream);
+ if (D.BUG) Log.d(TAG, "updateActiveStreamW " + activeStream);
+ final int s = activeStream < DYNAMIC_STREAM_START_INDEX ? activeStream : -1;
+ if (D.BUG) Log.d(TAG, "forceVolumeControlStream " + s);
+ mAudio.forceVolumeControlStream(s);
+ return true;
+ }
+
+ private StreamState streamStateW(int stream) {
+ StreamState ss = mState.states.get(stream);
+ if (ss == null) {
+ ss = new StreamState();
+ mState.states.put(stream, ss);
+ }
+ return ss;
+ }
+
+ private void onGetStateW() {
+ for (int stream : STREAMS) {
+ updateStreamLevelW(stream, mAudio.getLastAudibleStreamVolume(stream));
+ streamStateW(stream).levelMin = mAudio.getStreamMinVolume(stream);
+ streamStateW(stream).levelMax = mAudio.getStreamMaxVolume(stream);
+ updateStreamMuteW(stream, mAudio.isStreamMute(stream));
+ final StreamState ss = streamStateW(stream);
+ ss.muteSupported = mAudio.isStreamAffectedByMute(stream);
+ ss.name = mStreamTitles[stream];
+ checkRoutedToBluetoothW(stream);
+ }
+ updateRingerModeExternalW(mAudio.getRingerMode());
+ updateZenModeW();
+ updateEffectsSuppressorW(mNoMan.getEffectsSuppressor());
+ updateExitConditionW();
+ mCallbacks.onStateChanged(mState);
+ }
+
+ private boolean updateStreamRoutedToBluetoothW(int stream, boolean routedToBluetooth) {
+ final StreamState ss = streamStateW(stream);
+ if (ss.routedToBluetooth == routedToBluetooth) return false;
+ ss.routedToBluetooth = routedToBluetooth;
+ if (D.BUG) Log.d(TAG, "updateStreamRoutedToBluetoothW stream=" + stream
+ + " routedToBluetooth=" + routedToBluetooth);
+ return true;
+ }
+
+ private boolean updateStreamLevelW(int stream, int level) {
+ final StreamState ss = streamStateW(stream);
+ if (ss.level == level) return false;
+ ss.level = level;
+ if (isLogWorthy(stream)) {
+ Events.writeEvent(Events.EVENT_LEVEL_CHANGED, stream, level);
+ }
+ return true;
+ }
+
+ private static boolean isLogWorthy(int stream) {
+ switch (stream) {
+ case AudioSystem.STREAM_ALARM:
+ case AudioSystem.STREAM_BLUETOOTH_SCO:
+ case AudioSystem.STREAM_MUSIC:
+ case AudioSystem.STREAM_RING:
+ case AudioSystem.STREAM_SYSTEM:
+ case AudioSystem.STREAM_VOICE_CALL:
+ return true;
+ }
+ return false;
+ }
+
+ private boolean updateStreamMuteW(int stream, boolean muted) {
+ final StreamState ss = streamStateW(stream);
+ if (ss.muted == muted) return false;
+ ss.muted = muted;
+ if (isLogWorthy(stream)) {
+ Events.writeEvent(Events.EVENT_MUTE_CHANGED, stream, muted);
+ }
+ if (muted && isRinger(stream)) {
+ updateRingerModeInternalW(mAudio.getRingerModeInternal());
+ }
+ return true;
+ }
+
+ private static boolean isRinger(int stream) {
+ return stream == AudioManager.STREAM_RING || stream == AudioManager.STREAM_NOTIFICATION;
+ }
+
+ private boolean updateExitConditionW() {
+ final Condition exitCondition = mNoMan.getZenModeCondition();
+ if (Objects.equals(mState.exitCondition, exitCondition)) return false;
+ mState.exitCondition = exitCondition;
+ return true;
+ }
+
+ private boolean updateEffectsSuppressorW(ComponentName effectsSuppressor) {
+ if (Objects.equals(mState.effectsSuppressor, effectsSuppressor)) return false;
+ mState.effectsSuppressor = effectsSuppressor;
+ mState.effectsSuppressorName = getApplicationName(mContext, mState.effectsSuppressor);
+ Events.writeEvent(Events.EVENT_SUPPRESSOR_CHANGED, mState.effectsSuppressor,
+ mState.effectsSuppressorName);
+ return true;
+ }
+
+ private static String getApplicationName(Context context, ComponentName component) {
+ if (component == null) return null;
+ final PackageManager pm = context.getPackageManager();
+ final String pkg = component.getPackageName();
+ try {
+ final ApplicationInfo ai = pm.getApplicationInfo(pkg, 0);
+ final String rt = Objects.toString(ai.loadLabel(pm), "").trim();
+ if (rt.length() > 0) {
+ return rt;
+ }
+ } catch (NameNotFoundException e) {}
+ return pkg;
+ }
+
+ private boolean updateZenModeW() {
+ final int zen = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF);
+ if (mState.zenMode == zen) return false;
+ mState.zenMode = zen;
+ Events.writeEvent(Events.EVENT_ZEN_MODE_CHANGED, zen);
+ return true;
+ }
+
+ private boolean updateRingerModeExternalW(int rm) {
+ if (rm == mState.ringerModeExternal) return false;
+ mState.ringerModeExternal = rm;
+ Events.writeEvent(Events.EVENT_EXTERNAL_RINGER_MODE_CHANGED, rm);
+ return true;
+ }
+
+ private boolean updateRingerModeInternalW(int rm) {
+ if (rm == mState.ringerModeInternal) return false;
+ mState.ringerModeInternal = rm;
+ Events.writeEvent(Events.EVENT_INTERNAL_RINGER_MODE_CHANGED, rm);
+ return true;
+ }
+
+ private void onSetRingerModeW(int mode, boolean external) {
+ if (external) {
+ mAudio.setRingerMode(mode);
+ } else {
+ mAudio.setRingerModeInternal(mode);
+ }
+ }
+
+ private void onSetStreamMuteW(int stream, boolean mute) {
+ mAudio.adjustStreamVolume(stream, mute ? AudioManager.ADJUST_MUTE
+ : AudioManager.ADJUST_UNMUTE, 0);
+ }
+
+ private void onSetStreamVolumeW(int stream, int level) {
+ if (D.BUG) Log.d(TAG, "onSetStreamVolume " + stream + " level=" + level);
+ if (stream >= DYNAMIC_STREAM_START_INDEX) {
+ mMediaSessionsCallbacksW.setStreamVolume(stream, level);
+ return;
+ }
+ mAudio.setStreamVolume(stream, level, 0);
+ }
+
+ private void onSetActiveStreamW(int stream) {
+ boolean changed = updateActiveStreamW(stream);
+ if (changed) {
+ mCallbacks.onStateChanged(mState);
+ }
+ }
+
+ private void onSetExitConditionW(Condition condition) {
+ mNoMan.setZenModeCondition(condition);
+ }
+
+ private void onSetZenModeW(int mode) {
+ if (D.BUG) Log.d(TAG, "onSetZenModeW " + mode);
+ mNoMan.setZenMode(mode);
+ }
+
+ private void onDismissRequestedW(int reason) {
+ mCallbacks.onDismissRequested(reason);
+ }
+
+ public void showDndTile(boolean visible) {
+ if (D.BUG) Log.d(TAG, "showDndTile");
+ mContext.sendBroadcast(new Intent("com.android.systemui.dndtile.SET_VISIBLE")
+ .putExtra("visible", visible));
+ }
+
+ private final class VC extends IVolumeController.Stub {
+ private final String TAG = VolumeDialogController.TAG + ".VC";
+
+ @Override
+ public void displaySafeVolumeWarning(int flags) throws RemoteException {
+ if (D.BUG) Log.d(TAG, "displaySafeVolumeWarning "
+ + Util.audioManagerFlagsToString(flags));
+ if (mDestroyed) return;
+ mWorker.obtainMessage(W.SHOW_SAFETY_WARNING, flags, 0).sendToTarget();
+ }
+
+ @Override
+ public void volumeChanged(int streamType, int flags) throws RemoteException {
+ if (D.BUG) Log.d(TAG, "volumeChanged " + AudioSystem.streamToString(streamType)
+ + " " + Util.audioManagerFlagsToString(flags));
+ if (mDestroyed) return;
+ mWorker.obtainMessage(W.VOLUME_CHANGED, streamType, flags).sendToTarget();
+ }
+
+ @Override
+ public void masterMuteChanged(int flags) throws RemoteException {
+ if (D.BUG) Log.d(TAG, "masterMuteChanged");
+ }
+
+ @Override
+ public void setLayoutDirection(int layoutDirection) throws RemoteException {
+ if (D.BUG) Log.d(TAG, "setLayoutDirection");
+ if (mDestroyed) return;
+ mWorker.obtainMessage(W.LAYOUT_DIRECTION_CHANGED, layoutDirection, 0).sendToTarget();
+ }
+
+ @Override
+ public void dismiss() throws RemoteException {
+ if (D.BUG) Log.d(TAG, "dismiss requested");
+ if (mDestroyed) return;
+ mWorker.obtainMessage(W.DISMISS_REQUESTED, Events.DISMISS_REASON_VOLUME_CONTROLLER, 0)
+ .sendToTarget();
+ mWorker.sendEmptyMessage(W.DISMISS_REQUESTED);
+ }
+ }
+
+ private final class W extends Handler {
+ private static final int VOLUME_CHANGED = 1;
+ private static final int DISMISS_REQUESTED = 2;
+ private static final int GET_STATE = 3;
+ private static final int SET_RINGER_MODE = 4;
+ private static final int SET_ZEN_MODE = 5;
+ private static final int SET_EXIT_CONDITION = 6;
+ private static final int SET_STREAM_MUTE = 7;
+ private static final int LAYOUT_DIRECTION_CHANGED = 8;
+ private static final int CONFIGURATION_CHANGED = 9;
+ private static final int SET_STREAM_VOLUME = 10;
+ private static final int SET_ACTIVE_STREAM = 11;
+ private static final int NOTIFY_VISIBLE = 12;
+ private static final int USER_ACTIVITY = 13;
+ private static final int SHOW_SAFETY_WARNING = 14;
+
+ W(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case VOLUME_CHANGED: onVolumeChangedW(msg.arg1, msg.arg2); break;
+ case DISMISS_REQUESTED: onDismissRequestedW(msg.arg1); break;
+ case GET_STATE: onGetStateW(); break;
+ case SET_RINGER_MODE: onSetRingerModeW(msg.arg1, msg.arg2 != 0); break;
+ case SET_ZEN_MODE: onSetZenModeW(msg.arg1); break;
+ case SET_EXIT_CONDITION: onSetExitConditionW((Condition) msg.obj); break;
+ case SET_STREAM_MUTE: onSetStreamMuteW(msg.arg1, msg.arg2 != 0); break;
+ case LAYOUT_DIRECTION_CHANGED: mCallbacks.onLayoutDirectionChanged(msg.arg1); break;
+ case CONFIGURATION_CHANGED: mCallbacks.onConfigurationChanged(); break;
+ case SET_STREAM_VOLUME: onSetStreamVolumeW(msg.arg1, msg.arg2); break;
+ case SET_ACTIVE_STREAM: onSetActiveStreamW(msg.arg1); break;
+ case NOTIFY_VISIBLE: onNotifyVisibleW(msg.arg1 != 0); break;
+ case USER_ACTIVITY: onUserActivityW(); break;
+ case SHOW_SAFETY_WARNING: onShowSafetyWarningW(msg.arg1); break;
+ }
+ }
+ }
+
+ private final class C implements Callbacks {
+ private final HashMap<Callbacks, Handler> mCallbackMap = new HashMap<>();
+
+ public void add(Callbacks callback, Handler handler) {
+ if (callback == null || handler == null) throw new IllegalArgumentException();
+ mCallbackMap.put(callback, handler);
+ }
+
+ public void remove(Callbacks callback) {
+ mCallbackMap.remove(callback);
+ }
+
+ @Override
+ public void onShowRequested(final int reason) {
+ for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+ entry.getValue().post(new Runnable() {
+ @Override
+ public void run() {
+ entry.getKey().onShowRequested(reason);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onDismissRequested(final int reason) {
+ for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+ entry.getValue().post(new Runnable() {
+ @Override
+ public void run() {
+ entry.getKey().onDismissRequested(reason);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onStateChanged(final State state) {
+ final long time = System.currentTimeMillis();
+ final State copy = state.copy();
+ for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+ entry.getValue().post(new Runnable() {
+ @Override
+ public void run() {
+ entry.getKey().onStateChanged(copy);
+ }
+ });
+ }
+ Events.writeState(time, copy);
+ }
+
+ @Override
+ public void onLayoutDirectionChanged(final int layoutDirection) {
+ for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+ entry.getValue().post(new Runnable() {
+ @Override
+ public void run() {
+ entry.getKey().onLayoutDirectionChanged(layoutDirection);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged() {
+ for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+ entry.getValue().post(new Runnable() {
+ @Override
+ public void run() {
+ entry.getKey().onConfigurationChanged();
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onShowVibrateHint() {
+ for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+ entry.getValue().post(new Runnable() {
+ @Override
+ public void run() {
+ entry.getKey().onShowVibrateHint();
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onShowSilentHint() {
+ for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+ entry.getValue().post(new Runnable() {
+ @Override
+ public void run() {
+ entry.getKey().onShowSilentHint();
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onScreenOff() {
+ for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+ entry.getValue().post(new Runnable() {
+ @Override
+ public void run() {
+ entry.getKey().onScreenOff();
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onShowSafetyWarning(final int flags) {
+ for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+ entry.getValue().post(new Runnable() {
+ @Override
+ public void run() {
+ entry.getKey().onShowSafetyWarning(flags);
+ }
+ });
+ }
+ }
+ }
+
+
+ private final class SettingObserver extends ContentObserver {
+ private final Uri SERVICE_URI = Settings.Secure.getUriFor(
+ Settings.Secure.VOLUME_CONTROLLER_SERVICE_COMPONENT);
+ private final Uri ZEN_MODE_URI =
+ Settings.Global.getUriFor(Settings.Global.ZEN_MODE);
+ private final Uri ZEN_MODE_CONFIG_URI =
+ Settings.Global.getUriFor(Settings.Global.ZEN_MODE_CONFIG_ETAG);
+
+ public SettingObserver(Handler handler) {
+ super(handler);
+ }
+
+ public void init() {
+ mContext.getContentResolver().registerContentObserver(SERVICE_URI, false, this);
+ mContext.getContentResolver().registerContentObserver(ZEN_MODE_URI, false, this);
+ mContext.getContentResolver().registerContentObserver(ZEN_MODE_CONFIG_URI, false, this);
+ onChange(true, SERVICE_URI);
+ }
+
+ public void destroy() {
+ mContext.getContentResolver().unregisterContentObserver(this);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ boolean changed = false;
+ if (SERVICE_URI.equals(uri)) {
+ final String setting = Settings.Secure.getString(mContext.getContentResolver(),
+ Settings.Secure.VOLUME_CONTROLLER_SERVICE_COMPONENT);
+ final boolean enabled = setting != null && mComponent != null
+ && mComponent.equals(ComponentName.unflattenFromString(setting));
+ if (enabled == mEnabled) return;
+ if (enabled) {
+ register();
+ }
+ mEnabled = enabled;
+ }
+ if (ZEN_MODE_URI.equals(uri)) {
+ changed = updateZenModeW();
+ }
+ if (ZEN_MODE_CONFIG_URI.equals(uri)) {
+ changed = updateExitConditionW();
+ }
+ if (changed) {
+ mCallbacks.onStateChanged(mState);
+ }
+ }
+ }
+
+ private final class Receiver extends BroadcastReceiver {
+
+ public void init() {
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
+ filter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION);
+ filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
+ filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
+ filter.addAction(AudioManager.STREAM_MUTE_CHANGED_ACTION);
+ filter.addAction(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED);
+ filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ mContext.registerReceiver(this, filter, null, mWorker);
+ }
+
+ public void destroy() {
+ mContext.unregisterReceiver(this);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ boolean changed = false;
+ if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
+ final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
+ final int level = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
+ final int oldLevel = intent
+ .getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, -1);
+ if (D.BUG) Log.d(TAG, "onReceive VOLUME_CHANGED_ACTION stream=" + stream
+ + " level=" + level + " oldLevel=" + oldLevel);
+ changed = updateStreamLevelW(stream, level);
+ } else if (action.equals(AudioManager.STREAM_DEVICES_CHANGED_ACTION)) {
+ final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
+ final int devices = intent
+ .getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_DEVICES, -1);
+ final int oldDevices = intent
+ .getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_DEVICES, -1);
+ if (D.BUG) Log.d(TAG, "onReceive STREAM_DEVICES_CHANGED_ACTION stream="
+ + stream + " devices=" + devices + " oldDevices=" + oldDevices);
+ changed = checkRoutedToBluetoothW(stream);
+ } else if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
+ final int rm = intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1);
+ if (D.BUG) Log.d(TAG, "onReceive RINGER_MODE_CHANGED_ACTION rm="
+ + Util.ringerModeToString(rm));
+ changed = updateRingerModeExternalW(rm);
+ } else if (action.equals(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION)) {
+ final int rm = intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1);
+ if (D.BUG) Log.d(TAG, "onReceive INTERNAL_RINGER_MODE_CHANGED_ACTION rm="
+ + Util.ringerModeToString(rm));
+ changed = updateRingerModeInternalW(rm);
+ } else if (action.equals(AudioManager.STREAM_MUTE_CHANGED_ACTION)) {
+ final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
+ final boolean muted = intent
+ .getBooleanExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, false);
+ if (D.BUG) Log.d(TAG, "onReceive STREAM_MUTE_CHANGED_ACTION stream=" + stream
+ + " muted=" + muted);
+ changed = updateStreamMuteW(stream, muted);
+ } else if (action.equals(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED)) {
+ if (D.BUG) Log.d(TAG, "onReceive ACTION_EFFECTS_SUPPRESSOR_CHANGED");
+ changed = updateEffectsSuppressorW(mNoMan.getEffectsSuppressor());
+ } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
+ if (D.BUG) Log.d(TAG, "onReceive ACTION_CONFIGURATION_CHANGED");
+ mCallbacks.onConfigurationChanged();
+ } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
+ if (D.BUG) Log.d(TAG, "onReceive ACTION_SCREEN_OFF");
+ mCallbacks.onScreenOff();
+ }
+ if (changed) {
+ mCallbacks.onStateChanged(mState);
+ }
+ }
+ }
+
+ private final class MediaSessionsCallbacks implements MediaSessions.Callbacks {
+ private final HashMap<Token, Integer> mRemoteStreams = new HashMap<>();
+
+ private int mNextStream = DYNAMIC_STREAM_START_INDEX;
+
+ @Override
+ public void onRemoteUpdate(Token token, String name, PlaybackInfo pi) {
+ if (!mRemoteStreams.containsKey(token)) {
+ mRemoteStreams.put(token, mNextStream);
+ if (D.BUG) Log.d(TAG, "onRemoteUpdate: " + name + " is stream " + mNextStream);
+ mNextStream++;
+ }
+ final int stream = mRemoteStreams.get(token);
+ boolean changed = mState.states.indexOfKey(stream) < 0;
+ final StreamState ss = streamStateW(stream);
+ ss.dynamic = true;
+ ss.levelMin = 0;
+ ss.levelMax = pi.getMaxVolume();
+ if (ss.level != pi.getCurrentVolume()) {
+ ss.level = pi.getCurrentVolume();
+ changed = true;
+ }
+ if (!Objects.equals(ss.name, name)) {
+ ss.name = name;
+ changed = true;
+ }
+ if (changed) {
+ if (D.BUG) Log.d(TAG, "onRemoteUpdate: " + name + ": " + ss.level
+ + " of " + ss.levelMax);
+ mCallbacks.onStateChanged(mState);
+ }
+ }
+
+ @Override
+ public void onRemoteVolumeChanged(Token token, int flags) {
+ final int stream = mRemoteStreams.get(token);
+ final boolean showUI = (flags & AudioManager.FLAG_SHOW_UI) != 0;
+ boolean changed = updateActiveStreamW(stream);
+ if (showUI) {
+ changed |= checkRoutedToBluetoothW(AudioManager.STREAM_MUSIC);
+ }
+ if (changed) {
+ mCallbacks.onStateChanged(mState);
+ }
+ if (showUI) {
+ mCallbacks.onShowRequested(Events.SHOW_REASON_REMOTE_VOLUME_CHANGED);
+ }
+ }
+
+ @Override
+ public void onRemoteRemoved(Token token) {
+ final int stream = mRemoteStreams.get(token);
+ mState.states.remove(stream);
+ if (mState.activeStream == stream) {
+ updateActiveStreamW(-1);
+ }
+ mCallbacks.onStateChanged(mState);
+ }
+
+ public void setStreamVolume(int stream, int level) {
+ final Token t = findToken(stream);
+ if (t == null) {
+ Log.w(TAG, "setStreamVolume: No token found for stream: " + stream);
+ return;
+ }
+ mMediaSessions.setVolume(t, level);
+ }
+
+ private Token findToken(int stream) {
+ for (Map.Entry<Token, Integer> entry : mRemoteStreams.entrySet()) {
+ if (entry.getValue().equals(stream)) {
+ return entry.getKey();
+ }
+ }
+ return null;
+ }
+ }
+
+ public static final class StreamState {
+ public boolean dynamic;
+ public int level;
+ public int levelMin;
+ public int levelMax;
+ public boolean muted;
+ public boolean muteSupported;
+ public String name;
+ public boolean routedToBluetooth;
+
+ public StreamState copy() {
+ final StreamState rt = new StreamState();
+ rt.dynamic = dynamic;
+ rt.level = level;
+ rt.levelMin = levelMin;
+ rt.levelMax = levelMax;
+ rt.muted = muted;
+ rt.muteSupported = muteSupported;
+ rt.name = name;
+ rt.routedToBluetooth = routedToBluetooth;
+ return rt;
+ }
+ }
+
+ public static final class State {
+ public static int NO_ACTIVE_STREAM = -1;
+
+ public final SparseArray<StreamState> states = new SparseArray<StreamState>();
+
+ public int ringerModeInternal;
+ public int ringerModeExternal;
+ public int zenMode;
+ public ComponentName effectsSuppressor;
+ public String effectsSuppressorName;
+ public Condition exitCondition;
+ public int activeStream = NO_ACTIVE_STREAM;
+
+ public State copy() {
+ final State rt = new State();
+ for (int i = 0; i < states.size(); i++) {
+ rt.states.put(states.keyAt(i), states.valueAt(i).copy());
+ }
+ rt.ringerModeExternal = ringerModeExternal;
+ rt.ringerModeInternal = ringerModeInternal;
+ rt.zenMode = zenMode;
+ if (effectsSuppressor != null) rt.effectsSuppressor = effectsSuppressor.clone();
+ rt.effectsSuppressorName = effectsSuppressorName;
+ if (exitCondition != null) rt.exitCondition = exitCondition.copy();
+ rt.activeStream = activeStream;
+ return rt;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("{");
+ for (int i = 0; i < states.size(); i++) {
+ if (i > 0) sb.append(',');
+ final int stream = states.keyAt(i);
+ final StreamState ss = states.valueAt(i);
+ sb.append(AudioSystem.streamToString(stream)).append(":").append(ss.level)
+ .append("[").append(ss.levelMin).append("..").append(ss.levelMax);
+ if (ss.muted) sb.append(" [MUTED]");
+ }
+ sb.append(",ringerModeExternal:").append(ringerModeExternal);
+ sb.append(",ringerModeInternal:").append(ringerModeInternal);
+ sb.append(",zenMode:").append(zenMode);
+ sb.append(",effectsSuppressor:").append(effectsSuppressor);
+ sb.append(",effectsSuppressorName:").append(effectsSuppressorName);
+ sb.append(",exitCondition:").append(exitCondition);
+ sb.append(",activeStream:").append(activeStream);
+ return sb.append('}').toString();
+ }
+ }
+
+ public interface Callbacks {
+ void onShowRequested(int reason);
+ void onDismissRequested(int reason);
+ void onStateChanged(State state);
+ void onLayoutDirectionChanged(int layoutDirection);
+ void onConfigurationChanged();
+ void onShowVibrateHint();
+ void onShowSilentHint();
+ void onScreenOff();
+ void onShowSafetyWarning(int flags);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
index acdcfc1..f16e9d2 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
@@ -37,7 +37,6 @@ import android.graphics.PixelFormat;
import android.graphics.drawable.ColorDrawable;
import android.media.AudioAttributes;
import android.media.AudioManager;
-import android.media.AudioService;
import android.media.AudioSystem;
import android.media.RingtoneManager;
import android.media.ToneGenerator;
@@ -53,7 +52,6 @@ import android.os.Message;
import android.os.Vibrator;
import android.util.Log;
import android.util.SparseArray;
-import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -73,7 +71,6 @@ import android.widget.TextView;
import com.android.internal.R;
import com.android.systemui.DemoMode;
-import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.ZenModeController;
import java.io.FileDescriptor;
@@ -88,7 +85,7 @@ public class VolumePanel extends Handler implements DemoMode {
private static final String TAG = "VolumePanel";
private static boolean LOGD = Log.isLoggable(TAG, Log.DEBUG);
- private static final int PLAY_SOUND_DELAY = AudioService.PLAY_SOUND_DELAY;
+ private static final int PLAY_SOUND_DELAY = AudioSystem.PLAY_SOUND_DELAY;
/**
* The delay before vibrating. This small period exists so if the user is
@@ -126,8 +123,6 @@ public class VolumePanel extends Handler implements DemoMode {
private static final int MSG_NOTIFICATION_EFFECTS_SUPPRESSOR_CHANGED = 15;
private static final int MSG_INTERNAL_RINGER_MODE_CHANGED = 16;
- // Pseudo stream type for master volume
- private static final int STREAM_MASTER = -100;
// Pseudo stream type for remote volume
private static final int STREAM_REMOTE_MUSIC = -200;
@@ -155,10 +150,6 @@ public class VolumePanel extends Handler implements DemoMode {
private int mLastRingerProgress = 0;
private int mDemoIcon;
- // True if we want to play tones on the system stream when the master stream is specified.
- private final boolean mPlayMasterStreamTones;
-
-
/** Volume panel content view */
private final View mView;
/** Dialog hosting the panel */
@@ -214,12 +205,6 @@ public class VolumePanel extends Handler implements DemoMode {
com.android.systemui.R.drawable.ic_ringer_audible,
com.android.systemui.R.drawable.ic_ringer_mute,
true),
- // for now, use media resources for master volume
- MasterStream(STREAM_MASTER,
- R.string.volume_icon_description_media, //FIXME should have its own description
- IC_AUDIO_VOL,
- IC_AUDIO_VOL_MUTE,
- false),
RemoteStream(STREAM_REMOTE_MUSIC,
R.string.volume_icon_description_media, //FIXME should have its own description
com.android.systemui.R.drawable.ic_audio_remote,
@@ -250,7 +235,6 @@ public class VolumePanel extends Handler implements DemoMode {
StreamResources.MediaStream,
StreamResources.NotificationStream,
StreamResources.AlarmStream,
- StreamResources.MasterStream,
StreamResources.RemoteStream
};
@@ -267,6 +251,7 @@ public class VolumePanel extends Handler implements DemoMode {
int iconRes;
int iconMuteRes;
int iconSuppressedRes;
+ int minVolume;
}
// Synchronize when accessing this
@@ -277,78 +262,15 @@ public class VolumePanel extends Handler implements DemoMode {
private static AlertDialog sSafetyWarning;
private static Object sSafetyWarningLock = new Object();
- private static class SafetyWarning extends SystemUIDialog
- implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener {
- private final Context mContext;
- private final VolumePanel mVolumePanel;
- private final AudioManager mAudioManager;
-
- private boolean mNewVolumeUp;
-
- SafetyWarning(Context context, VolumePanel volumePanel, AudioManager audioManager) {
- super(context);
- mContext = context;
- mVolumePanel = volumePanel;
- mAudioManager = audioManager;
-
- setMessage(mContext.getString(com.android.internal.R.string.safe_media_volume_warning));
- setButton(DialogInterface.BUTTON_POSITIVE,
- mContext.getString(com.android.internal.R.string.yes), this);
- setButton(DialogInterface.BUTTON_NEGATIVE,
- mContext.getString(com.android.internal.R.string.no), (OnClickListener) null);
- setOnDismissListener(this);
-
- IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
- context.registerReceiver(mReceiver, filter);
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_VOLUME_UP && event.getRepeatCount() == 0) {
- mNewVolumeUp = true;
- }
- return super.onKeyDown(keyCode, event);
- }
-
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_VOLUME_UP && mNewVolumeUp) {
- if (LOGD) Log.d(TAG, "Confirmed warning via VOLUME_UP");
- mAudioManager.disableSafeMediaVolume();
- dismiss();
- }
- return super.onKeyUp(keyCode, event);
- }
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- mAudioManager.disableSafeMediaVolume();
- }
-
- @Override
- public void onDismiss(DialogInterface unused) {
- mContext.unregisterReceiver(mReceiver);
- cleanUp();
- }
-
- private void cleanUp() {
- synchronized (sSafetyWarningLock) {
- sSafetyWarning = null;
- }
- mVolumePanel.forceTimeout(0);
- mVolumePanel.updateStates();
- }
-
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
- if (LOGD) Log.d(TAG, "Received ACTION_CLOSE_SYSTEM_DIALOGS");
- cancel();
- cleanUp();
- }
- }
- };
+ protected LayoutParams getDialogLayoutParams(Window window, Resources res) {
+ final LayoutParams lp = window.getAttributes();
+ lp.token = null;
+ lp.y = res.getDimensionPixelOffset(com.android.systemui.R.dimen.volume_panel_top);
+ lp.type = LayoutParams.TYPE_STATUS_BAR_PANEL;
+ lp.format = PixelFormat.TRANSLUCENT;
+ lp.windowAnimations = com.android.systemui.R.style.VolumePanelAnimation;
+ lp.setTitle(TAG);
+ return lp;
}
public VolumePanel(Context context, ZenModeController zenController) {
@@ -361,15 +283,6 @@ public class VolumePanel extends Handler implements DemoMode {
mSecondaryIconTransition = new SecondaryIconTransition();
mIconPulser = new IconPulser(context);
- // For now, only show master volume if master volume is supported
- final Resources res = context.getResources();
- final boolean useMasterVolume = res.getBoolean(R.bool.config_useMasterVolume);
- if (useMasterVolume) {
- for (int i = 0; i < STREAMS.length; i++) {
- StreamResources streamRes = STREAMS[i];
- streamRes.show = (streamRes.streamType == STREAM_MASTER);
- }
- }
if (LOGD) Log.d(mTag, "new VolumePanel");
mDisabledAlpha = 0.5f;
@@ -395,7 +308,7 @@ public class VolumePanel extends Handler implements DemoMode {
final Window window = mDialog.getWindow();
window.requestFeature(Window.FEATURE_NO_TITLE);
mDialog.setCanceledOnTouchOutside(true);
- mDialog.setContentView(com.android.systemui.R.layout.volume_dialog);
+ mDialog.setContentView(com.android.systemui.R.layout.volume_panel_dialog);
mDialog.setOnDismissListener(new OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
@@ -409,14 +322,8 @@ public class VolumePanel extends Handler implements DemoMode {
mDialog.create();
- final LayoutParams lp = window.getAttributes();
- lp.token = null;
- lp.y = res.getDimensionPixelOffset(com.android.systemui.R.dimen.volume_panel_top);
- lp.type = LayoutParams.TYPE_STATUS_BAR_PANEL;
- lp.format = PixelFormat.TRANSLUCENT;
- lp.windowAnimations = com.android.systemui.R.style.VolumePanelAnimation;
- lp.setTitle(TAG);
- window.setAttributes(lp);
+ final Resources res = context.getResources();
+ window.setAttributes(getDialogLayoutParams(window, res));
updateWidth();
@@ -444,16 +351,12 @@ public class VolumePanel extends Handler implements DemoMode {
mHasVibrator = mVibrator != null && mVibrator.hasVibrator();
mVoiceCapable = context.getResources().getBoolean(R.bool.config_voice_capable);
- if (mZenController != null && !useMasterVolume) {
+ if (mZenController != null) {
mZenModeAvailable = mZenController.isZenAvailable();
mNotificationEffectsSuppressor = mZenController.getEffectsSuppressor();
mZenController.addCallback(mZenCallback);
}
- final boolean masterVolumeOnly = res.getBoolean(R.bool.config_useMasterVolume);
- final boolean masterVolumeKeySounds = res.getBoolean(R.bool.config_useVolumeKeySounds);
- mPlayMasterStreamTones = masterVolumeOnly && masterVolumeKeySounds;
-
registerReceiver();
}
@@ -486,7 +389,6 @@ public class VolumePanel extends Handler implements DemoMode {
pw.print(" mDisabledAlpha="); pw.println(mDisabledAlpha);
pw.print(" mLastRingerMode="); pw.println(mLastRingerMode);
pw.print(" mLastRingerProgress="); pw.println(mLastRingerProgress);
- pw.print(" mPlayMasterStreamTones="); pw.println(mPlayMasterStreamTones);
pw.print(" isShowing()="); pw.println(isShowing());
pw.print(" mCallback="); pw.println(mCallback);
pw.print(" sConfirmSafeVolumeDialog=");
@@ -573,9 +475,7 @@ public class VolumePanel extends Handler implements DemoMode {
}
private boolean isMuted(int streamType) {
- if (streamType == STREAM_MASTER) {
- return mAudioManager.isMasterMute();
- } else if (streamType == STREAM_REMOTE_MUSIC) {
+ if (streamType == STREAM_REMOTE_MUSIC) {
// TODO do we need to support a distinct mute property for remote?
return false;
} else {
@@ -583,10 +483,16 @@ public class VolumePanel extends Handler implements DemoMode {
}
}
+ private int getStreamMinVolume(int streamType) {
+ if (streamType == STREAM_REMOTE_MUSIC) {
+ return 0;
+ } else {
+ return mAudioManager.getStreamMinVolume(streamType);
+ }
+ }
+
private int getStreamMaxVolume(int streamType) {
- if (streamType == STREAM_MASTER) {
- return mAudioManager.getMasterMaxVolume();
- } else if (streamType == STREAM_REMOTE_MUSIC) {
+ if (streamType == STREAM_REMOTE_MUSIC) {
if (mStreamControls != null) {
StreamControl sc = mStreamControls.get(streamType);
if (sc != null && sc.controller != null) {
@@ -601,9 +507,7 @@ public class VolumePanel extends Handler implements DemoMode {
}
private int getStreamVolume(int streamType) {
- if (streamType == STREAM_MASTER) {
- return mAudioManager.getMasterVolume();
- } else if (streamType == STREAM_REMOTE_MUSIC) {
+ if (streamType == STREAM_REMOTE_MUSIC) {
if (mStreamControls != null) {
StreamControl sc = mStreamControls.get(streamType);
if (sc != null && sc.controller != null) {
@@ -613,7 +517,7 @@ public class VolumePanel extends Handler implements DemoMode {
}
return -1;
} else {
- return mAudioManager.getStreamVolume(streamType);
+ return mAudioManager.getLastAudibleStreamVolume(streamType);
}
}
@@ -625,11 +529,7 @@ public class VolumePanel extends Handler implements DemoMode {
Log.w(mTag, "Adjusting remote volume without a controller!");
}
} else if (getStreamVolume(sc.streamType) != index) {
- if (sc.streamType == STREAM_MASTER) {
- mAudioManager.setMasterVolume(index, flags);
- } else {
- mAudioManager.setStreamVolume(sc.streamType, index, flags);
- }
+ mAudioManager.setStreamVolume(sc.streamType, index, flags);
}
}
@@ -694,9 +594,8 @@ public class VolumePanel extends Handler implements DemoMode {
}
});
}
- final int plusOne = (streamType == AudioSystem.STREAM_BLUETOOTH_SCO ||
- streamType == AudioSystem.STREAM_VOICE_CALL) ? 1 : 0;
- sc.seekbarView.setMax(getStreamMaxVolume(streamType) + plusOne);
+ sc.minVolume = getStreamMinVolume(streamType);
+ sc.seekbarView.setMax(getStreamMaxVolume(streamType) - sc.minVolume);
sc.seekbarView.setOnSeekBarChangeListener(mSeekListener);
sc.seekbarView.setTag(sc);
mStreamControls.put(streamType, sc);
@@ -739,7 +638,7 @@ public class VolumePanel extends Handler implements DemoMode {
if (progress < 0) {
progress = getStreamVolume(sc.streamType);
}
- sc.seekbarView.setProgress(progress);
+ sc.seekbarView.setProgress(progress - sc.minVolume);
if (isRinger) {
mLastRingerProgress = progress;
}
@@ -830,7 +729,7 @@ public class VolumePanel extends Handler implements DemoMode {
sc.icon.setAlpha(mDisabledAlpha);
sc.icon.setClickable(false);
} else if (fixedVolume ||
- (sc.streamType != mAudioManager.getMasterStreamType() && !isRinger && muted) ||
+ (sc.streamType != mAudioManager.getUiSoundsStreamType() && !isRinger && muted) ||
(sSafetyWarning != null)) {
sc.seekbarView.setEnabled(false);
} else {
@@ -974,10 +873,6 @@ public class VolumePanel extends Handler implements DemoMode {
obtainMessage(MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN).sendToTarget();
}
- public void postMasterVolumeChanged(int flags) {
- postVolumeChanged(STREAM_MASTER, flags);
- }
-
public void postMuteChanged(int streamType, int flags) {
if (hasMessages(MSG_VOLUME_CHANGED)) return;
synchronized (this) {
@@ -989,10 +884,6 @@ public class VolumePanel extends Handler implements DemoMode {
obtainMessage(MSG_MUTE_CHANGED, streamType, flags).sendToTarget();
}
- public void postMasterMuteChanged(int flags) {
- postMuteChanged(STREAM_MASTER, flags);
- }
-
public void postDisplaySafeVolumeWarning(int flags) {
if (hasMessages(MSG_DISPLAY_SAFE_VOLUME_WARNING)) return;
obtainMessage(MSG_DISPLAY_SAFE_VOLUME_WARNING, flags, 0).sendToTarget();
@@ -1012,7 +903,7 @@ public class VolumePanel extends Handler implements DemoMode {
}
private static String streamToString(int stream) {
- return AudioService.streamToString(stream);
+ return AudioSystem.streamToString(stream);
}
/**
@@ -1075,7 +966,7 @@ public class VolumePanel extends Handler implements DemoMode {
// get max volume for progress bar
- int max = getStreamMaxVolume(streamType);
+ int max = getStreamMaxVolume(streamType) - getStreamMinVolume(streamType);
StreamControl sc = mStreamControls.get(streamType);
switch (streamType) {
@@ -1102,17 +993,6 @@ public class VolumePanel extends Handler implements DemoMode {
break;
}
- case AudioManager.STREAM_VOICE_CALL: {
- /*
- * For in-call voice call volume, there is no inaudible volume.
- * Rescale the UI control so the progress bar doesn't go all
- * the way to zero and don't show the mute icon.
- */
- index++;
- max++;
- break;
- }
-
case AudioManager.STREAM_ALARM: {
break;
}
@@ -1126,17 +1006,6 @@ public class VolumePanel extends Handler implements DemoMode {
break;
}
- case AudioManager.STREAM_BLUETOOTH_SCO: {
- /*
- * For in-call voice call volume, there is no inaudible volume.
- * Rescale the UI control so the progress bar doesn't go all
- * the way to zero and don't show the mute icon.
- */
- index++;
- max++;
- break;
- }
-
case STREAM_REMOTE_MUSIC: {
if (controller == null && sc != null) {
// If we weren't passed one try using the last one set.
@@ -1189,9 +1058,7 @@ public class VolumePanel extends Handler implements DemoMode {
if (!isShowing()) {
int stream = (streamType == STREAM_REMOTE_MUSIC) ? -1 : streamType;
// when the stream is for remote playback, use -1 to reset the stream type evaluation
- if (stream != STREAM_MASTER) {
- mAudioManager.forceVolumeControlStream(stream);
- }
+ mAudioManager.forceVolumeControlStream(stream);
mDialog.show();
if (mCallback != null) {
mCallback.onVisible(true);
@@ -1340,7 +1207,16 @@ public class VolumePanel extends Handler implements DemoMode {
if (sSafetyWarning != null) {
return;
}
- sSafetyWarning = new SafetyWarning(mContext, this, mAudioManager);
+ sSafetyWarning = new SafetyWarningDialog(mContext, mAudioManager) {
+ @Override
+ protected void cleanUp() {
+ synchronized (sSafetyWarningLock) {
+ sSafetyWarning = null;
+ }
+ forceTimeout(0);
+ updateStates();
+ }
+ };
sSafetyWarning.show();
}
updateStates();
@@ -1357,16 +1233,6 @@ public class VolumePanel extends Handler implements DemoMode {
* Lock on this VolumePanel instance as long as you use the returned ToneGenerator.
*/
private ToneGenerator getOrCreateToneGenerator(int streamType) {
- if (streamType == STREAM_MASTER) {
- // For devices that use the master volume setting only but still want to
- // play a volume-changed tone, direct the master volume pseudostream to
- // the system stream's tone generator.
- if (mPlayMasterStreamTones) {
- streamType = AudioManager.STREAM_SYSTEM;
- } else {
- return null;
- }
- }
synchronized (this) {
if (mToneGenerators[streamType] == null) {
try {
@@ -1546,7 +1412,7 @@ public class VolumePanel extends Handler implements DemoMode {
final Object tag = seekBar.getTag();
if (fromUser && tag instanceof StreamControl) {
StreamControl sc = (StreamControl) tag;
- setStreamVolume(sc, progress,
+ setStreamVolume(sc, progress + sc.minVolume,
AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE);
}
resetTimeout();
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanelComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanelComponent.java
new file mode 100644
index 0000000..fa6ea9e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanelComponent.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.media.AudioManager;
+import android.media.IRemoteVolumeController;
+import android.media.IVolumeController;
+import android.media.VolumePolicy;
+import android.media.session.ISessionController;
+import android.media.session.MediaController;
+import android.media.session.MediaSessionManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.RemoteException;
+
+import com.android.systemui.R;
+import com.android.systemui.SystemUI;
+import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.qs.tiles.DndTile;
+import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.statusbar.policy.ZenModeController;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Implementation of VolumeComponent backed by the old volume panel.
+ */
+public class VolumePanelComponent implements VolumeComponent {
+
+ private final SystemUI mSysui;
+ private final Context mContext;
+ private final Handler mHandler;
+ private final VolumeController mVolumeController;
+ private final RemoteVolumeController mRemoteVolumeController;
+ private final AudioManager mAudioManager;
+ private final MediaSessionManager mMediaSessionManager;
+
+ private VolumePanel mPanel;
+ private int mDismissDelay;
+
+ public VolumePanelComponent(SystemUI sysui, Context context, Handler handler,
+ ZenModeController controller) {
+ mSysui = sysui;
+ mContext = context;
+ mHandler = handler;
+ mAudioManager = context.getSystemService(AudioManager.class);
+ mMediaSessionManager = context.getSystemService(MediaSessionManager.class);
+ mVolumeController = new VolumeController();
+ mRemoteVolumeController = new RemoteVolumeController();
+ mDismissDelay = mContext.getResources().getInteger(R.integer.volume_panel_dismiss_delay);
+ mPanel = new VolumePanel(mContext, controller);
+ mPanel.setCallback(new VolumePanel.Callback() {
+ @Override
+ public void onZenSettings() {
+ mHandler.removeCallbacks(mStartZenSettings);
+ mHandler.post(mStartZenSettings);
+ }
+
+ @Override
+ public void onInteraction() {
+ final KeyguardViewMediator kvm = mSysui.getComponent(KeyguardViewMediator.class);
+ if (kvm != null) {
+ kvm.userActivity();
+ }
+ }
+
+ @Override
+ public void onVisible(boolean visible) {
+ if (mAudioManager != null && mVolumeController != null) {
+ mAudioManager.notifyVolumeControllerVisible(mVolumeController, visible);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (mPanel != null) {
+ mPanel.dump(fd, pw, args);
+ }
+ }
+
+ public void register() {
+ mAudioManager.setVolumeController(mVolumeController);
+ mAudioManager.setVolumePolicy(VolumePolicy.DEFAULT);
+ mMediaSessionManager.setRemoteVolumeController(mRemoteVolumeController);
+ DndTile.setVisible(mContext, false);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ if (mPanel != null) {
+ mPanel.onConfigurationChanged(newConfig);
+ }
+ }
+
+ @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 Runnable mStartZenSettings = new Runnable() {
+ @Override
+ public void run() {
+ mSysui.getComponent(PhoneStatusBar.class).startActivityDismissingKeyguard(
+ ZenModePanel.ZEN_SETTINGS, true /* onlyProvisioned */, true /* dismissShade */);
+ mPanel.postDismiss(mDismissDelay);
+ }
+ };
+
+ private final class RemoteVolumeController extends IRemoteVolumeController.Stub {
+ @Override
+ public void remoteVolumeChanged(ISessionController binder, int flags)
+ throws RemoteException {
+ MediaController controller = new MediaController(mContext, binder);
+ mPanel.postRemoteVolumeChanged(controller, flags);
+ }
+
+ @Override
+ public void updateRemoteController(ISessionController session) throws RemoteException {
+ mPanel.postRemoteSliderVisibility(session != null);
+ // TODO stash default session in case the slider can be opened other
+ // than by remoteVolumeChanged.
+ }
+ }
+
+ /** For now, simply host an unmodified base volume panel in this process. */
+ private final class VolumeController extends IVolumeController.Stub {
+
+ @Override
+ public void displaySafeVolumeWarning(int flags) throws RemoteException {
+ mPanel.postDisplaySafeVolumeWarning(flags);
+ }
+
+ @Override
+ public void volumeChanged(int streamType, int flags)
+ throws RemoteException {
+ mPanel.postVolumeChanged(streamType, flags);
+ }
+
+ @Override
+ public void masterMuteChanged(int flags) throws RemoteException {
+ // no-op
+ }
+
+ @Override
+ public void setLayoutDirection(int layoutDirection)
+ throws RemoteException {
+ mPanel.postLayoutDirection(layoutDirection);
+ }
+
+ @Override
+ public void dismiss() throws RemoteException {
+ dismissNow();
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index 7102c2a..387aed0 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -1,216 +1,231 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.systemui.volume;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
-import android.database.ContentObserver;
import android.media.AudioManager;
-import android.media.IRemoteVolumeController;
-import android.media.IVolumeController;
-import android.media.session.ISessionController;
-import android.media.session.MediaController;
import android.media.session.MediaSessionManager;
-import android.net.Uri;
-import android.os.Bundle;
import android.os.Handler;
-import android.os.RemoteException;
+import android.os.SystemProperties;
import android.provider.Settings;
+import android.text.TextUtils;
import android.util.Log;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
-import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.qs.tiles.DndTile;
+import com.android.systemui.statusbar.ServiceMonitor;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.statusbar.policy.ZenModeControllerImpl;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
public class VolumeUI extends SystemUI {
private static final String TAG = "VolumeUI";
- private static final String SETTING = "systemui_volume_controller"; // for testing
- private static final Uri SETTING_URI = Settings.Global.getUriFor(SETTING);
- private static final int DEFAULT = 1; // enabled by default
+ private static boolean LOGD = Log.isLoggable(TAG, Log.DEBUG);
+
+ private static final boolean USE_OLD_VOLUME = SystemProperties.getBoolean("volume.old", false);
private final Handler mHandler = new Handler();
+ private final Receiver mReceiver = new Receiver();
+ private final RestorationNotification mRestorationNotification = new RestorationNotification();
private boolean mEnabled;
private AudioManager mAudioManager;
+ private NotificationManager mNotificationManager;
private MediaSessionManager mMediaSessionManager;
- private VolumeController mVolumeController;
- private RemoteVolumeController mRemoteVolumeController;
+ private ServiceMonitor mVolumeControllerService;
- private VolumePanel mPanel;
- private int mDismissDelay;
+ private VolumePanelComponent mOldVolume;
+ private VolumeDialogComponent mNewVolume;
@Override
public void start() {
mEnabled = mContext.getResources().getBoolean(R.bool.enable_volume_ui);
if (!mEnabled) return;
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ mNotificationManager =
+ (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
mMediaSessionManager = (MediaSessionManager) mContext
.getSystemService(Context.MEDIA_SESSION_SERVICE);
- initPanel();
- mVolumeController = new VolumeController();
- mRemoteVolumeController = new RemoteVolumeController();
- putComponent(VolumeComponent.class, mVolumeController);
- updateController();
- mContext.getContentResolver().registerContentObserver(SETTING_URI, false, mObserver);
+ final ZenModeController zenController = new ZenModeControllerImpl(mContext, mHandler);
+ mOldVolume = new VolumePanelComponent(this, mContext, mHandler, zenController);
+ mNewVolume = new VolumeDialogComponent(this, mContext, null, zenController);
+ putComponent(VolumeComponent.class, getVolumeComponent());
+ mReceiver.start();
+ mVolumeControllerService = new ServiceMonitor(TAG, LOGD,
+ mContext, Settings.Secure.VOLUME_CONTROLLER_SERVICE_COMPONENT,
+ new ServiceMonitorCallbacks());
+ mVolumeControllerService.start();
+ }
+
+ private VolumeComponent getVolumeComponent() {
+ return USE_OLD_VOLUME ? mOldVolume : mNewVolume;
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- if (mPanel != null) {
- mPanel.onConfigurationChanged(newConfig);
- }
+ if (!mEnabled) return;
+ getVolumeComponent().onConfigurationChanged(newConfig);
}
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.print("mEnabled="); pw.println(mEnabled);
- if (mPanel != null) {
- mPanel.dump(fd, pw, args);
- }
+ if (!mEnabled) return;
+ pw.print("mVolumeControllerService="); pw.println(mVolumeControllerService.getComponent());
+ getVolumeComponent().dump(fd, pw, args);
}
- private void updateController() {
- if (Settings.Global.getInt(mContext.getContentResolver(), SETTING, DEFAULT) != 0) {
- Log.d(TAG, "Registering volume controller");
- mAudioManager.setVolumeController(mVolumeController);
- mMediaSessionManager.setRemoteVolumeController(mRemoteVolumeController);
+ private void setDefaultVolumeController(boolean register) {
+ if (register) {
+ DndTile.setVisible(mContext, false);
+ if (LOGD) Log.d(TAG, "Registering default volume controller");
+ getVolumeComponent().register();
} else {
- Log.d(TAG, "Unregistering volume controller");
+ if (LOGD) Log.d(TAG, "Unregistering default volume controller");
mAudioManager.setVolumeController(null);
mMediaSessionManager.setRemoteVolumeController(null);
}
}
- private void initPanel() {
- mDismissDelay = mContext.getResources().getInteger(R.integer.volume_panel_dismiss_delay);
- mPanel = new VolumePanel(mContext, new ZenModeControllerImpl(mContext, mHandler));
- mPanel.setCallback(new VolumePanel.Callback() {
- @Override
- public void onZenSettings() {
- mHandler.removeCallbacks(mStartZenSettings);
- mHandler.post(mStartZenSettings);
- }
-
- @Override
- public void onInteraction() {
- final KeyguardViewMediator kvm = getComponent(KeyguardViewMediator.class);
- if (kvm != null) {
- kvm.userActivity();
- }
+ private String getAppLabel(ComponentName component) {
+ final String pkg = component.getPackageName();
+ try {
+ final ApplicationInfo ai = mContext.getPackageManager().getApplicationInfo(pkg, 0);
+ final String rt = mContext.getPackageManager().getApplicationLabel(ai).toString();
+ if (!TextUtils.isEmpty(rt)) {
+ return rt;
}
+ } catch (Exception e) {
+ Log.w(TAG, "Error loading app label", e);
+ }
+ return pkg;
+ }
+ private void showServiceActivationDialog(final ComponentName component) {
+ final SystemUIDialog d = new SystemUIDialog(mContext);
+ d.setMessage(mContext.getString(R.string.volumeui_prompt_message, getAppLabel(component)));
+ d.setPositiveButton(R.string.volumeui_prompt_allow, new OnClickListener() {
@Override
- public void onVisible(boolean visible) {
- if (mAudioManager != null && mVolumeController != null) {
- mAudioManager.notifyVolumeControllerVisible(mVolumeController, visible);
- }
+ public void onClick(DialogInterface dialog, int which) {
+ mVolumeControllerService.setComponent(component);
}
});
+ d.setNegativeButton(R.string.volumeui_prompt_deny, null);
+ d.show();
}
- private final ContentObserver mObserver = new ContentObserver(mHandler) {
- public void onChange(boolean selfChange, Uri uri) {
- if (SETTING_URI.equals(uri)) {
- updateController();
- }
- }
- };
-
- private final Runnable mStartZenSettings = new Runnable() {
- @Override
- public void run() {
- getComponent(PhoneStatusBar.class).startActivityDismissingKeyguard(
- ZenModePanel.ZEN_SETTINGS, true /* onlyProvisioned */, true /* dismissShade */);
- mPanel.postDismiss(mDismissDelay);
- }
- };
-
- /** For now, simply host an unmodified base volume panel in this process. */
- private final class VolumeController extends IVolumeController.Stub implements VolumeComponent {
-
- @Override
- public void displaySafeVolumeWarning(int flags) throws RemoteException {
- mPanel.postDisplaySafeVolumeWarning(flags);
- }
-
- @Override
- public void volumeChanged(int streamType, int flags)
- throws RemoteException {
- mPanel.postVolumeChanged(streamType, flags);
- }
-
+ private final class ServiceMonitorCallbacks implements ServiceMonitor.Callbacks {
@Override
- public void masterVolumeChanged(int flags) throws RemoteException {
- mPanel.postMasterVolumeChanged(flags);
- }
-
- @Override
- public void masterMuteChanged(int flags) throws RemoteException {
- mPanel.postMasterMuteChanged(flags);
+ public void onNoService() {
+ if (LOGD) Log.d(TAG, "onNoService");
+ setDefaultVolumeController(true);
+ mRestorationNotification.hide();
+ if (!mVolumeControllerService.isPackageAvailable()) {
+ mVolumeControllerService.setComponent(null);
+ }
}
@Override
- public void setLayoutDirection(int layoutDirection)
- throws RemoteException {
- mPanel.postLayoutDirection(layoutDirection);
+ public long onServiceStartAttempt() {
+ if (LOGD) Log.d(TAG, "onServiceStartAttempt");
+ // poke the setting to update the uid
+ mVolumeControllerService.setComponent(mVolumeControllerService.getComponent());
+ setDefaultVolumeController(false);
+ getVolumeComponent().dismissNow();
+ mRestorationNotification.show();
+ return 0;
}
+ }
- @Override
- public void dismiss() throws RemoteException {
- dismissNow();
- }
+ private final class Receiver extends BroadcastReceiver {
+ private static final String ENABLE = "com.android.systemui.vui.ENABLE";
+ private static final String DISABLE = "com.android.systemui.vui.DISABLE";
+ private static final String EXTRA_COMPONENT = "component";
- @Override
- public ZenModeController getZenController() {
- return mPanel.getZenController();
+ public void start() {
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(ENABLE);
+ filter.addAction(DISABLE);
+ mContext.registerReceiver(this, filter, null, mHandler);
}
@Override
- public void dispatchDemoCommand(String command, Bundle args) {
- mPanel.dispatchDemoCommand(command, args);
- }
-
- @Override
- public void dismissNow() {
- mPanel.postDismiss(0);
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ final ComponentName component = intent.getParcelableExtra(EXTRA_COMPONENT);
+ final boolean current = component.equals(mVolumeControllerService.getComponent());
+ if (ENABLE.equals(action) && component != null) {
+ if (!current) {
+ showServiceActivationDialog(component);
+ }
+ }
+ if (DISABLE.equals(action) && component != null) {
+ if (current) {
+ mVolumeControllerService.setComponent(null);
+ }
+ }
}
}
- private final class RemoteVolumeController extends IRemoteVolumeController.Stub {
-
- @Override
- public void remoteVolumeChanged(ISessionController binder, int flags)
- throws RemoteException {
- MediaController controller = new MediaController(mContext, binder);
- mPanel.postRemoteVolumeChanged(controller, flags);
+ private final class RestorationNotification {
+ public void hide() {
+ mNotificationManager.cancel(R.id.notification_volumeui);
}
- @Override
- public void updateRemoteController(ISessionController session) throws RemoteException {
- mPanel.postRemoteSliderVisibility(session != null);
- // TODO stash default session in case the slider can be opened other
- // than by remoteVolumeChanged.
+ public void show() {
+ final ComponentName component = mVolumeControllerService.getComponent();
+ if (component == null) {
+ Log.w(TAG, "Not showing restoration notification, component not active");
+ return;
+ }
+ final Intent intent = new Intent(Receiver.DISABLE)
+ .putExtra(Receiver.EXTRA_COMPONENT, component);
+ mNotificationManager.notify(R.id.notification_volumeui,
+ new Notification.Builder(mContext)
+ .setSmallIcon(R.drawable.ic_ringer_audible)
+ .setWhen(0)
+ .setShowWhen(false)
+ .setOngoing(true)
+ .setContentTitle(mContext.getString(
+ R.string.volumeui_notification_title, getAppLabel(component)))
+ .setContentText(mContext.getString(R.string.volumeui_notification_text))
+ .setContentIntent(PendingIntent.getBroadcast(mContext, 0, intent,
+ PendingIntent.FLAG_UPDATE_CURRENT))
+ .setPriority(Notification.PRIORITY_MIN)
+ .setVisibility(Notification.VISIBILITY_PUBLIC)
+ .setColor(mContext.getColor(
+ com.android.internal.R.color.system_notification_accent_color))
+ .build());
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java b/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java
new file mode 100644
index 0000000..f99eb6d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.volume;
+
+import android.animation.LayoutTransition;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.provider.Settings.Global;
+import android.service.notification.Condition;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.View;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.LinearLayout;
+import android.widget.Switch;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.ZenModeController;
+
+/**
+ * Switch bar + zen mode panel (conditions) attached to the bottom of the volume dialog.
+ */
+public class ZenFooter extends LinearLayout {
+ private static final String TAG = Util.logTag(ZenFooter.class);
+
+ private final Context mContext;
+ private final float mSecondaryAlpha;
+ private final LayoutTransition mLayoutTransition;
+
+ private ZenModeController mController;
+ private Switch mSwitch;
+ private ZenModePanel mZenModePanel;
+ private View mZenModePanelButtons;
+ private View mZenModePanelMoreButton;
+ private View mZenModePanelDoneButton;
+ private View mSwitchBar;
+ private View mSwitchBarIcon;
+ private View mSummary;
+ private TextView mSummaryLine1;
+ private TextView mSummaryLine2;
+ private boolean mFooterExpanded;
+ private int mZen = -1;
+ private Callback mCallback;
+
+ public ZenFooter(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mContext = context;
+ mSecondaryAlpha = getFloat(context.getResources(), R.dimen.volume_secondary_alpha);
+ mLayoutTransition = new LayoutTransition();
+ mLayoutTransition.setDuration(new ValueAnimator().getDuration() / 2);
+ mLayoutTransition.disableTransitionType(LayoutTransition.DISAPPEARING);
+ mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
+ }
+
+ private static float getFloat(Resources r, int resId) {
+ final TypedValue tv = new TypedValue();
+ r.getValue(resId, tv, true);
+ return tv.getFloat();
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mSwitchBar = findViewById(R.id.volume_zen_switch_bar);
+ mSwitchBarIcon = findViewById(R.id.volume_zen_switch_bar_icon);
+ mSwitch = (Switch) findViewById(R.id.volume_zen_switch);
+ mZenModePanel = (ZenModePanel) findViewById(R.id.zen_mode_panel);
+ mZenModePanelButtons = findViewById(R.id.volume_zen_mode_panel_buttons);
+ mZenModePanelMoreButton = findViewById(R.id.volume_zen_mode_panel_more);
+ mZenModePanelDoneButton = findViewById(R.id.volume_zen_mode_panel_done);
+ mSummary = findViewById(R.id.volume_zen_panel_summary);
+ mSummaryLine1 = (TextView) findViewById(R.id.volume_zen_panel_summary_line_1);
+ mSummaryLine2 = (TextView) findViewById(R.id.volume_zen_panel_summary_line_2);
+ }
+
+ public void init(ZenModeController controller, Callback callback) {
+ mCallback = callback;
+ mController = controller;
+ mZenModePanel.init(controller);
+ mZenModePanel.setEmbedded(true);
+ mSwitch.setOnCheckedChangeListener(mCheckedListener);
+ mController.addCallback(new ZenModeController.Callback() {
+ @Override
+ public void onZenChanged(int zen) {
+ setZen(zen);
+ }
+ @Override
+ public void onExitConditionChanged(Condition exitCondition) {
+ update();
+ }
+ });
+ mSwitchBar.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mSwitch.setChecked(!mSwitch.isChecked());
+ }
+ });
+ mZenModePanelMoreButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mCallback != null) {
+ mCallback.onSettingsClicked();
+ }
+ }
+ });
+ mZenModePanelDoneButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mCallback != null) {
+ mCallback.onDoneClicked();
+ }
+ }
+ });
+ mZen = mController.getZen();
+ update();
+ }
+
+ private void setZen(int zen) {
+ if (mZen == zen) return;
+ mZen = zen;
+ update();
+ }
+
+ public boolean isZen() {
+ return isZenPriority() || isZenAlarms() || isZenNone();
+ }
+
+ private boolean isZenPriority() {
+ return mZen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ }
+
+ private boolean isZenAlarms() {
+ return mZen == Global.ZEN_MODE_ALARMS;
+ }
+
+ private boolean isZenNone() {
+ return mZen == Global.ZEN_MODE_NO_INTERRUPTIONS;
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ setLayoutTransition(null);
+ setFooterExpanded(false);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ setLayoutTransition(mLayoutTransition);
+ }
+
+ private boolean setFooterExpanded(boolean expanded) {
+ if (mFooterExpanded == expanded) return false;
+ mFooterExpanded = expanded;
+ update();
+ if (mCallback != null) {
+ mCallback.onFooterExpanded();
+ }
+ return true;
+ }
+
+ public boolean isFooterExpanded() {
+ return mFooterExpanded;
+ }
+
+ public void update() {
+ final boolean isZen = isZen();
+ mSwitch.setOnCheckedChangeListener(null);
+ mSwitch.setChecked(isZen);
+ mSwitch.setOnCheckedChangeListener(mCheckedListener);
+ Util.setVisOrGone(mZenModePanel, isZen && mFooterExpanded);
+ Util.setVisOrGone(mZenModePanelButtons, isZen && mFooterExpanded);
+ Util.setVisOrGone(mSummary, isZen && !mFooterExpanded);
+ mSwitchBarIcon.setAlpha(isZen ? 1 : mSecondaryAlpha);
+ final String line1 =
+ isZenPriority() ? mContext.getString(R.string.interruption_level_priority)
+ : isZenAlarms() ? mContext.getString(R.string.interruption_level_alarms)
+ : isZenNone() ? mContext.getString(R.string.interruption_level_none)
+ : null;
+ Util.setText(mSummaryLine1, line1);
+ Util.setText(mSummaryLine2, mZenModePanel.getExitConditionText());
+ }
+
+ private final OnCheckedChangeListener mCheckedListener = new OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ if (D.BUG) Log.d(TAG, "onCheckedChanged " + isChecked);
+ if (isChecked != isZen()) {
+ final int newZen = isChecked ? Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
+ : Global.ZEN_MODE_OFF;
+ mZen = newZen; // this one's optimistic
+ setFooterExpanded(isChecked);
+ mController.setZen(newZen);
+ }
+ }
+ };
+
+ public interface Callback {
+ void onFooterExpanded();
+ void onSettingsClicked();
+ void onDoneClicked();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
index d40a2c0..cb6c29f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
@@ -19,12 +19,10 @@ package com.android.systemui.volume;
import android.animation.LayoutTransition;
import android.animation.LayoutTransition.TransitionListener;
import android.app.ActivityManager;
-import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
-import android.content.res.Resources;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Handler;
@@ -85,24 +83,26 @@ public class ZenModePanel extends LinearLayout {
private final int mSubheadWarningColor;
private final int mSubheadColor;
private final Interpolator mInterpolator;
- private final int mMaxConditions;
- private final int mMaxOptionalConditions;
- private final boolean mCountdownConditionSupported;
- private final int mFirstConditionIndex;
private final TransitionHelper mTransitionHelper = new TransitionHelper();
private final Uri mForeverId;
private String mTag = TAG + "/" + Integer.toHexString(System.identityHashCode(this));
private SegmentedButtons mZenButtons;
+ private ViewGroup mZenButtonsContainer;
private View mZenSubhead;
private TextView mZenSubheadCollapsed;
private TextView mZenSubheadExpanded;
+ private View mZenEmbeddedDivider;
private View mMoreSettings;
private LinearLayout mZenConditions;
private Callback mCallback;
private ZenModeController mController;
+ private boolean mCountdownConditionSupported;
+ private int mMaxConditions;
+ private int mMaxOptionalConditions;
+ private int mFirstConditionIndex;
private boolean mRequestingConditions;
private Condition mExitCondition;
private String mExitConditionText;
@@ -115,6 +115,7 @@ public class ZenModePanel extends LinearLayout {
private Condition mSessionExitCondition;
private Condition[] mConditions;
private Condition mTimeCondition;
+ private boolean mEmbedded;
public ZenModePanel(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -122,19 +123,10 @@ public class ZenModePanel extends LinearLayout {
mPrefs = new Prefs();
mInflater = LayoutInflater.from(mContext.getApplicationContext());
mIconPulser = new IconPulser(mContext);
- final Resources res = mContext.getResources();
- mSubheadWarningColor = res.getColor(R.color.system_warning_color);
- mSubheadColor = res.getColor(R.color.qs_subhead);
+ mSubheadWarningColor = context.getColor(R.color.system_warning_color);
+ mSubheadColor = context.getColor(R.color.qs_subhead);
mInterpolator = AnimationUtils.loadInterpolator(mContext,
com.android.internal.R.interpolator.fast_out_slow_in);
- mCountdownConditionSupported = NotificationManager.from(mContext)
- .isSystemConditionProviderEnabled(ZenModeConfig.COUNTDOWN_PATH);
- final int countdownDelta = mCountdownConditionSupported ? 1 : 0;
- mFirstConditionIndex = COUNTDOWN_CONDITION_INDEX + countdownDelta;
- final int minConditions = 1 /*forever*/ + countdownDelta;
- mMaxConditions = MathUtils.constrain(res.getInteger(R.integer.zen_mode_max_conditions),
- minConditions, 100);
- mMaxOptionalConditions = mMaxConditions - minConditions;
mForeverId = Condition.newId(mContext).appendPath("forever").build();
if (DEBUG) Log.d(mTag, "new ZenModePanel");
}
@@ -149,26 +141,45 @@ public class ZenModePanel extends LinearLayout {
pw.print(" mExpanded="); pw.println(mExpanded);
pw.print(" mSessionZen="); pw.println(mSessionZen);
pw.print(" mAttachedZen="); pw.println(mAttachedZen);
+ pw.print(" mEmbedded="); pw.println(mEmbedded);
mTransitionHelper.dump(fd, pw, args);
}
+ public void setEmbedded(boolean embedded) {
+ if (mEmbedded == embedded) return;
+ mEmbedded = embedded;
+ mZenButtonsContainer.setLayoutTransition(mEmbedded ? null : newLayoutTransition(null));
+ setLayoutTransition(mEmbedded ? null : newLayoutTransition(null));
+ if (mEmbedded) {
+ mZenButtonsContainer.setBackground(null);
+ } else {
+ mZenButtonsContainer.setBackgroundResource(R.drawable.qs_background_secondary);
+ }
+ mZenButtons.getChildAt(3).setVisibility(mEmbedded ? GONE : VISIBLE);
+ mZenEmbeddedDivider.setVisibility(mEmbedded ? VISIBLE : GONE);
+ setExpanded(mEmbedded);
+ updateWidgets();
+ }
+
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mZenButtons = (SegmentedButtons) findViewById(R.id.zen_buttons);
- mZenButtons.addButton(R.string.interruption_level_none, R.drawable.ic_zen_none,
+ mZenButtons.addButton(R.string.interruption_level_none_twoline,
Global.ZEN_MODE_NO_INTERRUPTIONS);
- mZenButtons.addButton(R.string.interruption_level_priority, R.drawable.ic_zen_important,
+ mZenButtons.addButton(R.string.interruption_level_alarms_twoline,
+ Global.ZEN_MODE_ALARMS);
+ mZenButtons.addButton(R.string.interruption_level_priority_twoline,
Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
- mZenButtons.addButton(R.string.interruption_level_all, R.drawable.ic_zen_all,
- Global.ZEN_MODE_OFF);
+ mZenButtons.addButton(R.string.interruption_level_all, Global.ZEN_MODE_OFF);
mZenButtons.setCallback(mZenButtonsCallback);
- final ViewGroup zenButtonsContainer = (ViewGroup) findViewById(R.id.zen_buttons_container);
- zenButtonsContainer.setLayoutTransition(newLayoutTransition(null));
+ mZenButtonsContainer = (ViewGroup) findViewById(R.id.zen_buttons_container);
+ mZenButtonsContainer.setLayoutTransition(newLayoutTransition(null));
mZenSubhead = findViewById(R.id.zen_subhead);
+ mZenEmbeddedDivider = findViewById(R.id.zen_embedded_divider);
mZenSubheadCollapsed = (TextView) findViewById(R.id.zen_subhead_collapsed);
mZenSubheadCollapsed.setOnClickListener(new View.OnClickListener() {
@@ -192,9 +203,6 @@ 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));
}
@@ -234,7 +242,9 @@ public class ZenModePanel extends LinearLayout {
mAttachedZen = -1;
mSessionZen = -1;
setSessionExitCondition(null);
- setExpanded(false);
+ if (!mEmbedded) {
+ setExpanded(false);
+ }
setRequestingConditions(false);
mTransitionHelper.clear();
}
@@ -266,6 +276,7 @@ public class ZenModePanel extends LinearLayout {
private void setExpanded(boolean expanded) {
if (expanded == mExpanded) return;
+ if (DEBUG) Log.d(mTag, "setExpanded " + expanded);
mExpanded = expanded;
if (mExpanded) {
ensureSelection();
@@ -306,6 +317,16 @@ public class ZenModePanel extends LinearLayout {
public void init(ZenModeController controller) {
mController = controller;
+ mCountdownConditionSupported = mController.isCountdownConditionSupported();
+ final int countdownDelta = mCountdownConditionSupported ? 1 : 0;
+ mFirstConditionIndex = COUNTDOWN_CONDITION_INDEX + countdownDelta;
+ final int minConditions = 1 /*forever*/ + countdownDelta;
+ mMaxConditions = MathUtils.constrain(mContext.getResources()
+ .getInteger(R.integer.zen_mode_max_conditions), minConditions, 100);
+ mMaxOptionalConditions = mMaxConditions - minConditions;
+ for (int i = 0; i < mMaxConditions; i++) {
+ mZenConditions.addView(mInflater.inflate(R.layout.zen_mode_condition, this, false));
+ }
setExitCondition(mController.getExitCondition());
refreshExitConditionText();
mSessionZen = getSelectedZen(-1);
@@ -339,6 +360,10 @@ public class ZenModePanel extends LinearLayout {
return condition == null ? null : condition.copy();
}
+ public String getExitConditionText() {
+ return mExitConditionText;
+ }
+
private void refreshExitConditionText() {
if (mExitCondition == null) {
mExitConditionText = foreverSummary();
@@ -363,7 +388,7 @@ public class ZenModePanel extends LinearLayout {
private void handleUpdateZen(int zen) {
if (mSessionZen != -1 && mSessionZen != zen) {
- setExpanded(zen != Global.ZEN_MODE_OFF);
+ setExpanded(mEmbedded || zen != Global.ZEN_MODE_OFF);
mSessionZen = zen;
}
mZenButtons.setSelectedValue(zen);
@@ -405,11 +430,11 @@ public class ZenModePanel extends LinearLayout {
final boolean expanded = !mHidden && mExpanded;
mZenButtons.setVisibility(mHidden ? GONE : VISIBLE);
- mZenSubhead.setVisibility(!mHidden && !zenOff ? VISIBLE : GONE);
+ mZenSubhead.setVisibility(!mHidden && !zenOff && !mEmbedded ? VISIBLE : GONE);
mZenSubheadExpanded.setVisibility(expanded ? VISIBLE : GONE);
mZenSubheadCollapsed.setVisibility(!expanded ? VISIBLE : GONE);
mMoreSettings.setVisibility(zenImportant && expanded ? VISIBLE : GONE);
- mZenConditions.setVisibility(!zenOff && expanded ? VISIBLE : GONE);
+ mZenConditions.setVisibility(mEmbedded || !zenOff && expanded ? VISIBLE : GONE);
if (zenNone) {
mZenSubheadExpanded.setText(R.string.zen_no_interruptions_with_warning);
@@ -696,7 +721,10 @@ public class ZenModePanel extends LinearLayout {
case Global.ZEN_MODE_NO_INTERRUPTIONS:
modeText = mContext.getString(R.string.zen_no_interruptions);
break;
- default:
+ case Global.ZEN_MODE_ALARMS:
+ modeText = mContext.getString(R.string.zen_alarms);
+ break;
+ default:
return;
}
announceForAccessibility(mContext.getString(R.string.zen_mode_and_condition, modeText,