diff options
Diffstat (limited to 'packages/SystemUI/src')
182 files changed, 15041 insertions, 13364 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java index 7bdbd0a..3fbc76b 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); @@ -214,33 +225,40 @@ public class BatteryMeterView extends View implements DemoMode, mSubpixelSmoothingRight = context.getResources().getFraction( R.fraction.battery_subpixel_smoothing_right, 1, 1); - mFramePaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mFramePaint = new Paint(); mFramePaint.setColor(frameColor); mFramePaint.setDither(true); mFramePaint.setStrokeWidth(0); mFramePaint.setStyle(Paint.Style.FILL_AND_STROKE); - mBatteryPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mBatteryPaint = new Paint(); mBatteryPaint.setDither(true); mBatteryPaint.setStrokeWidth(0); mBatteryPaint.setStyle(Paint.Style.FILL_AND_STROKE); - mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mTextPaint = new Paint(); Typeface font = Typeface.create("sans-serif-condensed", Typeface.BOLD); mTextPaint.setTypeface(font); mTextPaint.setTextAlign(Paint.Align.CENTER); - mWarningTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mWarningTextPaint = new Paint(); mWarningTextPaint.setColor(mColors[1]); font = Typeface.create("sans-serif", Typeface.BOLD); 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 = new Paint(); + 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(fillColor); + mChargeColor = fillColor; + invalidate(); + } + + private int getBackgroundColor(float darkIntensity) { + return getColorForDarkIntensity( + darkIntensity, mLightModeBackgroundColor, mDarkModeBackgroundColor); + } + + private int getFillColor(float darkIntensity) { + return getColorForDarkIntensity( + darkIntensity, mLightModeFillColor, mDarkModeFillColor); + } + + private int getColorForDarkIntensity(float darkIntensity, int lightColor, int darkColor) { + return (int) ArgbEvaluator.getInstance().evaluate(darkIntensity, lightColor, darkColor); + } + @Override public void draw(Canvas c) { BatteryTracker tracker = mDemoMode ? mDemoTracker : mTracker; diff --git a/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags b/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags index d2ce94b..a584cf6 100644 --- a/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags +++ b/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags @@ -5,7 +5,7 @@ option java_package com.android.systemui; # --------------------------- # PhoneStatusBar.java # --------------------------- -36000 sysui_statusbar_touch (type|1),(x|1),(y|1),(enabled|1) +36000 sysui_statusbar_touch (type|1),(x|1),(y|1),(disable1|1),(disable2|1) 36001 sysui_heads_up_status (key|3),(visible|1) 36002 sysui_fullscreen_notification (key|3) 36003 sysui_heads_up_escalation (key|3) diff --git a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java index d42ac61..fece07f 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()); } } @@ -323,6 +322,10 @@ public class ExpandHelper implements Gefingerpoken { isInside(mScrollAdapter.getHostView(), x, y) && mScrollAdapter.isScrolledToTop(); mResizedView = findView(x, y); + if (mResizedView != null && !mCallback.canChildBeExpanded(mResizedView)) { + mResizedView = null; + mWatchingForPull = false; + } mInitialTouchY = ev.getY(); break; @@ -387,7 +390,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/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java new file mode 100644 index 0000000..29d2a01 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/Prefs.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui; + +import android.annotation.StringDef; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Map; + +public final class Prefs { + private Prefs() {} // no instantation + + @Retention(RetentionPolicy.SOURCE) + @StringDef({ + Key.SEARCH_APP_WIDGET_ID, + Key.DEBUG_MODE_ENABLED, + Key.HOTSPOT_TILE_LAST_USED, + Key.COLOR_INVERSION_TILE_LAST_USED, + Key.DND_TILE_VISIBLE, + Key.DND_TILE_COMBINED_ICON, + Key.DND_CONFIRMED_PRIORITY_INTRODUCTION, + Key.DND_CONFIRMED_SILENCE_INTRODUCTION, + Key.DND_FAVORITE_BUCKET_INDEX, + Key.DND_NONE_SELECTED, + Key.DND_FAVORITE_ZEN, + }) + public @interface Key { + String SEARCH_APP_WIDGET_ID = "searchAppWidgetId"; + String DEBUG_MODE_ENABLED = "debugModeEnabled"; + String HOTSPOT_TILE_LAST_USED = "HotspotTileLastUsed"; + String COLOR_INVERSION_TILE_LAST_USED = "ColorInversionTileLastUsed"; + String DND_TILE_VISIBLE = "DndTileVisible"; + String DND_TILE_COMBINED_ICON = "DndTileCombinedIcon"; + String DND_CONFIRMED_PRIORITY_INTRODUCTION = "DndConfirmedPriorityIntroduction"; + String DND_CONFIRMED_SILENCE_INTRODUCTION = "DndConfirmedSilenceIntroduction"; + String DND_FAVORITE_BUCKET_INDEX = "DndCountdownMinuteIndex"; + String DND_NONE_SELECTED = "DndNoneSelected"; + String DND_FAVORITE_ZEN = "DndFavoriteZen"; + } + + public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) { + return get(context).getBoolean(key, defaultValue); + } + + public static void putBoolean(Context context, @Key String key, boolean value) { + get(context).edit().putBoolean(key, value).apply(); + } + + public static int getInt(Context context, @Key String key, int defaultValue) { + return get(context).getInt(key, defaultValue); + } + + public static void putInt(Context context, @Key String key, int value) { + get(context).edit().putInt(key, value).apply(); + } + + public static long getLong(Context context, @Key String key, long defaultValue) { + return get(context).getLong(key, defaultValue); + } + + public static void putLong(Context context, @Key String key, long value) { + get(context).edit().putLong(key, value).apply(); + } + + public static Map<String, ?> getAll(Context context) { + return get(context).getAll(); + } + + public static void remove(Context context, @Key String key) { + get(context).edit().remove(key).apply(); + } + + public static void registerListener(Context context, + OnSharedPreferenceChangeListener listener) { + get(context).registerOnSharedPreferenceChangeListener(listener); + } + + public static void unregisterListener(Context context, + OnSharedPreferenceChangeListener listener) { + get(context).unregisterOnSharedPreferenceChangeListener(listener); + } + + private static SharedPreferences get(Context context) { + return context.getSharedPreferences(context.getPackageName(), Context.MODE_PRIVATE); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/SearchPanelCircleView.java b/packages/SystemUI/src/com/android/systemui/SearchPanelCircleView.java deleted file mode 100644 index d8fb6da..0000000 --- a/packages/SystemUI/src/com/android/systemui/SearchPanelCircleView.java +++ /dev/null @@ -1,592 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.PropertyValuesHolder; -import android.animation.ValueAnimator; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Outline; -import android.graphics.Paint; -import android.graphics.Rect; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewOutlineProvider; -import android.view.animation.AnimationUtils; -import android.view.animation.Interpolator; -import android.view.animation.LinearInterpolator; -import android.widget.FrameLayout; -import android.widget.ImageView; -import com.android.systemui.statusbar.phone.PhoneStatusBar; - -import java.util.ArrayList; - -public class SearchPanelCircleView extends FrameLayout { - - private final int mCircleMinSize; - private final int mBaseMargin; - private final int mStaticOffset; - private final Paint mBackgroundPaint = new Paint(); - private final Paint mRipplePaint = new Paint(); - private final Rect mCircleRect = new Rect(); - private final Rect mStaticRect = new Rect(); - private final Interpolator mFastOutSlowInInterpolator; - private final Interpolator mAppearInterpolator; - private final Interpolator mDisappearInterpolator; - - private boolean mClipToOutline; - private final int mMaxElevation; - private boolean mAnimatingOut; - private float mOutlineAlpha; - private float mOffset; - private float mCircleSize; - private boolean mHorizontal; - private boolean mCircleHidden; - private ImageView mLogo; - private boolean mDraggedFarEnough; - private boolean mOffsetAnimatingIn; - private float mCircleAnimationEndValue; - private ArrayList<Ripple> mRipples = new ArrayList<Ripple>(); - - private ValueAnimator mOffsetAnimator; - private ValueAnimator mCircleAnimator; - private ValueAnimator mFadeOutAnimator; - private ValueAnimator.AnimatorUpdateListener mCircleUpdateListener - = new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - applyCircleSize((float) animation.getAnimatedValue()); - updateElevation(); - } - }; - private AnimatorListenerAdapter mClearAnimatorListener = new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mCircleAnimator = null; - } - }; - private ValueAnimator.AnimatorUpdateListener mOffsetUpdateListener - = new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - setOffset((float) animation.getAnimatedValue()); - } - }; - - - public SearchPanelCircleView(Context context) { - this(context, null); - } - - public SearchPanelCircleView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public SearchPanelCircleView(Context context, AttributeSet attrs, int defStyleAttr) { - this(context, attrs, defStyleAttr, 0); - } - - public SearchPanelCircleView(Context context, AttributeSet attrs, int defStyleAttr, - int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - setOutlineProvider(new ViewOutlineProvider() { - @Override - public void getOutline(View view, Outline outline) { - if (mCircleSize > 0.0f) { - outline.setOval(mCircleRect); - } else { - outline.setEmpty(); - } - outline.setAlpha(mOutlineAlpha); - } - }); - setWillNotDraw(false); - mCircleMinSize = context.getResources().getDimensionPixelSize( - R.dimen.search_panel_circle_size); - mBaseMargin = context.getResources().getDimensionPixelSize( - R.dimen.search_panel_circle_base_margin); - mStaticOffset = context.getResources().getDimensionPixelSize( - R.dimen.search_panel_circle_travel_distance); - mMaxElevation = context.getResources().getDimensionPixelSize( - R.dimen.search_panel_circle_elevation); - mAppearInterpolator = AnimationUtils.loadInterpolator(mContext, - android.R.interpolator.linear_out_slow_in); - mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(mContext, - android.R.interpolator.fast_out_slow_in); - mDisappearInterpolator = AnimationUtils.loadInterpolator(mContext, - android.R.interpolator.fast_out_linear_in); - mBackgroundPaint.setAntiAlias(true); - mBackgroundPaint.setColor(getResources().getColor(R.color.search_panel_circle_color)); - mRipplePaint.setColor(getResources().getColor(R.color.search_panel_ripple_color)); - mRipplePaint.setAntiAlias(true); - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - drawBackground(canvas); - drawRipples(canvas); - } - - private void drawRipples(Canvas canvas) { - for (int i = 0; i < mRipples.size(); i++) { - Ripple ripple = mRipples.get(i); - ripple.draw(canvas); - } - } - - private void drawBackground(Canvas canvas) { - canvas.drawCircle(mCircleRect.centerX(), mCircleRect.centerY(), mCircleSize / 2, - mBackgroundPaint); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mLogo = (ImageView) findViewById(R.id.search_logo); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - mLogo.layout(0, 0, mLogo.getMeasuredWidth(), mLogo.getMeasuredHeight()); - if (changed) { - updateCircleRect(mStaticRect, mStaticOffset, true); - } - } - - public void setCircleSize(float circleSize) { - setCircleSize(circleSize, false, null, 0, null); - } - - public void setCircleSize(float circleSize, boolean animated, final Runnable endRunnable, - int startDelay, Interpolator interpolator) { - boolean isAnimating = mCircleAnimator != null; - boolean animationPending = isAnimating && !mCircleAnimator.isRunning(); - boolean animatingOut = isAnimating && mCircleAnimationEndValue == 0; - if (animated || animationPending || animatingOut) { - if (isAnimating) { - if (circleSize == mCircleAnimationEndValue) { - return; - } - mCircleAnimator.cancel(); - } - mCircleAnimator = ValueAnimator.ofFloat(mCircleSize, circleSize); - mCircleAnimator.addUpdateListener(mCircleUpdateListener); - mCircleAnimator.addListener(mClearAnimatorListener); - mCircleAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (endRunnable != null) { - endRunnable.run(); - } - } - }); - Interpolator desiredInterpolator = interpolator != null ? interpolator - : circleSize == 0 ? mDisappearInterpolator : mAppearInterpolator; - mCircleAnimator.setInterpolator(desiredInterpolator); - mCircleAnimator.setDuration(300); - mCircleAnimator.setStartDelay(startDelay); - mCircleAnimator.start(); - mCircleAnimationEndValue = circleSize; - } else { - if (isAnimating) { - float diff = circleSize - mCircleAnimationEndValue; - PropertyValuesHolder[] values = mCircleAnimator.getValues(); - values[0].setFloatValues(diff, circleSize); - mCircleAnimator.setCurrentPlayTime(mCircleAnimator.getCurrentPlayTime()); - mCircleAnimationEndValue = circleSize; - } else { - applyCircleSize(circleSize); - updateElevation(); - } - } - } - - private void applyCircleSize(float circleSize) { - mCircleSize = circleSize; - updateLayout(); - } - - private void updateElevation() { - float t = (mStaticOffset - mOffset) / (float) mStaticOffset; - t = 1.0f - Math.max(t, 0.0f); - float offset = t * mMaxElevation; - setElevation(offset); - } - - /** - * Sets the offset to the edge of the screen. By default this not not animated. - * - * @param offset The offset to apply. - */ - public void setOffset(float offset) { - setOffset(offset, false, 0, null, null); - } - - /** - * Sets the offset to the edge of the screen. - * - * @param offset The offset to apply. - * @param animate Whether an animation should be performed. - * @param startDelay The desired start delay if animated. - * @param interpolator The desired interpolator if animated. If null, - * a default interpolator will be taken designed for appearing or - * disappearing. - * @param endRunnable The end runnable which should be executed when the animation is finished. - */ - private void setOffset(float offset, boolean animate, int startDelay, - Interpolator interpolator, final Runnable endRunnable) { - if (!animate) { - mOffset = offset; - updateLayout(); - if (endRunnable != null) { - endRunnable.run(); - } - } else { - if (mOffsetAnimator != null) { - mOffsetAnimator.removeAllListeners(); - mOffsetAnimator.cancel(); - } - mOffsetAnimator = ValueAnimator.ofFloat(mOffset, offset); - mOffsetAnimator.addUpdateListener(mOffsetUpdateListener); - mOffsetAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mOffsetAnimator = null; - if (endRunnable != null) { - endRunnable.run(); - } - } - }); - Interpolator desiredInterpolator = interpolator != null ? - interpolator : offset == 0 ? mDisappearInterpolator : mAppearInterpolator; - mOffsetAnimator.setInterpolator(desiredInterpolator); - mOffsetAnimator.setStartDelay(startDelay); - mOffsetAnimator.setDuration(300); - mOffsetAnimator.start(); - mOffsetAnimatingIn = offset != 0; - } - } - - private void updateLayout() { - updateCircleRect(); - updateLogo(); - invalidateOutline(); - invalidate(); - updateClipping(); - } - - private void updateClipping() { - boolean clip = mCircleSize < mCircleMinSize || !mRipples.isEmpty(); - if (clip != mClipToOutline) { - setClipToOutline(clip); - mClipToOutline = clip; - } - } - - private void updateLogo() { - boolean exitAnimationRunning = mFadeOutAnimator != null; - Rect rect = exitAnimationRunning ? mCircleRect : mStaticRect; - float translationX = (rect.left + rect.right) / 2.0f - mLogo.getWidth() / 2.0f; - float translationY = (rect.top + rect.bottom) / 2.0f - mLogo.getHeight() / 2.0f; - float t = (mStaticOffset - mOffset) / (float) mStaticOffset; - if (!exitAnimationRunning) { - if (mHorizontal) { - translationX += t * mStaticOffset * 0.3f; - } else { - translationY += t * mStaticOffset * 0.3f; - } - float alpha = 1.0f-t; - alpha = Math.max((alpha - 0.5f) * 2.0f, 0); - mLogo.setAlpha(alpha); - } else { - translationY += (mOffset - mStaticOffset) / 2; - } - mLogo.setTranslationX(translationX); - mLogo.setTranslationY(translationY); - } - - private void updateCircleRect() { - updateCircleRect(mCircleRect, mOffset, false); - } - - private void updateCircleRect(Rect rect, float offset, boolean useStaticSize) { - int left, top; - float circleSize = useStaticSize ? mCircleMinSize : mCircleSize; - if (mHorizontal) { - left = (int) (getWidth() - circleSize / 2 - mBaseMargin - offset); - top = (int) ((getHeight() - circleSize) / 2); - } else { - left = (int) (getWidth() - circleSize) / 2; - top = (int) (getHeight() - circleSize / 2 - mBaseMargin - offset); - } - rect.set(left, top, (int) (left + circleSize), (int) (top + circleSize)); - } - - public void setHorizontal(boolean horizontal) { - mHorizontal = horizontal; - updateCircleRect(mStaticRect, mStaticOffset, true); - updateLayout(); - } - - public void setDragDistance(float distance) { - if (!mAnimatingOut && (!mCircleHidden || mDraggedFarEnough)) { - float circleSize = mCircleMinSize + rubberband(distance); - setCircleSize(circleSize); - } - - } - - private float rubberband(float diff) { - return (float) Math.pow(Math.abs(diff), 0.6f); - } - - public void startAbortAnimation(Runnable endRunnable) { - if (mAnimatingOut) { - if (endRunnable != null) { - endRunnable.run(); - } - return; - } - setCircleSize(0, true, null, 0, null); - setOffset(0, true, 0, null, endRunnable); - mCircleHidden = true; - } - - public void startEnterAnimation() { - if (mAnimatingOut) { - return; - } - applyCircleSize(0); - setOffset(0); - setCircleSize(mCircleMinSize, true, null, 50, null); - setOffset(mStaticOffset, true, 50, null, null); - mCircleHidden = false; - } - - - public void startExitAnimation(final Runnable endRunnable) { - if (!mHorizontal) { - float offset = getHeight() / 2.0f; - setOffset(offset - mBaseMargin, true, 50, mFastOutSlowInInterpolator, null); - float xMax = getWidth() / 2; - float yMax = getHeight() / 2; - float maxRadius = (float) Math.ceil(Math.hypot(xMax, yMax) * 2); - setCircleSize(maxRadius, true, null, 50, mFastOutSlowInInterpolator); - performExitFadeOutAnimation(50, 300, endRunnable); - } else { - - // when in landscape, we don't wan't the animation as it interferes with the general - // rotation animation to the homescreen. - endRunnable.run(); - } - } - - private void performExitFadeOutAnimation(int startDelay, int duration, - final Runnable endRunnable) { - mFadeOutAnimator = ValueAnimator.ofFloat(mBackgroundPaint.getAlpha() / 255.0f, 0.0f); - - // Linear since we are animating multiple values - mFadeOutAnimator.setInterpolator(new LinearInterpolator()); - mFadeOutAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float animatedFraction = animation.getAnimatedFraction(); - float logoValue = animatedFraction > 0.5f ? 1.0f : animatedFraction / 0.5f; - logoValue = PhoneStatusBar.ALPHA_OUT.getInterpolation(1.0f - logoValue); - float backgroundValue = animatedFraction < 0.2f ? 0.0f : - PhoneStatusBar.ALPHA_OUT.getInterpolation((animatedFraction - 0.2f) / 0.8f); - backgroundValue = 1.0f - backgroundValue; - mBackgroundPaint.setAlpha((int) (backgroundValue * 255)); - mOutlineAlpha = backgroundValue; - mLogo.setAlpha(logoValue); - invalidateOutline(); - invalidate(); - } - }); - mFadeOutAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (endRunnable != null) { - endRunnable.run(); - } - mLogo.setAlpha(1.0f); - mBackgroundPaint.setAlpha(255); - mOutlineAlpha = 1.0f; - mFadeOutAnimator = null; - } - }); - mFadeOutAnimator.setStartDelay(startDelay); - mFadeOutAnimator.setDuration(duration); - mFadeOutAnimator.start(); - } - - public void setDraggedFarEnough(boolean farEnough) { - if (farEnough != mDraggedFarEnough) { - if (farEnough) { - if (mCircleHidden) { - startEnterAnimation(); - } - if (mOffsetAnimator == null) { - addRipple(); - } else { - postDelayed(new Runnable() { - @Override - public void run() { - addRipple(); - } - }, 100); - } - } else { - startAbortAnimation(null); - } - mDraggedFarEnough = farEnough; - } - - } - - private void addRipple() { - if (mRipples.size() > 1) { - // we only want 2 ripples at the time - return; - } - float xInterpolation, yInterpolation; - if (mHorizontal) { - xInterpolation = 0.75f; - yInterpolation = 0.5f; - } else { - xInterpolation = 0.5f; - yInterpolation = 0.75f; - } - float circleCenterX = mStaticRect.left * (1.0f - xInterpolation) - + mStaticRect.right * xInterpolation; - float circleCenterY = mStaticRect.top * (1.0f - yInterpolation) - + mStaticRect.bottom * yInterpolation; - float radius = Math.max(mCircleSize, mCircleMinSize * 1.25f) * 0.75f; - Ripple ripple = new Ripple(circleCenterX, circleCenterY, radius); - ripple.start(); - } - - public void reset() { - mDraggedFarEnough = false; - mAnimatingOut = false; - mCircleHidden = true; - mClipToOutline = false; - if (mFadeOutAnimator != null) { - mFadeOutAnimator.cancel(); - } - mBackgroundPaint.setAlpha(255); - mOutlineAlpha = 1.0f; - } - - /** - * Check if an animation is currently running - * - * @param enterAnimation Is the animating queried the enter animation. - */ - public boolean isAnimationRunning(boolean enterAnimation) { - return mOffsetAnimator != null && (enterAnimation == mOffsetAnimatingIn); - } - - public void performOnAnimationFinished(final Runnable runnable) { - if (mOffsetAnimator != null) { - mOffsetAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (runnable != null) { - runnable.run(); - } - } - }); - } else { - if (runnable != null) { - runnable.run(); - } - } - } - - public void setAnimatingOut(boolean animatingOut) { - mAnimatingOut = animatingOut; - } - - /** - * @return Whether the circle is currently launching to the search activity or aborting the - * interaction - */ - public boolean isAnimatingOut() { - return mAnimatingOut; - } - - @Override - public boolean hasOverlappingRendering() { - // not really true but it's ok during an animation, as it's never permanent - return false; - } - - private class Ripple { - float x; - float y; - float radius; - float endRadius; - float alpha; - - Ripple(float x, float y, float endRadius) { - this.x = x; - this.y = y; - this.endRadius = endRadius; - } - - void start() { - ValueAnimator animator = ValueAnimator.ofFloat(0.0f, 1.0f); - - // Linear since we are animating multiple values - animator.setInterpolator(new LinearInterpolator()); - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - alpha = 1.0f - animation.getAnimatedFraction(); - alpha = mDisappearInterpolator.getInterpolation(alpha); - radius = mAppearInterpolator.getInterpolation(animation.getAnimatedFraction()); - radius *= endRadius; - invalidate(); - } - }); - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mRipples.remove(Ripple.this); - updateClipping(); - } - - public void onAnimationStart(Animator animation) { - mRipples.add(Ripple.this); - updateClipping(); - } - }); - animator.setDuration(400); - animator.start(); - } - - public void draw(Canvas canvas) { - mRipplePaint.setAlpha((int) (alpha * 255)); - canvas.drawCircle(x, y, radius, mRipplePaint); - } - } - -} diff --git a/packages/SystemUI/src/com/android/systemui/SearchPanelView.java b/packages/SystemUI/src/com/android/systemui/SearchPanelView.java deleted file mode 100644 index 445b499..0000000 --- a/packages/SystemUI/src/com/android/systemui/SearchPanelView.java +++ /dev/null @@ -1,345 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui; - -import android.app.ActivityOptions; -import android.app.SearchManager; -import android.content.ActivityNotFoundException; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.media.AudioAttributes; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.UserHandle; -import android.os.Vibrator; -import android.provider.Settings; -import android.util.AttributeSet; -import android.util.Log; -import android.view.MotionEvent; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.ImageView; - -import com.android.systemui.statusbar.BaseStatusBar; -import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.StatusBarPanel; -import com.android.systemui.statusbar.phone.PhoneStatusBar; - -public class SearchPanelView extends FrameLayout implements StatusBarPanel { - - private static final String TAG = "SearchPanelView"; - private static final String ASSIST_ICON_METADATA_NAME = - "com.android.systemui.action_assist_icon"; - - private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder() - .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) - .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) - .build(); - - private final Context mContext; - private BaseStatusBar mBar; - - private SearchPanelCircleView mCircle; - private ImageView mLogo; - private View mScrim; - - private int mThreshold; - private boolean mHorizontal; - - private boolean mLaunching; - private boolean mDragging; - private boolean mDraggedFarEnough; - private float mStartTouch; - private float mStartDrag; - private boolean mLaunchPending; - - public SearchPanelView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public SearchPanelView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - mContext = context; - mThreshold = context.getResources().getDimensionPixelSize(R.dimen.search_panel_threshold); - } - - private void startAssistActivity() { - if (!mBar.isDeviceProvisioned()) return; - - // Close Recent Apps if needed - mBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL); - - final Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE)) - .getAssistIntent(mContext, true, UserHandle.USER_CURRENT); - if (intent == null) return; - - try { - final ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, - R.anim.search_launch_enter, R.anim.search_launch_exit); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - AsyncTask.execute(new Runnable() { - @Override - public void run() { - mContext.startActivityAsUser(intent, opts.toBundle(), - new UserHandle(UserHandle.USER_CURRENT)); - } - }); - } catch (ActivityNotFoundException e) { - Log.w(TAG, "Activity not found for " + intent.getAction()); - } - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - mCircle = (SearchPanelCircleView) findViewById(R.id.search_panel_circle); - mLogo = (ImageView) findViewById(R.id.search_logo); - mScrim = findViewById(R.id.search_panel_scrim); - } - - private void maybeSwapSearchIcon() { - Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE)) - .getAssistIntent(mContext, false, UserHandle.USER_CURRENT); - if (intent != null) { - ComponentName component = intent.getComponent(); - replaceDrawable(mLogo, component, ASSIST_ICON_METADATA_NAME); - } else { - mLogo.setImageDrawable(null); - } - } - - public void replaceDrawable(ImageView v, ComponentName component, String name) { - if (component != null) { - try { - PackageManager packageManager = mContext.getPackageManager(); - // Look for the search icon specified in the activity meta-data - Bundle metaData = packageManager.getActivityInfo( - component, PackageManager.GET_META_DATA).metaData; - if (metaData != null) { - int iconResId = metaData.getInt(name); - if (iconResId != 0) { - Resources res = packageManager.getResourcesForActivity(component); - v.setImageDrawable(res.getDrawable(iconResId)); - return; - } - } - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "Failed to swap drawable; " - + component.flattenToShortString() + " not found", e); - } catch (Resources.NotFoundException nfe) { - Log.w(TAG, "Failed to swap drawable from " - + component.flattenToShortString(), nfe); - } - } - v.setImageDrawable(null); - } - - @Override - public boolean isInContentArea(int x, int y) { - return true; - } - - private void vibrate() { - Context context = getContext(); - if (Settings.System.getIntForUser(context.getContentResolver(), - Settings.System.HAPTIC_FEEDBACK_ENABLED, 1, UserHandle.USER_CURRENT) != 0) { - Resources res = context.getResources(); - Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); - vibrator.vibrate(res.getInteger(R.integer.config_search_panel_view_vibration_duration), - VIBRATION_ATTRIBUTES); - } - } - - public void show(final boolean show, boolean animate) { - if (show) { - maybeSwapSearchIcon(); - if (getVisibility() != View.VISIBLE) { - setVisibility(View.VISIBLE); - vibrate(); - if (animate) { - startEnterAnimation(); - } else { - mScrim.setAlpha(1f); - } - } - setFocusable(true); - setFocusableInTouchMode(true); - requestFocus(); - } else { - if (animate) { - startAbortAnimation(); - } else { - setVisibility(View.INVISIBLE); - } - } - } - - private void startEnterAnimation() { - mCircle.startEnterAnimation(); - mScrim.setAlpha(0f); - mScrim.animate() - .alpha(1f) - .setDuration(300) - .setStartDelay(50) - .setInterpolator(PhoneStatusBar.ALPHA_IN) - .start(); - - } - - private void startAbortAnimation() { - mCircle.startAbortAnimation(new Runnable() { - @Override - public void run() { - mCircle.setAnimatingOut(false); - setVisibility(View.INVISIBLE); - } - }); - mCircle.setAnimatingOut(true); - mScrim.animate() - .alpha(0f) - .setDuration(300) - .setStartDelay(0) - .setInterpolator(PhoneStatusBar.ALPHA_OUT); - } - - public void hide(boolean animate) { - if (mBar != null) { - // This will indirectly cause show(false, ...) to get called - mBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); - } else { - if (animate) { - startAbortAnimation(); - } else { - setVisibility(View.INVISIBLE); - } - } - } - - @Override - public boolean dispatchHoverEvent(MotionEvent event) { - // Ignore hover events outside of this panel bounds since such events - // generate spurious accessibility events with the panel content when - // tapping outside of it, thus confusing the user. - final int x = (int) event.getX(); - final int y = (int) event.getY(); - if (x >= 0 && x < getWidth() && y >= 0 && y < getHeight()) { - return super.dispatchHoverEvent(event); - } - return true; - } - - /** - * Whether the panel is showing, or, if it's animating, whether it will be - * when the animation is done. - */ - public boolean isShowing() { - return getVisibility() == View.VISIBLE && !mCircle.isAnimatingOut(); - } - - public void setBar(BaseStatusBar bar) { - mBar = bar; - } - - public boolean isAssistantAvailable() { - return ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE)) - .getAssistIntent(mContext, false, UserHandle.USER_CURRENT) != null; - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - if (mLaunching || mLaunchPending) { - return false; - } - int action = event.getActionMasked(); - switch (action) { - case MotionEvent.ACTION_DOWN: - mStartTouch = mHorizontal ? event.getX() : event.getY(); - mDragging = false; - mDraggedFarEnough = false; - mCircle.reset(); - break; - case MotionEvent.ACTION_MOVE: - float currentTouch = mHorizontal ? event.getX() : event.getY(); - if (getVisibility() == View.VISIBLE && !mDragging && - (!mCircle.isAnimationRunning(true /* enterAnimation */) - || Math.abs(mStartTouch - currentTouch) > mThreshold)) { - mStartDrag = currentTouch; - mDragging = true; - } - if (mDragging) { - float offset = Math.max(mStartDrag - currentTouch, 0.0f); - mCircle.setDragDistance(offset); - mDraggedFarEnough = Math.abs(mStartTouch - currentTouch) > mThreshold; - mCircle.setDraggedFarEnough(mDraggedFarEnough); - } - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - if (mDraggedFarEnough) { - if (mCircle.isAnimationRunning(true /* enterAnimation */)) { - mLaunchPending = true; - mCircle.setAnimatingOut(true); - mCircle.performOnAnimationFinished(new Runnable() { - @Override - public void run() { - startExitAnimation(); - } - }); - } else { - startExitAnimation(); - } - } else { - startAbortAnimation(); - } - break; - } - return true; - } - - private void startExitAnimation() { - mLaunchPending = false; - if (mLaunching || getVisibility() != View.VISIBLE) { - return; - } - mLaunching = true; - startAssistActivity(); - vibrate(); - mCircle.setAnimatingOut(true); - mCircle.startExitAnimation(new Runnable() { - @Override - public void run() { - mLaunching = false; - mCircle.setAnimatingOut(false); - setVisibility(View.INVISIBLE); - } - }); - mScrim.animate() - .alpha(0f) - .setDuration(300) - .setStartDelay(0) - .setInterpolator(PhoneStatusBar.ALPHA_OUT); - } - - public void setHorizontal(boolean horizontal) { - mHorizontal = horizontal; - mCircle.setHorizontal(horizontal); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index b3f90d7..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..d9f2324 --- /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.activeServiceSupportsAssist(); + } 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 fae0643..6479dc5 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; @@ -172,8 +171,13 @@ public class KeyguardViewMediator extends SystemUI { */ private static final String KEYGUARD_ANALYTICS_SETTING = "keyguard_analytics"; + /** + * How much faster we collapse the lockscreen when authenticating with fingerprint. + */ + private static final float FINGERPRINT_COLLAPSE_SPEEDUP_FACTOR = 1.3f; + /** The stream type that the lock sounds are tied to. */ - private int mMasterStreamType; + private int mUiSoundsStreamType; private AlarmManager mAlarmManager; private AudioManager mAudioManager; @@ -314,9 +318,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 +334,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 +441,13 @@ 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( + FINGERPRINT_COLLAPSE_SPEEDUP_FACTOR); } }; @@ -477,11 +475,6 @@ public class KeyguardViewMediator extends SystemUI { } @Override - public void onUserActivityTimeoutChanged() { - mStatusBarKeyguardViewManager.updateUserActivityTimeout(); - } - - @Override public void keyguardDonePending() { mKeyguardDonePending = true; mHideAnimationRun = true; @@ -505,6 +498,11 @@ public class KeyguardViewMediator extends SystemUI { } @Override + public void resetKeyguard() { + resetStateLocked(); + } + + @Override public void playTrustedSound() { KeyguardViewMediator.this.playTrustedSound(); } @@ -513,6 +511,11 @@ public class KeyguardViewMediator extends SystemUI { public boolean isInputRestricted() { return KeyguardViewMediator.this.isInputRestricted(); } + + @Override + public boolean isScreenOn() { + return mScreenOn; + } }; public void userActivity() { @@ -536,10 +539,11 @@ public class KeyguardViewMediator extends SystemUI { mUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext); mLockPatternUtils = new LockPatternUtils(mContext); - mLockPatternUtils.setCurrentUser(ActivityManager.getCurrentUser()); + KeyguardUpdateMonitor.setCurrentUser(ActivityManager.getCurrentUser()); // Assume keyguard is showing (unless it's disabled) until we know for sure... - setShowingLocked(!shouldWaitForProvisioning() && !mLockPatternUtils.isLockScreenDisabled()); + setShowingLocked(!shouldWaitForProvisioning() && !mLockPatternUtils.isLockScreenDisabled( + KeyguardUpdateMonitor.getCurrentUser())); mTrustManager.reportKeyguardShowingChanged(); mStatusBarKeyguardViewManager = new StatusBarKeyguardViewManager(mContext, @@ -597,23 +601,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 @@ -637,8 +624,10 @@ public class KeyguardViewMediator extends SystemUI { // Lock immediately based on setting if secure (user has a pin/pattern/password). // This also "locks" the device when not secure to provide easy access to the // camera while preventing unwanted input. + int currentUser = KeyguardUpdateMonitor.getCurrentUser(); final boolean lockImmediately = - mLockPatternUtils.getPowerButtonInstantlyLocks() || !mLockPatternUtils.isSecure(); + mLockPatternUtils.getPowerButtonInstantlyLocks(currentUser) + || !mLockPatternUtils.isSecure(currentUser); notifyScreenOffLocked(); @@ -662,7 +651,7 @@ public class KeyguardViewMediator extends SystemUI { doKeyguardLocked(null); } } - KeyguardUpdateMonitor.getInstance(mContext).dispatchScreenTurndOff(why); + KeyguardUpdateMonitor.getInstance(mContext).dispatchScreenTurnedOff(why); } private void doKeyguardLaterLocked() { @@ -684,7 +673,7 @@ public class KeyguardViewMediator extends SystemUI { // From DevicePolicyAdmin final long policyTimeout = mLockPatternUtils.getDevicePolicyManager() - .getMaximumTimeToLock(null, mLockPatternUtils.getCurrentUser()); + .getMaximumTimeToLock(null, KeyguardUpdateMonitor.getCurrentUser()); long timeout; if (policyTimeout > 0) { @@ -733,7 +722,8 @@ public class KeyguardViewMediator extends SystemUI { } private void maybeSendUserPresentBroadcast() { - if (mSystemReady && mLockPatternUtils.isLockScreenDisabled()) { + if (mSystemReady && mLockPatternUtils.isLockScreenDisabled( + KeyguardUpdateMonitor.getCurrentUser())) { // Lock screen is disabled because the user has set the preference to "None". // In this case, send out ACTION_USER_PRESENT here instead of in // handleKeyguardDone() @@ -747,7 +737,7 @@ public class KeyguardViewMediator extends SystemUI { */ public void onDreamingStarted() { synchronized (this) { - if (mScreenOn && mLockPatternUtils.isSecure()) { + if (mScreenOn && mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())) { doKeyguardLaterLocked(); } } @@ -892,6 +882,12 @@ public class KeyguardViewMediator extends SystemUI { */ private void handleSetOccluded(boolean isOccluded) { synchronized (KeyguardViewMediator.this) { + if (mHiding && isOccluded) { + // We're in the process of going away but WindowManager wants to show a + // SHOW_WHEN_LOCKED activity instead. + startKeyguardExitAnimation(0, 0); + } + if (mOccluded != isOccluded) { mOccluded = isOccluded; mStatusBarKeyguardViewManager.setOccluded(isOccluded); @@ -982,12 +978,13 @@ public class KeyguardViewMediator extends SystemUI { return; } - if (mLockPatternUtils.isLockScreenDisabled() && !lockedOrMissing) { + if (mLockPatternUtils.isLockScreenDisabled(KeyguardUpdateMonitor.getCurrentUser()) + && !lockedOrMissing) { if (DEBUG) Log.d(TAG, "doKeyguard: not showing because lockscreen is off"); return; } - if (mLockPatternUtils.checkVoldPassword()) { + if (mLockPatternUtils.checkVoldPassword(KeyguardUpdateMonitor.getCurrentUser())) { if (DEBUG) Log.d(TAG, "Not showing lock screen since just decrypted"); // Without this, settings is not enabled until the lock screen first appears setShowingLocked(false); @@ -1080,7 +1077,7 @@ public class KeyguardViewMediator extends SystemUI { } public boolean isSecure() { - return mLockPatternUtils.isSecure() + return mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser()) || KeyguardUpdateMonitor.getInstance(mContext).isSimPinSecure(); } @@ -1091,7 +1088,7 @@ public class KeyguardViewMediator extends SystemUI { * @param newUserId The id of the incoming user. */ public void setCurrentUser(int newUserId) { - mLockPatternUtils.setCurrentUser(newUserId); + KeyguardUpdateMonitor.setCurrentUser(newUserId); } private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @@ -1221,7 +1218,7 @@ public class KeyguardViewMediator extends SystemUI { private void sendUserPresentBroadcast() { synchronized (this) { if (mBootCompleted) { - final UserHandle currentUser = new UserHandle(mLockPatternUtils.getCurrentUser()); + final UserHandle currentUser = new UserHandle(KeyguardUpdateMonitor.getCurrentUser()); final UserManager um = (UserManager) mContext.getSystemService( Context.USER_SERVICE); List <UserInfo> userHandles = um.getProfiles(currentUser.getIdentifier()); @@ -1275,10 +1272,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*/); @@ -1333,6 +1330,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. @@ -1550,6 +1549,7 @@ public class KeyguardViewMediator extends SystemUI { try { callback.onSimSecureStateChanged(mUpdateMonitor.isSimPinSecure()); callback.onShowingStateChanged(mShowing); + callback.onInputRestrictedStateChanged(mInputRestricted); } catch (RemoteException e) { Slog.w(TAG, "Failed to call onShowingStateChanged or onSimSecureStateChanged", e); } 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..f59e864 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java @@ -18,12 +18,12 @@ package com.android.systemui.qs; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; -import android.content.res.Configuration; +import android.content.Intent; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.UserHandle; import android.util.Log; -import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; @@ -40,6 +40,8 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene protected static final String TAG = "QSFooter"; protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + private static final String ACTION_VPN_SETTINGS = "android.net.vpn.SETTINGS"; + private final View mRootView; private final TextView mFooterText; private final ImageView mFooterIcon; @@ -108,21 +110,15 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene } private void handleRefreshState() { - if (mSecurityController.hasDeviceOwner()) { + boolean hasDeviceOwner = mSecurityController.hasDeviceOwner(); + boolean hasVpn = mSecurityController.isVpnEnabled(); + + mIsVisible = (hasVpn || hasDeviceOwner); + mIsIconVisible = hasVpn; + if (hasDeviceOwner) { mFooterTextId = R.string.device_owned_footer; - mIsVisible = true; - mIsIconVisible = false; - } else if (mSecurityController.hasProfileOwner()) { - mFooterTextId = R.string.profile_owned_footer; - mIsVisible = true; - mIsIconVisible = false; - } else if (mSecurityController.isVpnEnabled()) { - mFooterTextId = R.string.vpn_footer; - mIsVisible = true; - mIsIconVisible = true; } else { - mIsVisible = false; - mIsIconVisible = false; + mFooterTextId = R.string.vpn_footer; } mMainHandler.post(mUpdateDisplayState); } @@ -130,108 +126,56 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene @Override public void onClick(DialogInterface dialog, int which) { if (which == DialogInterface.BUTTON_NEGATIVE) { - mSecurityController.disconnectFromVpn(); + final Intent settingsIntent = new Intent(ACTION_VPN_SETTINGS); + mContext.startActivityAsUser(settingsIntent, UserHandle.CURRENT); } } private void createDialog() { + boolean hasDeviceOwner = mSecurityController.hasDeviceOwner(); + boolean hasProfile = mSecurityController.hasProfileOwner(); + boolean hasVpn = mSecurityController.isVpnEnabled(); + mDialog = new SystemUIDialog(mContext); - mDialog.setTitle(getTitle()); - mDialog.setMessage(getMessage()); + mDialog.setTitle(getTitle(hasDeviceOwner, hasProfile)); + mDialog.setMessage(getMessage(hasDeviceOwner, hasProfile, hasVpn)); mDialog.setButton(DialogInterface.BUTTON_POSITIVE, getPositiveButton(), this); - if (mSecurityController.isVpnEnabled()) { + if (hasVpn) { mDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getNegativeButton(), this); } mDialog.show(); } private String getNegativeButton() { - if (mSecurityController.isLegacyVpn()) { - return mContext.getString(R.string.disconnect_vpn); - } else { - return mContext.getString(R.string.disable_vpn); - } + return mContext.getString(R.string.status_bar_settings_settings_button); } private String getPositiveButton() { return mContext.getString(R.string.quick_settings_done); } - private String getMessage() { - if (mSecurityController.hasDeviceOwner()) { - if (mSecurityController.hasProfileOwner()) { - if (mSecurityController.isVpnEnabled()) { - if (mSecurityController.isLegacyVpn()) { - return mContext.getString( - R.string.monitoring_description_legacy_vpn_device_and_profile_owned, - mSecurityController.getDeviceOwnerName(), - mSecurityController.getProfileOwnerName(), - mSecurityController.getLegacyVpnName()); - } else { - return mContext.getString( - R.string.monitoring_description_vpn_device_and_profile_owned, - mSecurityController.getDeviceOwnerName(), - mSecurityController.getProfileOwnerName(), - mSecurityController.getVpnApp()); - } - } else { - return mContext.getString( - R.string.monitoring_description_device_and_profile_owned, - mSecurityController.getDeviceOwnerName(), - mSecurityController.getProfileOwnerName()); - } + private String getMessage(boolean hasDeviceOwner, boolean hasProfile, boolean hasVpn) { + if (hasDeviceOwner) { + if (hasVpn) { + return mContext.getString(R.string.monitoring_description_vpn_device_owned, + mSecurityController.getDeviceOwnerName()); } else { - if (mSecurityController.isVpnEnabled()) { - if (mSecurityController.isLegacyVpn()) { - return mContext.getString( - R.string.monitoring_description_legacy_vpn_device_owned, - mSecurityController.getDeviceOwnerName(), - mSecurityController.getLegacyVpnName()); - } else { - return mContext.getString(R.string.monitoring_description_vpn_device_owned, - mSecurityController.getDeviceOwnerName(), - mSecurityController.getVpnApp()); - } - } else { - return mContext.getString(R.string.monitoring_description_device_owned, - mSecurityController.getDeviceOwnerName()); - } - } - } else if (mSecurityController.hasProfileOwner()) { - if (mSecurityController.isVpnEnabled()) { - if (mSecurityController.isLegacyVpn()) { - return mContext.getString( - R.string.monitoring_description_legacy_vpn_profile_owned, - mSecurityController.getProfileOwnerName(), - mSecurityController.getLegacyVpnName()); - } else { - return mContext.getString( - R.string.monitoring_description_vpn_profile_owned, - mSecurityController.getProfileOwnerName(), - mSecurityController.getVpnApp()); - } - } else { - return mContext.getString( - R.string.monitoring_description_profile_owned, - mSecurityController.getProfileOwnerName()); + return mContext.getString(R.string.monitoring_description_device_owned, + mSecurityController.getDeviceOwnerName()); } + } else if (hasProfile) { + return mContext.getString( + R.string.monitoring_description_vpn_profile_owned, + mSecurityController.getProfileOwnerName()); } else { - if (mSecurityController.isLegacyVpn()) { - return mContext.getString(R.string.monitoring_description_legacy_vpn, - mSecurityController.getLegacyVpnName()); - - } else { - return mContext.getString(R.string.monitoring_description_vpn, - mSecurityController.getVpnApp()); - } + return mContext.getString(R.string.monitoring_description_vpn); } } - private int getTitle() { - if (mSecurityController.hasDeviceOwner()) { + private int getTitle(boolean hasDeviceOwner, boolean hasProfile) { + if (hasDeviceOwner) { return R.string.monitoring_title_device_owned; - } - if (mSecurityController.hasProfileOwner()) { + } else if (hasProfile) { return R.string.monitoring_title_profile_owned; } return R.string.monitoring_title; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 4dacacf..b5c1ca8 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); } @@ -345,6 +338,7 @@ public class QSPanel extends ViewGroup { r.detailAdapter = r.tile.getDetailAdapter(); if (r.detailAdapter == null) return; } + r.tile.setDetailListening(show); int x = r.tileView.getLeft() + r.tileView.getWidth() / 2; int y = r.tileView.getTop() + r.tileView.getHeight() / 2; handleShowDetailImpl(r, show, x, y); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java index 1790a4e..b9574dc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java @@ -18,6 +18,7 @@ package com.android.systemui.qs; import android.content.Context; import android.content.Intent; +import android.graphics.drawable.Animatable; import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.Drawable; import android.os.Handler; @@ -148,6 +149,10 @@ public abstract class QSTile<TState extends State> implements Listenable { return mState; } + public void setDetailListening(boolean listening) { + // optional + } + // call only on tile worker looper private void handleSetCallback(Callback callback) { @@ -325,7 +330,7 @@ public abstract class QSTile<TState extends State> implements Listenable { public static class ResourceIcon extends Icon { private static final SparseArray<Icon> ICONS = new SparseArray<Icon>(); - private final int mResId; + protected final int mResId; private ResourceIcon(int resId) { mResId = resId; @@ -342,7 +347,11 @@ public abstract class QSTile<TState extends State> implements Listenable { @Override public Drawable getDrawable(Context context) { - return context.getDrawable(mResId); + Drawable d = context.getDrawable(mResId); + if (d instanceof Animatable) { + ((Animatable) d).start(); + } + return d; } @Override @@ -370,7 +379,7 @@ public abstract class QSTile<TState extends State> implements Listenable { @Override public Drawable getDrawable(Context context) { // workaround: get a clean state for every new AVD - final AnimatedVectorDrawable d = (AnimatedVectorDrawable) super.getDrawable(context) + final AnimatedVectorDrawable d = (AnimatedVectorDrawable) context.getDrawable(mResId) .getConstantState().newDrawable(); d.start(); if (mAllowAnimation) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java index 16ae6b4..af9d3a5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java @@ -38,6 +38,7 @@ import android.widget.TextView; import com.android.systemui.FontSizeUtils; import com.android.systemui.R; +import com.android.systemui.qs.QSTile.AnimationIcon; import com.android.systemui.qs.QSTile.State; import java.util.Objects; @@ -88,7 +89,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 +139,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 +158,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); @@ -315,8 +316,9 @@ public class QSTileView extends ViewGroup { iv.setImageDrawable(d); iv.setTag(R.id.qs_icon_tag, state.icon); if (d instanceof Animatable) { - if (!iv.isShown()) { - ((Animatable) d).stop(); // skip directly to end state + Animatable a = (Animatable) d; + if (state.icon instanceof AnimationIcon && !iv.isShown()) { + a.stop(); // skip directly to end state } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/UsageTracker.java b/packages/SystemUI/src/com/android/systemui/qs/UsageTracker.java index e60aa53..f36019b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/UsageTracker.java +++ b/packages/SystemUI/src/com/android/systemui/qs/UsageTracker.java @@ -23,6 +23,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; +import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.Listenable; @@ -32,14 +33,15 @@ public class UsageTracker implements Listenable { private final Context mContext; private final long mTimeToShowTile; - private final String mPrefKey; + @Prefs.Key private final String mPrefKey; private final String mResetAction; private boolean mRegistered; - public UsageTracker(Context context, Class<?> tile, int timeoutResource) { + public UsageTracker(Context context, @Prefs.Key String prefKey, Class<?> tile, + int timeoutResource) { mContext = context; - mPrefKey = tile.getSimpleName() + "LastUsed"; + mPrefKey = prefKey; mTimeToShowTile = MILLIS_PER_DAY * mContext.getResources().getInteger(timeoutResource); mResetAction = "com.android.systemui.qs." + tile.getSimpleName() + ".usage_reset"; } @@ -56,16 +58,16 @@ public class UsageTracker implements Listenable { } public boolean isRecentlyUsed() { - long lastUsed = getSharedPrefs().getLong(mPrefKey, 0); + long lastUsed = Prefs.getLong(mContext, mPrefKey, 0L /* defaultValue */); return (System.currentTimeMillis() - lastUsed) < mTimeToShowTile; } public void trackUsage() { - getSharedPrefs().edit().putLong(mPrefKey, System.currentTimeMillis()).commit(); + Prefs.putLong(mContext, mPrefKey, System.currentTimeMillis()); } public void reset() { - getSharedPrefs().edit().remove(mPrefKey).commit(); + Prefs.remove(mContext, mPrefKey); } public void showResetConfirmation(String title, final Runnable onConfirmed) { @@ -87,10 +89,6 @@ public class UsageTracker implements Listenable { d.show(); } - private SharedPreferences getSharedPrefs() { - return mContext.getSharedPreferences(mContext.getPackageName(), 0); - } - private BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java index 2dd02a5..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/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java index 5963a45..4a33f55 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java @@ -18,6 +18,7 @@ package com.android.systemui.qs.tiles; import android.provider.Settings.Secure; +import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.qs.QSTile; import com.android.systemui.qs.SecureSetting; @@ -50,7 +51,8 @@ public class ColorInversionTile extends QSTile<QSTile.BooleanState> { } } }; - mUsageTracker = new UsageTracker(host.getContext(), ColorInversionTile.class, + mUsageTracker = new UsageTracker(host.getContext(), + Prefs.Key.COLOR_INVERSION_TILE_LAST_USED, ColorInversionTile.class, R.integer.days_to_show_color_inversion_tile); if (mSetting.getValue() != 0 && !mUsageTracker.isRecentlyUsed()) { mUsageTracker.trackUsage(); 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..5145bc7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; +import android.provider.Settings; +import android.provider.Settings.Global; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnAttachStateChangeListener; +import android.view.ViewGroup; + +import com.android.systemui.Prefs; +import com.android.systemui.R; +import com.android.systemui.qs.QSTile; +import com.android.systemui.statusbar.policy.ZenModeController; +import com.android.systemui.volume.ZenModePanel; + +/** Quick settings tile: Do not disturb **/ +public class DndTile extends QSTile<QSTile.BooleanState> { + private static final Intent ZEN_SETTINGS = + new Intent(Settings.ACTION_ZEN_MODE_SETTINGS); + + private static final Intent ZEN_PRIORITY_SETTINGS = + new Intent(Settings.ACTION_ZEN_MODE_PRIORITY_SETTINGS); + + private static final String ACTION_SET_VISIBLE = "com.android.systemui.dndtile.SET_VISIBLE"; + private static final String EXTRA_VISIBLE = "visible"; + + private final ZenModeController mController; + private final DndDetailAdapter mDetailAdapter; + + private boolean mListening; + private boolean mShowingDetail; + + public DndTile(Host host) { + super(host); + mController = host.getZenModeController(); + mDetailAdapter = new DndDetailAdapter(); + mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_SET_VISIBLE)); + } + + public static void setVisible(Context context, boolean visible) { + Prefs.putBoolean(context, Prefs.Key.DND_TILE_VISIBLE, visible); + } + + public static boolean isVisible(Context context) { + return Prefs.getBoolean(context, Prefs.Key.DND_TILE_VISIBLE, false /* defaultValue */); + } + + public static void setCombinedIcon(Context context, boolean combined) { + Prefs.putBoolean(context, Prefs.Key.DND_TILE_COMBINED_ICON, combined); + } + + public static boolean isCombinedIcon(Context context) { + return Prefs.getBoolean(context, Prefs.Key.DND_TILE_COMBINED_ICON, + false /* defaultValue */); + } + + @Override + public DetailAdapter getDetailAdapter() { + return mDetailAdapter; + } + + @Override + protected BooleanState newTileState() { + return new BooleanState(); + } + + @Override + public void handleClick() { + if (mState.value) { + mController.setZen(Global.ZEN_MODE_OFF, null, TAG); + } else { + int zen = Prefs.getInt(mContext, Prefs.Key.DND_FAVORITE_ZEN, Global.ZEN_MODE_ALARMS); + mController.setZen(zen, null, TAG); + refreshState(zen); // this one's optimistic + 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 = isVisible(mContext); + switch (zen) { + case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: + state.icon = ResourceIcon.get(R.drawable.ic_qs_dnd_on); + state.label = mContext.getString(R.string.quick_settings_dnd_priority_label); + state.contentDescription = mContext.getString( + R.string.accessibility_quick_settings_dnd_priority_on); + break; + case Global.ZEN_MODE_NO_INTERRUPTIONS: + state.icon = 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); + Prefs.registerListener(mContext, mPrefListener); + } else { + mController.removeCallback(mZenCallback); + Prefs.unregisterListener(mContext, mPrefListener); + } + } + + private final OnSharedPreferenceChangeListener mPrefListener + = new OnSharedPreferenceChangeListener() { + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, + @Prefs.Key String key) { + if (Prefs.Key.DND_TILE_COMBINED_ICON.equals(key) || + Prefs.Key.DND_TILE_VISIBLE.equals(key)) { + refreshState(); + } + } + }; + + private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() { + public void onZenChanged(int zen) { + refreshState(zen); + } + }; + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final boolean visible = intent.getBooleanExtra(EXTRA_VISIBLE, false); + setVisible(mContext, visible); + refreshState(); + } + }; + + private final class DndDetailAdapter implements DetailAdapter, OnAttachStateChangeListener { + + @Override + public int getTitle() { + return R.string.quick_settings_dnd_label; + } + + @Override + public Boolean getToggleState() { + return mState.value; + } + + @Override + public Intent getSettingsIntent() { + return ZEN_SETTINGS; + } + + @Override + public void setToggleState(boolean state) { + if (!state) { + mController.setZen(Global.ZEN_MODE_OFF, null, TAG); + 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.addOnAttachStateChangeListener(this); + zmp.setCallback(mZenModePanelCallback); + } + return zmp; + } + + @Override + public void onViewAttachedToWindow(View v) { + mShowingDetail = true; + } + + @Override + public void onViewDetachedFromWindow(View v) { + mShowingDetail = false; + } + } + + private final ZenModePanel.Callback mZenModePanelCallback = new ZenModePanel.Callback() { + @Override + public void onPrioritySettings() { + mHost.startSettingsActivity(ZEN_PRIORITY_SETTINGS); + } + + @Override + public void onInteraction() { + // noop + } + + @Override + public void onExpanded(boolean expanded) { + // noop + } + }; + +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java index 5c1a317..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/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java index fcc517f..6063f80 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java @@ -20,6 +20,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.qs.UsageTracker; import com.android.systemui.qs.QSTile; @@ -105,7 +106,8 @@ public class HotspotTile extends QSTile<QSTile.BooleanState> { } private static UsageTracker newUsageTracker(Context context) { - return new UsageTracker(context, HotspotTile.class, R.integer.days_to_show_hotspot_tile); + return new UsageTracker(context, Prefs.Key.HOTSPOT_TILE_LAST_USED, HotspotTile.class, + R.integer.days_to_show_hotspot_tile); } private final class Callback implements HotspotController.Callback { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/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..d589366 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 **/ @@ -66,9 +67,16 @@ public class WifiTile extends QSTile<QSTile.SignalState> { public void setListening(boolean listening) { if (listening) { mController.addNetworkSignalChangedCallback(mCallback); - mWifiController.addAccessPointCallback(mDetailAdapter); } else { mController.removeNetworkSignalChangedCallback(mCallback); + } + } + + @Override + public void setDetailListening(boolean listening) { + if (listening) { + mWifiController.addAccessPointCallback(mDetailAdapter); + } else { mWifiController.removeAccessPointCallback(mDetailAdapter); } } @@ -282,10 +290,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 +307,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 +334,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..c7f8919 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 @@ -59,8 +59,6 @@ public class Constants { public static class Values { public static class App { public static int AppWidgetHostId = 1024; - public static String Key_SearchAppWidgetId = "searchAppWidgetId"; - public static String Key_DebugModeEnabled = "debugModeEnabled"; public static String DebugModeVersion = "A"; } diff --git a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java index 2ddab48..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..1001feb 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java @@ -19,14 +19,12 @@ package com.android.systemui.recents; import android.app.Activity; import android.app.ActivityOptions; import android.app.SearchManager; -import android.appwidget.AppWidgetHostView; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProviderInfo; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.SharedPreferences; import android.os.Bundle; import android.os.SystemClock; import android.os.UserHandle; @@ -36,6 +34,7 @@ import android.view.View; import android.view.ViewStub; import android.widget.Toast; +import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.recents.misc.DebugTrigger; import com.android.systemui.recents.misc.ReferenceCountedTrigger; @@ -43,15 +42,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 +71,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 +126,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 +177,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 +196,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 +242,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 +292,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); @@ -347,6 +342,12 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView } } + /** Dismisses Recents directly to Home without transition animation. */ + void dismissRecentsToHomeWithoutTransitionAnimation() { + finish(); + overridePendingTransition(0, 0); + } + /** Dismisses Recents directly to Home if we currently aren't transitioning. */ boolean dismissRecentsToHome(boolean animated) { SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy(); @@ -362,12 +363,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 +382,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 +420,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 +431,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 +458,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(); @@ -565,10 +566,9 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView /** Called when debug mode is triggered */ public void onDebugModeTriggered() { if (mConfig.developerOptionsEnabled) { - SharedPreferences settings = getSharedPreferences(getPackageName(), 0); - if (settings.getBoolean(Constants.Values.App.Key_DebugModeEnabled, false)) { + if (Prefs.getBoolean(this, Prefs.Key.DEBUG_MODE_ENABLED, false /* boolean */)) { // Disable the debug mode - settings.edit().remove(Constants.Values.App.Key_DebugModeEnabled).apply(); + Prefs.remove(this, Prefs.Key.DEBUG_MODE_ENABLED); mConfig.debugModeEnabled = false; inflateDebugOverlay(); if (mDebugOverlay != null) { @@ -576,7 +576,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView } } else { // Enable the debug mode - settings.edit().putBoolean(Constants.Values.App.Key_DebugModeEnabled, true).apply(); + Prefs.putBoolean(this, Prefs.Key.DEBUG_MODE_ENABLED, true); mConfig.debugModeEnabled = true; inflateDebugOverlay(); if (mDebugOverlay != null) { @@ -589,6 +589,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 +629,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..244fada 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java @@ -18,15 +18,15 @@ package com.android.systemui.recents; import android.app.ActivityManager; import android.content.Context; -import android.content.SharedPreferences; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Rect; import android.provider.Settings; import android.util.DisplayMetrics; -import android.util.TypedValue; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; + +import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.recents.misc.Console; import com.android.systemui.recents.misc.SystemServicesProxy; @@ -50,9 +50,6 @@ public class RecentsConfiguration { // Disable all thumbnail loading. public static final int SVELTE_DISABLE_LOADING = 3; - /** Animations */ - public float animationPxMovementPerSecond; - /** Interpolators */ public Interpolator fastOutSlowInInterpolator; public Interpolator fastOutLinearInInterpolator; @@ -83,6 +80,7 @@ public class RecentsConfiguration { public int taskStackScrollDuration; public int taskStackMaxDim; public int taskStackTopPaddingPx; + public int dismissAllButtonSizePx; public float taskStackWidthPaddingPct; public float taskStackOverscrollPct; @@ -137,6 +135,7 @@ public class RecentsConfiguration { public boolean fakeShadows; /** Dev options and global settings */ + public boolean multiStackEnabled; public boolean lockToAppEnabled; public boolean developerOptionsEnabled; public boolean debugModeEnabled; @@ -179,12 +178,12 @@ public class RecentsConfiguration { /** Updates the state, given the specified context */ void update(Context context) { - SharedPreferences settings = context.getSharedPreferences(context.getPackageName(), 0); Resources res = context.getResources(); DisplayMetrics dm = res.getDisplayMetrics(); // Debug mode - debugModeEnabled = settings.getBoolean(Constants.Values.App.Key_DebugModeEnabled, false); + debugModeEnabled = Prefs.getBoolean(context, Prefs.Key.DEBUG_MODE_ENABLED, + false /* defaultValue */); if (debugModeEnabled) { Console.Enabled = true; } @@ -197,10 +196,6 @@ public class RecentsConfiguration { // Insets displayRect.set(0, 0, dm.widthPixels, dm.heightPixels); - // Animations - animationPxMovementPerSecond = - res.getDimensionPixelSize(R.dimen.recents_animation_movement_in_dps_per_second); - // Filtering filteringCurrentViewsAnimDuration = res.getInteger(R.integer.recents_filter_animate_current_views_duration); @@ -212,19 +207,17 @@ public class RecentsConfiguration { // Search Bar searchBarSpaceHeightPx = res.getDimensionPixelSize(R.dimen.recents_search_bar_space_height); - searchBarAppWidgetId = settings.getInt(Constants.Values.App.Key_SearchAppWidgetId, -1); + searchBarAppWidgetId = Prefs.getInt(context, Prefs.Key.SEARCH_APP_WIDGET_ID, + -1 /* defaultValue */); // 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 +247,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); @@ -295,9 +282,7 @@ public class RecentsConfiguration { /** Updates the search bar app widget */ public void updateSearchBarAppWidgetId(Context context, int appWidgetId) { searchBarAppWidgetId = appWidgetId; - SharedPreferences settings = context.getSharedPreferences(context.getPackageName(), 0); - settings.edit().putInt(Constants.Values.App.Key_SearchAppWidgetId, - appWidgetId).apply(); + Prefs.putInt(context, Prefs.Key.SEARCH_APP_WIDGET_ID, appWidgetId); } /** Updates the states that need to be re-read whenever we re-initialize. */ @@ -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..b701e0b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.recents; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.app.FragmentManager; +import android.content.Context; +import android.content.DialogInterface; +import android.graphics.Rect; +import android.os.Bundle; +import android.util.Pair; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.Button; +import com.android.systemui.R; +import com.android.systemui.recents.misc.SystemServicesProxy; +import com.android.systemui.recents.model.RecentsTaskLoader; +import com.android.systemui.recents.model.Task; +import com.android.systemui.recents.RecentsActivity; +import com.android.systemui.recents.views.RecentsView; + +import java.util.ArrayList; + +/** + * A helper for the dialogs that show when task debugging is on. + */ +public class RecentsResizeTaskDialog extends DialogFragment { + + static final String TAG = "RecentsResizeTaskDialog"; + + // The various window arrangements we can handle. + private static final int PLACE_LEFT = 1; + private static final int PLACE_RIGHT = 2; + private static final int PLACE_TOP = 3; + private static final int PLACE_BOTTOM = 4; + private static final int PLACE_TOP_LEFT = 5; + private static final int PLACE_TOP_RIGHT = 6; + private static final int PLACE_BOTTOM_LEFT = 7; + private static final int PLACE_BOTTOM_RIGHT = 8; + private static final int PLACE_FULL = 9; + + // The button resource ID combined with the arrangement command. + private static final int[][] BUTTON_DEFINITIONS = + {{R.id.place_left, PLACE_LEFT}, + {R.id.place_right, PLACE_RIGHT}, + {R.id.place_top, PLACE_TOP}, + {R.id.place_bottom, PLACE_BOTTOM}, + {R.id.place_top_left, PLACE_TOP_LEFT}, + {R.id.place_top_right, PLACE_TOP_RIGHT}, + {R.id.place_bottom_left, PLACE_BOTTOM_LEFT}, + {R.id.place_bottom_right, PLACE_BOTTOM_RIGHT}, + {R.id.place_full, PLACE_FULL}}; + + // The task we want to resize. + private FragmentManager mFragmentManager; + private View mResizeTaskDialogContent; + private RecentsActivity mRecentsActivity; + private RecentsView mRecentsView; + private SystemServicesProxy mSsp; + private Rect[] mBounds = {new Rect(), new Rect(), new Rect(), new Rect()}; + private Task[] mTasks = {null, null, null, null}; + + public RecentsResizeTaskDialog(FragmentManager mgr, RecentsActivity activity) { + mFragmentManager = mgr; + mRecentsActivity = activity; + mSsp = RecentsTaskLoader.getInstance().getSystemServicesProxy(); + } + + /** Shows the resize-task dialog. */ + void showResizeTaskDialog(Task mainTask, RecentsView rv) { + mTasks[0] = mainTask; + mRecentsView = rv; + + show(mFragmentManager, TAG); + } + + /** Creates a new resize-task dialog. */ + private void createResizeTaskDialog(final Context context, LayoutInflater inflater, + AlertDialog.Builder builder) { + builder.setTitle(R.string.recents_caption_resize); + mResizeTaskDialogContent = + inflater.inflate(R.layout.recents_task_resize_dialog, null, false); + + for (int i = 0; i < BUTTON_DEFINITIONS.length; i++) { + Button b = (Button)mResizeTaskDialogContent.findViewById(BUTTON_DEFINITIONS[i][0]); + if (b != null) { + final int action = BUTTON_DEFINITIONS[i][1]; + b.setOnClickListener( + new View.OnClickListener() { + public void onClick(View v) { + placeTasks(action); + } + }); + } + } + + builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dismiss(); + } + }); + + builder.setView(mResizeTaskDialogContent); + } + + /** Helper function to place window(s) on the display according to an arrangement request. */ + private void placeTasks(int arrangement) { + Rect rect = mSsp.getWindowRect(); + for (int i = 0; i < mBounds.length; ++i) { + mBounds[i].set(rect); + if (i != 0) { + mTasks[i] = null; + } + } + int additionalTasks = 0; + switch (arrangement) { + case PLACE_LEFT: + mBounds[0].right = mBounds[0].centerX(); + mBounds[1].left = mBounds[0].right; + additionalTasks = 1; + break; + case PLACE_RIGHT: + mBounds[1].right = mBounds[1].centerX(); + mBounds[0].left = mBounds[1].right; + additionalTasks = 1; + break; + case PLACE_TOP: + mBounds[0].bottom = mBounds[0].centerY(); + mBounds[1].top = mBounds[0].bottom; + additionalTasks = 1; + break; + case PLACE_BOTTOM: + mBounds[1].bottom = mBounds[1].centerY(); + mBounds[0].top = mBounds[1].bottom; + additionalTasks = 1; + break; + case PLACE_TOP_LEFT: // TL, TR, BL, BR + mBounds[0].right = mBounds[0].centerX(); + mBounds[0].bottom = mBounds[0].centerY(); + mBounds[1].left = mBounds[0].right; + mBounds[1].bottom = mBounds[0].bottom; + mBounds[2].right = mBounds[0].right; + mBounds[2].top = mBounds[0].bottom; + mBounds[3].left = mBounds[0].right; + mBounds[3].top = mBounds[0].bottom; + additionalTasks = 3; + break; + case PLACE_TOP_RIGHT: // TR, TL, BR, BL + mBounds[0].left = mBounds[0].centerX(); + mBounds[0].bottom = mBounds[0].centerY(); + mBounds[1].right = mBounds[0].left; + mBounds[1].bottom = mBounds[0].bottom; + mBounds[2].left = mBounds[0].left; + mBounds[2].top = mBounds[0].bottom; + mBounds[3].right = mBounds[0].left; + mBounds[3].top = mBounds[0].bottom; + additionalTasks = 3; + break; + case PLACE_BOTTOM_LEFT: // BL, BR, TL, TR + mBounds[0].right = mBounds[0].centerX(); + mBounds[0].top = mBounds[0].centerY(); + mBounds[1].left = mBounds[0].right; + mBounds[1].top = mBounds[0].top; + mBounds[2].right = mBounds[0].right; + mBounds[2].bottom = mBounds[0].top; + mBounds[3].left = mBounds[0].right; + mBounds[3].bottom = mBounds[0].top; + additionalTasks = 3; + break; + case PLACE_BOTTOM_RIGHT: // BR, BL, TR, TL + mBounds[0].left = mBounds[0].centerX(); + mBounds[0].top = mBounds[0].centerY(); + mBounds[1].right = mBounds[0].left; + mBounds[1].top = mBounds[0].top; + mBounds[2].left = mBounds[0].left; + mBounds[2].bottom = mBounds[0].top; + mBounds[3].right = mBounds[0].left; + mBounds[3].bottom = mBounds[0].top; + additionalTasks = 3; + break; + case PLACE_FULL: + // Nothing to change. + break; + } + + // Get the other tasks. + for (int i = 1; i <= additionalTasks && mTasks[i - 1] != null; ++i) { + mTasks[i] = mRecentsView.getNextTaskOrTopTask(mTasks[i - 1]); + // Do stop if we circled back to the first item. + if (mTasks[i] == mTasks[0]) { + mTasks[i] = null; + } + } + + // Get rid of the dialog. + dismiss(); + mRecentsActivity.dismissRecentsToHomeWithoutTransitionAnimation(); + + // Resize all tasks beginning from the "oldest" one. + for (int i = additionalTasks; i >= 0; --i) { + if (mTasks[i] != null) { + mSsp.resizeTask(mTasks[i].key.id, mBounds[i]); + } + } + + // Show tasks as they might not be currently visible - beginning with the oldest so that + // the focus ends on the selected one. + for (int i = additionalTasks; i >= 0; --i) { + if (mTasks[i] != null) { + mRecentsView.launchTask(mTasks[i]); + } + } + } + + @Override + public Dialog onCreateDialog(Bundle args) { + final Context context = this.getActivity(); + LayoutInflater inflater = getActivity().getLayoutInflater(); + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + createResizeTaskDialog(context, inflater, builder); + return builder.create(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsUserEventProxyReceiver.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsUserEventProxyReceiver.java index 236da5d..5eefbc7 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsUserEventProxyReceiver.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsUserEventProxyReceiver.java @@ -19,8 +19,6 @@ package com.android.systemui.recents; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import com.android.systemui.recent.Recents; - /** * A proxy for Recents events which happens strictly for non-owner users. @@ -39,28 +37,27 @@ public class RecentsUserEventProxyReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - AlternateRecentsComponent recents = Recents.getRecentsComponent( - context.getApplicationContext(), true); + Recents recents = Recents.getInstanceAndStartIfNeeded(context); switch (intent.getAction()) { case ACTION_PROXY_SHOW_RECENTS_TO_USER: { boolean triggeredFromAltTab = intent.getBooleanExtra( - AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_ALT_TAB, false); - recents.showRecents(triggeredFromAltTab); + Recents.EXTRA_TRIGGERED_FROM_ALT_TAB, false); + recents.showRecentsInternal(triggeredFromAltTab); break; } case ACTION_PROXY_HIDE_RECENTS_TO_USER: { boolean triggeredFromAltTab = intent.getBooleanExtra( - AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_ALT_TAB, false); + Recents.EXTRA_TRIGGERED_FROM_ALT_TAB, false); boolean triggeredFromHome = intent.getBooleanExtra( - AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_HOME_KEY, false); - recents.hideRecents(triggeredFromAltTab, triggeredFromHome); + Recents.EXTRA_TRIGGERED_FROM_HOME_KEY, false); + recents.hideRecentsInternal(triggeredFromAltTab, triggeredFromHome); break; } case ACTION_PROXY_TOGGLE_RECENTS_TO_USER: - recents.toggleRecents(); + recents.toggleRecentsInternal(); break; case ACTION_PROXY_PRELOAD_RECENTS_TO_USER: - recents.preloadRecents(); + recents.preloadRecentsInternal(); break; case ACTION_PROXY_CONFIG_CHANGE_TO_USER: recents.configurationChanged(); diff --git a/packages/SystemUI/src/com/android/systemui/recent/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java index 2fa0b58..cbf5c05 100644 --- a/packages/SystemUI/src/com/android/systemui/recent/ScreenPinningRequest.java +++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.recent; +package com.android.systemui.recents; import android.animation.ArgbEvaluator; import android.animation.ValueAnimator; @@ -41,7 +41,6 @@ import android.widget.LinearLayout; import android.widget.TextView; import com.android.systemui.R; -import com.android.systemui.recents.model.RecentsTaskLoader; import java.util.ArrayList; @@ -145,7 +144,7 @@ public class ScreenPinningRequest implements View.OnClickListener { boolean isLandscape = isLandscapePhone(mContext); inflateView(isLandscape); - int bgColor = mContext.getResources().getColor( + int bgColor = mContext.getColor( R.color.screen_pinning_request_window_bg); if (ActivityManager.isHighEndGfx()) { mLayout.setAlpha(0f); diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java index 90b099c..e3fb16a 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; @@ -382,6 +438,33 @@ public class SystemServicesProxy { return info.loadLabel(mPm).toString(); } + /** Returns the application label */ + public String getApplicationLabel(Intent baseIntent, int userId) { + if (mPm == null) return null; + + // If we are mocking, then return a mock label + if (Constants.DebugFlags.App.EnableSystemServicesProxy) { + return "Recent Task"; + } + + ResolveInfo ri = mPm.resolveActivityAsUser(baseIntent, 0, userId); + CharSequence label = (ri != null) ? ri.loadLabel(mPm) : null; + return (label != null) ? label.toString() : null; + } + + /** Returns the content description for a given task */ + public String getContentDescription(Intent baseIntent, int userId, String activityLabel, + Resources res) { + String applicationLabel = getApplicationLabel(baseIntent, userId); + if (applicationLabel == null) { + return getBadgedLabel(activityLabel, userId); + } + String badgedApplicationLabel = getBadgedLabel(applicationLabel, userId); + return applicationLabel.equals(activityLabel) ? badgedApplicationLabel + : res.getString(R.string.accessibility_recents_task_header, + badgedApplicationLabel, activityLabel); + } + /** * Returns the activity icon for the ActivityInfo for a user, badging if * necessary. @@ -408,6 +491,16 @@ public class SystemServicesProxy { return icon; } + /** + * Returns the given label for a user, badging if necessary. + */ + public String getBadgedLabel(String label, int userId) { + if (userId != UserHandle.myUserId()) { + label = mPm.getUserBadgedLabel(label, new UserHandle(userId)).toString(); + } + return label; + } + /** Returns the package name of the home activity. */ public String getHomeActivityPackageName() { if (mPm == null) return null; @@ -524,6 +617,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..40cd211 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(); @@ -120,6 +130,8 @@ public class RecentsTaskLoadPlan { // Load the label, icon, and color String activityLabel = loader.getAndUpdateActivityLabel(taskKey, t.taskDescription, mSystemServicesProxy, infoHandle); + String contentDescription = loader.getAndUpdateContentDescription(taskKey, + activityLabel, mSystemServicesProxy, res); Drawable activityIcon = loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, mSystemServicesProxy, res, infoHandle, false); int activityColor = loader.getActivityPrimaryColor(t.taskDescription, mConfig); @@ -138,19 +150,48 @@ public class RecentsTaskLoadPlan { // Add the task to the stack Task task = new Task(taskKey, (t.id != RecentsTaskLoader.INVALID_TASK_ID), - t.affiliatedTaskId, t.affiliatedTaskColor, activityLabel, activityIcon, - activityColor, (i == (taskCount - 1)), mConfig.lockToAppEnabled, icon, - iconFilename); + t.affiliatedTaskId, t.affiliatedTaskColor, activityLabel, contentDescription, + activityIcon, activityColor, (i == (taskCount - 1)), mConfig.lockToAppEnabled, + icon, iconFilename); task.thumbnail = loader.getAndUpdateThumbnail(taskKey, mSystemServicesProxy, false); if (DEBUG) Log.d(TAG, "\tthumbnail: " + taskKey + ", " + task.thumbnail); - loadedTasks.add(task); + + if (!mConfig.multiStackEnabled || + Constants.DebugFlags.App.EnableMultiStackToSingleStack) { + 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 +207,93 @@ public class RecentsTaskLoadPlan { Resources res = mContext.getResources(); // Iterate through each of the tasks and load them according to the load conditions. - ArrayList<Task> tasks = mStack.getTasks(); - int taskCount = tasks.size(); - for (int i = 0; i < taskCount; i++) { - ActivityManager.RecentTaskInfo t = mRawTasks.get(i); - Task task = tasks.get(i); - Task.TaskKey taskKey = task.key; + int stackCount = mStacks.size(); + for (int j = 0; j < stackCount; j++) { + ArrayList<Task> tasks = mStacks.valueAt(j).getTasks(); + int taskCount = tasks.size(); + for (int i = 0; i < taskCount; i++) { + ActivityManager.RecentTaskInfo t = mRawTasks.get(i); + Task task = tasks.get(i); + Task.TaskKey taskKey = task.key; - // Get an existing activity info handle if possible - Task.ComponentNameKey cnKey = taskKey.getComponentNameKey(); - ActivityInfoHandle infoHandle; - boolean hadCachedActivityInfo = false; - if (mActivityInfoCache.containsKey(cnKey)) { - infoHandle = mActivityInfoCache.get(cnKey); - hadCachedActivityInfo = true; - } else { - infoHandle = new ActivityInfoHandle(); - } + // Get an existing activity info handle if possible + Task.ComponentNameKey cnKey = taskKey.getComponentNameKey(); + ActivityInfoHandle infoHandle; + boolean hadCachedActivityInfo = false; + if (mActivityInfoCache.containsKey(cnKey)) { + infoHandle = mActivityInfoCache.get(cnKey); + hadCachedActivityInfo = true; + } else { + infoHandle = new ActivityInfoHandle(); + } - boolean isRunningTask = (task.key.id == opts.runningTaskId); - boolean isVisibleTask = i >= (taskCount - opts.numVisibleTasks); - boolean isVisibleThumbnail = i >= (taskCount - opts.numVisibleTaskThumbnails); + boolean isRunningTask = (task.key.id == opts.runningTaskId); + boolean isVisibleTask = i >= (taskCount - opts.numVisibleTasks); + boolean isVisibleThumbnail = i >= (taskCount - opts.numVisibleTaskThumbnails); - // If requested, skip the running task - if (opts.onlyLoadPausedActivities && isRunningTask) { - continue; - } + // If requested, skip the running task + if (opts.onlyLoadPausedActivities && isRunningTask) { + continue; + } - if (opts.loadIcons && (isRunningTask || isVisibleTask)) { - if (task.activityIcon == null) { - if (DEBUG) Log.d(TAG, "\tLoading icon: " + taskKey); - task.activityIcon = loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, - mSystemServicesProxy, res, infoHandle, true); + if (opts.loadIcons && (isRunningTask || isVisibleTask)) { + if (task.activityIcon == null) { + if (DEBUG) Log.d(TAG, "\tLoading icon: " + taskKey); + task.activityIcon = loader.getAndUpdateActivityIcon(taskKey, + t.taskDescription, mSystemServicesProxy, res, infoHandle, true); + } } - } - if (opts.loadThumbnails && (isRunningTask || isVisibleThumbnail)) { - if (task.thumbnail == null || isRunningTask) { - if (DEBUG) Log.d(TAG, "\tLoading thumbnail: " + taskKey); - if (mConfig.svelteLevel <= RecentsConfiguration.SVELTE_LIMIT_CACHE) { - task.thumbnail = loader.getAndUpdateThumbnail(taskKey, mSystemServicesProxy, - true); - } else if (mConfig.svelteLevel == RecentsConfiguration.SVELTE_DISABLE_CACHE) { - loadQueue.addTask(task); + if (opts.loadThumbnails && (isRunningTask || isVisibleThumbnail)) { + if (task.thumbnail == null || isRunningTask) { + if (DEBUG) Log.d(TAG, "\tLoading thumbnail: " + taskKey); + if (mConfig.svelteLevel <= RecentsConfiguration.SVELTE_LIMIT_CACHE) { + task.thumbnail = loader.getAndUpdateThumbnail(taskKey, + mSystemServicesProxy, true); + } else if (mConfig.svelteLevel == RecentsConfiguration.SVELTE_DISABLE_CACHE) { + loadQueue.addTask(task); + } } } - } - // Update the activity info cache - if (!hadCachedActivityInfo && infoHandle.info != null) { - mActivityInfoCache.put(cnKey, infoHandle); + // Update the activity info cache + if (!hadCachedActivityInfo && infoHandle.info != null) { + mActivityInfoCache.put(cnKey, infoHandle); + } } } } /** - * Composes and returns a TaskStack from the preloaded list of recent tasks. + * Returns all TaskStacks from the preloaded list of recent tasks. */ - public TaskStack getTaskStack() { - return mStack; + public ArrayList<TaskStack> getAllTaskStacks() { + ArrayList<TaskStack> stacks = new ArrayList<TaskStack>(); + int stackCount = mStacks.size(); + for (int i = 0; i < stackCount; i++) { + stacks.add(mStacks.valueAt(i)); + } + // Ensure that we have at least one stack + if (stacks.isEmpty()) { + stacks.add(new TaskStack()); + } + return stacks; } /** - * Composes and returns a SpaceNode from the preloaded list of recent tasks. + * Returns a specific TaskStack from the preloaded list of recent tasks. */ - public SpaceNode getSpaceNode() { - SpaceNode node = new SpaceNode(); - node.setStack(mStack); - return node; + public TaskStack getTaskStack(int stackId) { + return mStacks.get(stackId); + } + + /** Returns whether there are any tasks in any stacks. */ + public boolean hasTasks() { + int stackCount = mStacks.size(); + for (int i = 0; i < stackCount; i++) { + if (mStacks.valueAt(i).getTaskCount() > 0) { + return true; + } + } + return false; } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java index ba2903a..b2aa2b6 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java @@ -33,7 +33,6 @@ import com.android.systemui.recents.Constants; import com.android.systemui.recents.RecentsConfiguration; import com.android.systemui.recents.misc.SystemServicesProxy; -import java.util.Collection; import java.util.concurrent.ConcurrentLinkedQueue; @@ -47,18 +46,6 @@ class TaskResourceLoadQueue { ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<Task>(); /** Adds a new task to the load queue */ - void addTasks(Collection<Task> tasks) { - for (Task t : tasks) { - if (!mQueue.contains(t)) { - mQueue.add(t); - } - } - synchronized(this) { - notifyAll(); - } - } - - /** Adds a new task to the load queue */ void addTask(Task t) { if (!mQueue.contains(t)) { mQueue.add(t); @@ -272,6 +259,7 @@ public class RecentsTaskLoader { DrawableLruCache mApplicationIconCache; BitmapLruCache mThumbnailCache; StringLruCache mActivityLabelCache; + StringLruCache mContentDescriptionCache; TaskResourceLoadQueue mLoadQueue; TaskResourceLoader mLoader; @@ -311,6 +299,7 @@ public class RecentsTaskLoader { mApplicationIconCache = new DrawableLruCache(iconCacheSize); mThumbnailCache = new BitmapLruCache(thumbnailCacheSize); mActivityLabelCache = new StringLruCache(100); + mContentDescriptionCache = new StringLruCache(100); mLoader = new TaskResourceLoader(mLoadQueue, mApplicationIconCache, mThumbnailCache, mDefaultThumbnail, mDefaultApplicationIcon); } @@ -361,6 +350,24 @@ public class RecentsTaskLoader { return label; } + /** Returns the content description using as many cached values as we can. */ + public String getAndUpdateContentDescription(Task.TaskKey taskKey, String activityLabel, + SystemServicesProxy ssp, Resources res) { + // Return the cached content description if it exists + String label = mContentDescriptionCache.getAndInvalidateIfModified(taskKey); + if (label != null) { + return label; + } + label = ssp.getContentDescription(taskKey.baseIntent, taskKey.userId, activityLabel, res); + if (label != null) { + mContentDescriptionCache.put(taskKey, label); + } else { + Log.w(TAG, "Missing content description for " + taskKey.baseIntent.getComponent() + + " u=" + taskKey.userId); + } + return label; + } + /** Returns the activity icon using as many cached values as we can. */ public Drawable getAndUpdateActivityIcon(Task.TaskKey taskKey, ActivityManager.TaskDescription td, SystemServicesProxy ssp, @@ -554,6 +561,7 @@ public class RecentsTaskLoader { mApplicationIconCache.evictAll(); // The cache is small, only clear the label cache when we are critical mActivityLabelCache.evictAll(); + mContentDescriptionCache.evictAll(); break; default: break; diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java b/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java deleted file mode 100644 index 831698a..0000000 --- a/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.recents.model; - -import android.graphics.Rect; - -import java.util.ArrayList; - - -/** - * The full recents space is partitioned using a BSP into various nodes that define where task - * stacks should be placed. - */ -public class SpaceNode { - /* BSP node callbacks */ - public interface SpaceNodeCallbacks { - /** Notifies when a node is added */ - public void onSpaceNodeAdded(SpaceNode node); - /** Notifies when a node is measured */ - public void onSpaceNodeMeasured(SpaceNode node, Rect rect); - } - - SpaceNode mStartNode; - SpaceNode mEndNode; - - TaskStack mStack; - - public SpaceNode() { - // Do nothing - } - - /** Sets the current stack for this space node */ - public void setStack(TaskStack stack) { - mStack = stack; - } - - /** Returns the task stack (not null if this is a leaf) */ - TaskStack getStack() { - return mStack; - } - - /** Returns whether there are any tasks in any stacks below this node. */ - public boolean hasTasks() { - return (mStack.getTaskCount() > 0) || - (mStartNode != null && mStartNode.hasTasks()) || - (mEndNode != null && mEndNode.hasTasks()); - } - - /** Returns whether this is a leaf node */ - boolean isLeafNode() { - return (mStartNode == null) && (mEndNode == null); - } - - /** Returns all the descendent task stacks */ - private void getStacksRec(ArrayList<TaskStack> stacks) { - if (isLeafNode()) { - stacks.add(mStack); - } else { - mStartNode.getStacksRec(stacks); - mEndNode.getStacksRec(stacks); - } - } - public ArrayList<TaskStack> getStacks() { - ArrayList<TaskStack> stacks = new ArrayList<TaskStack>(); - getStacksRec(stacks); - return stacks; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java index 55dfe45..c14adf4 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java @@ -36,6 +36,9 @@ public class Task { public void onTaskDataLoaded(); /* Notifies when a task has been unbound */ public void onTaskDataUnloaded(); + + /* Notifies when a task's stack id has changed. */ + public void onMultiStackDebugTaskStackIdChanged(); } /** The ComponentNameKey represents the unique primary key for a component @@ -68,14 +71,17 @@ public class Task { public static class TaskKey { final ComponentNameKey mComponentNameKey; public final int id; + public int stackId; public final Intent baseIntent; public final int userId; public long firstActiveTime; public long lastActiveTime; - public TaskKey(int id, Intent intent, int userId, long firstActiveTime, long lastActiveTime) { + public TaskKey(int id, int stackId, Intent intent, int userId, long firstActiveTime, + long lastActiveTime) { mComponentNameKey = new ComponentNameKey(intent.getComponent(), userId); this.id = id; + this.stackId = stackId; this.baseIntent = intent; this.userId = userId; this.firstActiveTime = firstActiveTime; @@ -92,18 +98,19 @@ public class Task { if (!(o instanceof TaskKey)) { return false; } - return id == ((TaskKey) o).id - && userId == ((TaskKey) o).userId; + TaskKey otherKey = (TaskKey) o; + return id == otherKey.id && stackId == otherKey.stackId && userId == otherKey.userId; } @Override public int hashCode() { - return (id << 5) + userId; + return Objects.hash(id, stackId, userId); } @Override public String toString() { return "Task.Key: " + id + ", " + + "s: " + stackId + ", " + "u: " + userId + ", " + "lat: " + lastActiveTime + ", " + baseIntent.getComponent().getPackageName(); @@ -117,6 +124,7 @@ public class Task { public boolean isLaunchTarget; public Drawable applicationIcon; public Drawable activityIcon; + public String contentDescription; public String activityLabel; public int colorPrimary; public boolean useLightOnPrimaryColor; @@ -133,8 +141,8 @@ public class Task { } public Task(TaskKey key, boolean isActive, int taskAffiliation, int taskAffiliationColor, - String activityTitle, Drawable activityIcon, int colorPrimary, - boolean lockToThisTask, boolean lockToTaskEnabled, Bitmap icon, + String activityTitle, String contentDescription, Drawable activityIcon, + int colorPrimary, boolean lockToThisTask, boolean lockToTaskEnabled, Bitmap icon, String iconFilename) { boolean isInAffiliationGroup = (taskAffiliation != key.id); boolean hasAffiliationGroupColor = isInAffiliationGroup && (taskAffiliationColor != 0); @@ -142,6 +150,7 @@ public class Task { this.taskAffiliation = taskAffiliation; this.taskAffiliationColor = taskAffiliationColor; this.activityLabel = activityTitle; + this.contentDescription = contentDescription; this.activityIcon = activityIcon; this.colorPrimary = hasAffiliationGroupColor ? taskAffiliationColor : colorPrimary; this.useLightOnPrimaryColor = Utilities.computeContrastBetweenColors(this.colorPrimary, @@ -159,6 +168,7 @@ public class Task { this.taskAffiliation = o.taskAffiliation; this.taskAffiliationColor = o.taskAffiliationColor; this.activityLabel = o.activityLabel; + this.contentDescription = o.contentDescription; this.activityIcon = o.activityIcon; this.colorPrimary = o.colorPrimary; this.useLightOnPrimaryColor = o.useLightOnPrimaryColor; @@ -180,6 +190,14 @@ public class Task { this.group = group; } + /** Updates the stack id of this task. */ + public void setStackId(int stackId) { + key.stackId = stackId; + if (mCb != null) { + mCb.onMultiStackDebugTaskStackIdChanged(); + } + } + /** Notifies the callback listeners that this task has been loaded */ public void notifyTaskDataLoaded(Bitmap thumbnail, Drawable applicationIcon) { this.applicationIcon = applicationIcon; diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java index 255d642..5aaea15 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java @@ -17,6 +17,7 @@ package com.android.systemui.recents.model; import android.graphics.Color; +import android.graphics.Rect; import com.android.systemui.recents.Constants; import com.android.systemui.recents.RecentsConfiguration; import com.android.systemui.recents.misc.NamedCounter; @@ -165,39 +166,46 @@ public class TaskStack { public void onStackTaskAdded(TaskStack stack, Task t); /* Notifies when a task has been removed from the stack */ public void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask); + /* Notifies when all task has been removed from the stack */ + public void onStackAllTasksRemoved(TaskStack stack, ArrayList<Task> removedTasks); /** Notifies when the stack was filtered */ public void onStackFiltered(TaskStack newStack, ArrayList<Task> curTasks, Task t); /** Notifies when the stack was un-filtered */ public void onStackUnfiltered(TaskStack newStack, ArrayList<Task> curTasks); } - /** A pair of indices representing the group and task positions in the stack and group. */ - public static class GroupTaskIndex { - public int groupIndex; // Index in the stack - public int taskIndex; // Index in the group - - public GroupTaskIndex() {} - - public GroupTaskIndex(int gi, int ti) { - groupIndex = gi; - taskIndex = ti; - } - } - // The task offset to apply to a task id as a group affiliation static final int IndividualTaskIdOffset = 1 << 16; + public final int id; + public final Rect stackBounds = new Rect(); + public final Rect displayBounds = new Rect(); + FilteredTaskList mTaskList = new FilteredTaskList(); TaskStackCallbacks mCb; ArrayList<TaskGrouping> mGroups = new ArrayList<TaskGrouping>(); HashMap<Integer, TaskGrouping> mAffinitiesGroups = new HashMap<Integer, TaskGrouping>(); - /** Sets the callbacks for this task stack */ + public TaskStack() { + this(0); + } + + public TaskStack(int stackId) { + id = stackId; + } + + /** Sets the callbacks for this task stack. */ public void setCallbacks(TaskStackCallbacks cb) { mCb = cb; } + /** Sets the bounds of this stack. */ + public void setBounds(Rect stackBounds, Rect displayBounds) { + this.stackBounds.set(stackBounds); + this.displayBounds.set(displayBounds); + } + /** Resets this TaskStack. */ public void reset() { mCb = null; @@ -214,19 +222,24 @@ public class TaskStack { } } + /** Does the actual work associated with removing the task. */ + void removeTaskImpl(Task t) { + // Remove the task from the list + mTaskList.remove(t); + // Remove it from the group as well, and if it is empty, remove the group + TaskGrouping group = t.group; + group.removeTask(t); + if (group.getTaskCount() == 0) { + removeGroup(group); + } + // Update the lock-to-app state + t.lockToThisTask = false; + } + /** Removes a task */ public void removeTask(Task t) { if (mTaskList.contains(t)) { - // Remove the task from the list - mTaskList.remove(t); - // Remove it from the group as well, and if it is empty, remove the group - TaskGrouping group = t.group; - group.removeTask(t); - if (group.getTaskCount() == 0) { - removeGroup(group); - } - // Update the lock-to-app state - t.lockToThisTask = false; + removeTaskImpl(t); Task newFrontMostTask = getFrontMostTask(); if (newFrontMostTask != null && newFrontMostTask.lockToTaskEnabled) { newFrontMostTask.lockToThisTask = true; @@ -238,20 +251,27 @@ public class TaskStack { } } + /** Removes all tasks */ + public void removeAllTasks() { + ArrayList<Task> taskList = new ArrayList<Task>(mTaskList.getTasks()); + int taskCount = taskList.size(); + for (int i = taskCount - 1; i >= 0; i--) { + Task t = taskList.get(i); + removeTaskImpl(t); + } + if (mCb != null) { + // Notify that all tasks have been removed + mCb.onStackAllTasksRemoved(this, taskList); + } + } + /** Sets a few tasks in one go */ public void setTasks(List<Task> tasks) { ArrayList<Task> taskList = mTaskList.getTasks(); int taskCount = taskList.size(); - for (int i = 0; i < taskCount; i++) { + for (int i = taskCount - 1; i >= 0; i--) { Task t = taskList.get(i); - // Remove the task from the list - mTaskList.remove(t); - // Remove it from the group as well, and if it is empty, remove the group - TaskGrouping group = t.group; - group.removeTask(t); - if (group.getTaskCount() == 0) { - removeGroup(group); - } + removeTaskImpl(t); if (mCb != null) { // Notify that a task has been removed mCb.onStackTaskRemoved(this, t, null); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FakeShadowDrawable.java b/packages/SystemUI/src/com/android/systemui/recents/views/FakeShadowDrawable.java index 72f9001..509ad1b 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/FakeShadowDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/FakeShadowDrawable.java @@ -159,9 +159,9 @@ class FakeShadowDrawable extends Drawable { } @Override - public void setColorFilter(ColorFilter cf) { - mCornerShadowPaint.setColorFilter(cf); - mEdgeShadowPaint.setColorFilter(cf); + public void setColorFilter(ColorFilter colorFilter) { + mCornerShadowPaint.setColorFilter(colorFilter); + mEdgeShadowPaint.setColorFilter(colorFilter); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/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..f397bc3 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) { @@ -195,13 +190,14 @@ public class TaskViewHeader extends FrameLayout { } else if (t.applicationIcon != null) { mApplicationIcon.setImageDrawable(t.applicationIcon); } - mApplicationIcon.setContentDescription(t.activityLabel); + mApplicationIcon.setContentDescription(t.contentDescription); if (!mActivityDescription.getText().toString().equals(t.activityLabel)) { mActivityDescription.setText(t.activityLabel); } + mActivityDescription.setContentDescription(t.contentDescription); + // Try and apply the system ui tint - int existingBgColor = (getBackground() instanceof ColorDrawable) ? - ((ColorDrawable) getBackground()).getColor() : 0; + int existingBgColor = getBackgroundColor(); if (existingBgColor != t.colorPrimary) { mBackgroundColorDrawable.setColor(t.colorPrimary); mBackgroundColor = t.colorPrimary; @@ -213,7 +209,44 @@ public class TaskViewHeader extends FrameLayout { mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ? mLightDismissDrawable : mDarkDismissDrawable); mDismissButton.setContentDescription(String.format(mDismissContentDescription, - t.activityLabel)); + t.contentDescription)); + mMoveTaskButton.setVisibility((mConfig.multiStackEnabled) ? View.VISIBLE : View.INVISIBLE); + if (mConfig.multiStackEnabled) { + updateResizeTaskBarIcon(t); + } + } + + /** Updates the resize task bar button. */ + void updateResizeTaskBarIcon(Task t) { + Rect display = mSsp.getWindowRect(); + Rect taskRect = mSsp.getTaskBounds(t.key.stackId); + int resId = R.drawable.star; + if (display.equals(taskRect) || taskRect.isEmpty()) { + resId = R.drawable.vector_drawable_place_fullscreen; + } else { + boolean top = display.top == taskRect.top; + boolean bottom = display.bottom == taskRect.bottom; + boolean left = display.left == taskRect.left; + boolean right = display.right == taskRect.right; + if (top && bottom && left) { + resId = R.drawable.vector_drawable_place_left; + } else if (top && bottom && right) { + resId = R.drawable.vector_drawable_place_right; + } else if (top && left && right) { + resId = R.drawable.vector_drawable_place_top; + } else if (bottom && left && right) { + resId = R.drawable.vector_drawable_place_bottom; + } else if (top && right) { + resId = R.drawable.vector_drawable_place_top_right; + } else if (top && left) { + resId = R.drawable.vector_drawable_place_top_left; + } else if (bottom && right) { + resId = R.drawable.vector_drawable_place_bottom_right; + } else if (bottom && left) { + resId = R.drawable.vector_drawable_place_bottom_left; + } + } + mMoveTaskButton.setImageResource(resId); } /** Unbinds the bar view from the task */ @@ -284,23 +317,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 +363,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..715f4e4 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -25,6 +25,7 @@ import android.app.Notification; import android.app.Notification.BigPictureStyle; import android.app.NotificationManager; import android.app.PendingIntent; +import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; @@ -122,7 +123,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( @@ -242,7 +243,12 @@ class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Voi sharingIntent.putExtra(Intent.EXTRA_STREAM, uri); sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject); - Intent chooserIntent = Intent.createChooser(sharingIntent, null); + final PendingIntent callback = PendingIntent.getBroadcast(context, 0, + new Intent(context, GlobalScreenshot.TargetChosenReceiver.class) + .putExtra(GlobalScreenshot.CANCEL_ID, mNotificationId), + PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT); + Intent chooserIntent = Intent.createChooser(sharingIntent, null, + callback.getIntentSender()); chooserIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); @@ -341,7 +347,8 @@ class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Voi class GlobalScreenshot { private static final String TAG = "GlobalScreenshot"; - private static final int SCREENSHOT_NOTIFICATION_ID = 789; + static final String CANCEL_ID = "android:cancel_id"; + 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 +471,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 +732,30 @@ class GlobalScreenshot { .setVisibility(Notification.VISIBILITY_PUBLIC) // ok to show outside lockscreen .setCategory(Notification.CATEGORY_ERROR) .setAutoCancel(true) - .setColor(context.getResources().getColor( + .setColor(context.getColor( com.android.internal.R.color.system_notification_accent_color)); Notification n = new Notification.BigTextStyle(b) .bigText(r.getString(R.string.screenshot_failed_text)) .build(); - nManager.notify(SCREENSHOT_NOTIFICATION_ID, n); + nManager.notify(R.id.notification_screenshot, n); + } + + /** + * Removes the notification for a screenshot after a share target is chosen. + */ + public static class TargetChosenReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (!intent.hasExtra(CANCEL_ID)) { + return; + } + + final NotificationManager nm = + (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + + final int id = intent.getIntExtra(CANCEL_ID, 0); + nm.cancel(id); + } } } 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..26c3b4e 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,34 +71,35 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewAnimationUtils; import android.view.ViewGroup; -import android.view.ViewGroup.LayoutParams; import android.view.ViewParent; -import android.view.ViewStub; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityManager; import android.view.animation.AnimationUtils; import android.widget.DateTimeView; import android.widget.ImageView; -import android.widget.LinearLayout; import android.widget.RemoteViews; import android.widget.TextView; +import android.widget.Toast; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.statusbar.StatusBarIconList; import com.android.internal.util.NotificationColorUtil; import com.android.internal.widget.LockPatternUtils; +import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.R; import com.android.systemui.RecentsComponent; -import com.android.systemui.SearchPanelView; import com.android.systemui.SwipeHelper; import com.android.systemui.SystemUI; +import com.android.systemui.recents.Recents; import com.android.systemui.statusbar.NotificationData.Entry; import com.android.systemui.statusbar.phone.NavigationBarView; +import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; -import com.android.systemui.statusbar.policy.HeadsUpNotificationView; +import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.PreviewInflater; +import com.android.systemui.statusbar.policy.RemoteInputView; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import java.util.ArrayList; @@ -116,6 +119,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 +131,6 @@ public abstract class BaseStatusBar extends SystemUI implements protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023; protected static final int MSG_SHOW_NEXT_AFFILIATED_TASK = 1024; protected static final int MSG_SHOW_PREV_AFFILIATED_TASK = 1025; - protected static final int MSG_CLOSE_SEARCH_PANEL = 1027; - protected static final int MSG_SHOW_HEADS_UP = 1028; - protected static final int MSG_HIDE_HEADS_UP = 1029; - protected static final int MSG_ESCALATE_HEADS_UP = 1030; - protected static final int MSG_DECAY_HEADS_UP = 1031; protected static final boolean ENABLE_HEADS_UP = true; // scores above this threshold should be displayed in heads up mode. @@ -137,10 +140,6 @@ public abstract class BaseStatusBar extends SystemUI implements // Should match the value in PhoneWindowManager public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps"; - public static final int EXPANDED_LEAVE_ALONE = -10000; - public static final int EXPANDED_FULL_OPEN = -10001; - - private static final int HIDDEN_NOTIFICATION_ID = 10000; private static final String BANNER_ACTION_CANCEL = "com.android.systemui.statusbar.banner_action_cancel"; private static final String BANNER_ACTION_SETUP = @@ -154,12 +153,10 @@ public abstract class BaseStatusBar extends SystemUI implements protected NotificationData mNotificationData; protected NotificationStackScrollLayout mStackScroller; - // for heads up notifications - protected HeadsUpNotificationView mHeadsUpNotificationView; - protected int mHeadsUpNotificationDecay; + protected NotificationGroupManager mGroupManager = new NotificationGroupManager(); - // Search panel - protected SearchPanelView mSearchPanelView; + // for heads up notifications + protected HeadsUpManager mHeadsUpManager; protected int mCurrentUserId = 0; final protected SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>(); @@ -379,6 +376,23 @@ public abstract class BaseStatusBar extends SystemUI implements userSwitched(mCurrentUserId); } else if (Intent.ACTION_USER_ADDED.equals(action)) { updateCurrentProfilesCache(); + } else if (Intent.ACTION_USER_PRESENT.equals(action)) { + List<ActivityManager.RecentTaskInfo> recentTask = null; + try { + recentTask = ActivityManagerNative.getDefault().getRecentTasks(1, + ActivityManager.RECENT_WITH_EXCLUDED + | ActivityManager.RECENT_INCLUDE_PROFILES, + mCurrentUserId); + } catch (RemoteException e) { + // Abandon hope activity manager not running. + } + if (recentTask != null && recentTask.size() > 0) { + UserInfo user = mUserManager.getUserInfo(recentTask.get(0).userId); + if (user != null && user.isManagedProfile()) { + Toast.makeText(mContext, R.string.managed_profile_foreground_toast, + Toast.LENGTH_SHORT).show(); + } + } } else if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals( action)) { mUsersAllowingPrivateNotifications.clear(); @@ -387,7 +401,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 +428,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 +438,68 @@ public abstract class BaseStatusBar extends SystemUI implements public void onNotificationPosted(final StatusBarNotification sbn, final RankingMap rankingMap) { if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn); - mHandler.post(new Runnable() { - @Override - public void run() { - Notification n = sbn.getNotification(); - boolean isUpdate = mNotificationData.get(sbn.getKey()) != null - || isHeadsUp(sbn.getKey()); - - // Ignore children of notifications that have a summary, since we're not - // going to show them anyway. This is true also when the summary is canceled, - // because children are automatically canceled by NoMan in that case. - if (n.isGroupChild() && - mNotificationData.isGroupWithSummary(sbn.getGroupKey())) { - if (DEBUG) { - Log.d(TAG, "Ignoring group child due to existing summary: " + sbn); - } + if (sbn != null) { + mHandler.post(new Runnable() { + @Override + public void run() { + processForRemoteInput(sbn.getNotification()); + String key = sbn.getKey(); + boolean isUpdate = mNotificationData.get(key) != null; + + // In case we don't allow child notifications, we ignore children of + // notifications that have a summary, since we're not going to show them + // anyway. This is true also when the summary is canceled, + // because children are automatically canceled by NoMan in that case. + if (!ENABLE_CHILD_NOTIFICATIONS + && mGroupManager.isChildInGroupWithSummary(sbn)) { + if (DEBUG) { + Log.d(TAG, "Ignoring group child due to existing summary: " + sbn); + } - // Remove existing notification to avoid stale data. + // Remove existing notification to avoid stale data. + if (isUpdate) { + removeNotification(key, rankingMap); + } else { + mNotificationData.updateRanking(rankingMap); + } + return; + } if (isUpdate) { - removeNotification(sbn.getKey(), rankingMap); + updateNotification(sbn, rankingMap); } else { - mNotificationData.updateRanking(rankingMap); + addNotification(sbn, rankingMap, null /* oldEntry */); } - return; - } - if (isUpdate) { - updateNotification(sbn, rankingMap); - } else { - addNotification(sbn, rankingMap); } - } - }); + }); + } } @Override - public void onNotificationRemoved(final StatusBarNotification sbn, + public void onNotificationRemoved(StatusBarNotification sbn, final RankingMap rankingMap) { if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn); - mHandler.post(new Runnable() { - @Override - public void run() { - removeNotification(sbn.getKey(), rankingMap); - } - }); + if (sbn != null) { + final String key = sbn.getKey(); + mHandler.post(new Runnable() { + @Override + public void run() { + removeNotification(key, rankingMap); + } + }); + } } @Override public void onNotificationRankingUpdate(final RankingMap rankingMap) { if (DEBUG) Log.d(TAG, "onRankingUpdate"); + if (rankingMap != null) { mHandler.post(new Runnable() { @Override public void run() { updateNotificationRanking(rankingMap); } }); - } + } } }; @@ -511,7 +532,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 +552,7 @@ public abstract class BaseStatusBar extends SystemUI implements mBarService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); - mRecents = getComponent(RecentsComponent.class); + mRecents = getComponent(Recents.class); mRecents.setCallback(this); final Configuration currentConfig = mContext.getResources().getConfiguration(); @@ -561,7 +581,8 @@ public abstract class BaseStatusBar extends SystemUI implements createAndAddWindows(); - disable(switches[0], false /* animate */); + mSettingsObserver.onChange(false); // set up + disable(switches[0], switches[6], false /* animate */); setSystemUiVisibility(switches[1], 0xffffffff); topAppWindowChanged(switches[2] != 0); // StatusBarManagerService has a back up of IME token and it's restored here. @@ -605,6 +626,7 @@ public abstract class BaseStatusBar extends SystemUI implements IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_USER_SWITCHED); filter.addAction(Intent.ACTION_USER_ADDED); + filter.addAction(Intent.ACTION_USER_PRESENT); filter.addAction(BANNER_ACTION_CANCEL); filter.addAction(BANNER_ACTION_SETUP); filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); @@ -618,7 +640,7 @@ public abstract class BaseStatusBar extends SystemUI implements Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 1)) { Log.d(TAG, "user hasn't seen notification about hidden notifications"); final LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext); - if (!lockPatternUtils.isSecure()) { + if (!lockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())) { Log.d(TAG, "insecure lockscreen, skipping notification"); Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0); @@ -647,7 +669,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 +680,7 @@ public abstract class BaseStatusBar extends SystemUI implements NotificationManager noMan = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); - noMan.notify(HIDDEN_NOTIFICATION_ID, note.build()); + noMan.notify(R.id.notification_hidden, note.build()); } } @@ -666,15 +688,7 @@ public abstract class BaseStatusBar extends SystemUI implements setHeadsUpUser(newUserId); } - private void setHeadsUpUser(int newUserId) { - if (mHeadsUpNotificationView != null) { - mHeadsUpNotificationView.setUser(newUserId); - } - } - - public boolean isHeadsUp(String key) { - return mHeadsUpNotificationView != null && mHeadsUpNotificationView.isShowing(key); - } + protected abstract void setHeadsUpUser(int newUserId); @Override // NotificationData.Environment public boolean isNotificationForCurrentProfiles(StatusBarNotification n) { @@ -687,6 +701,14 @@ public abstract class BaseStatusBar extends SystemUI implements return isCurrentProfile(notificationUserId); } + protected void setNotificationShown(StatusBarNotification n) { + mNotificationListener.setNotificationsShown(new String[] { n.getKey() }); + } + + protected void setNotificationsShown(String[] keys) { + mNotificationListener.setNotificationsShown(keys); + } + protected boolean isCurrentProfile(int userId) { synchronized (mCurrentProfiles) { return userId == UserHandle.USER_ALL || mCurrentProfiles.get(userId) != null; @@ -698,6 +720,11 @@ public abstract class BaseStatusBar extends SystemUI implements return null; } + @Override + public NotificationGroupManager getGroupManager() { + return mGroupManager; + } + /** * Takes the necessary steps to prepare the status bar for starting an activity, then starts it. * @param action A dismiss action that is called if it's safe to start the activity. @@ -727,8 +754,7 @@ public abstract class BaseStatusBar extends SystemUI implements protected View updateNotificationVetoButton(View row, StatusBarNotification n) { View vetoButton = row.findViewById(R.id.veto); - if (n.isClearable() || (mHeadsUpNotificationView.getEntry() != null - && mHeadsUpNotificationView.getEntry().row == row)) { + if (n.isClearable()) { final String _pkg = n.getPackageName(); final String _tag = n.getTag(); final int _id = n.getId(); @@ -758,7 +784,8 @@ public abstract class BaseStatusBar extends SystemUI implements protected void applyColorsAndBackgrounds(StatusBarNotification sbn, NotificationData.Entry entry) { - if (entry.expanded.getId() != com.android.internal.R.id.status_bar_latest_event_content) { + if (entry.getContentView().getId() + != com.android.internal.R.id.status_bar_latest_event_content) { // Using custom RemoteViews if (entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP) { @@ -770,25 +797,22 @@ public abstract class BaseStatusBar extends SystemUI implements final int color = sbn.getNotification().color; if (isMediaNotification(entry)) { entry.row.setTintColor(color == Notification.COLOR_DEFAULT - ? mContext.getResources().getColor( + ? mContext.getColor( R.color.notification_material_background_media_default_color) : color); } } if (entry.icon != null) { - if (entry.targetSdk >= Build.VERSION_CODES.LOLLIPOP) { - entry.icon.setColorFilter(mContext.getResources().getColor(android.R.color.white)); - } else { - entry.icon.setColorFilter(null); - } + entry.icon.setTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP); } } public boolean isMediaNotification(NotificationData.Entry entry) { // TODO: confirm that there's a valid media key - return entry.expandedBig != null && - entry.expandedBig.findViewById(com.android.internal.R.id.media_actions) != null; + return entry.getExpandedContentView() != null && + entry.getExpandedContentView() + .findViewById(com.android.internal.R.id.media_actions) != null; } // The gear button in the guts that links to the app's own notification settings @@ -834,16 +858,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 +942,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; @@ -978,9 +999,6 @@ public abstract class BaseStatusBar extends SystemUI implements } } - public void onHeadsUpDismissed() { - } - @Override public void showRecentApps(boolean triggeredFromAltTab) { int msg = MSG_SHOW_RECENT_APPS; @@ -1031,50 +1049,6 @@ public abstract class BaseStatusBar extends SystemUI implements mHandler.sendEmptyMessage(msg); } - @Override - public void showSearchPanel() { - if (mSearchPanelView != null && mSearchPanelView.isAssistantAvailable()) { - mSearchPanelView.show(true, true); - } - } - - @Override - public void hideSearchPanel() { - int msg = MSG_CLOSE_SEARCH_PANEL; - mHandler.removeMessages(msg); - mHandler.sendEmptyMessage(msg); - } - - protected abstract WindowManager.LayoutParams getSearchLayoutParams( - LayoutParams layoutParams); - - protected void updateSearchPanel() { - // Search Panel - boolean visible = false; - if (mSearchPanelView != null) { - visible = mSearchPanelView.isShowing(); - mWindowManager.removeView(mSearchPanelView); - } - - // Provide SearchPanel with a temporary parent to allow layout params to work. - LinearLayout tmpRoot = new LinearLayout(mContext); - mSearchPanelView = (SearchPanelView) LayoutInflater.from(mContext).inflate( - R.layout.status_bar_search_panel, tmpRoot, false); - mSearchPanelView.setOnTouchListener( - new TouchOutsideListener(MSG_CLOSE_SEARCH_PANEL, mSearchPanelView)); - mSearchPanelView.setVisibility(View.GONE); - boolean vertical = mNavigationBarView != null && mNavigationBarView.isVertical(); - mSearchPanelView.setHorizontal(vertical); - - WindowManager.LayoutParams lp = getSearchLayoutParams(mSearchPanelView.getLayoutParams()); - - mWindowManager.addView(mSearchPanelView, lp); - mSearchPanelView.setBar(this); - if (visible) { - mSearchPanelView.show(true, false); - } - } - protected H createHandler() { return new H(); } @@ -1161,13 +1135,10 @@ public abstract class BaseStatusBar extends SystemUI implements // Do nothing } - public abstract void resetHeadsUpDecayTimer(); - - public abstract void scheduleHeadsUpOpen(); - - public abstract void scheduleHeadsUpClose(); - - public abstract void scheduleHeadsUpEscalation(); + /** + * If there is an active heads-up notification and it has a fullscreen intent, fire it now. + */ + public abstract void maybeEscalateHeadsUp(); /** * Save the current "public" (locked and secure) state of the lockscreen. @@ -1190,14 +1161,15 @@ public abstract class BaseStatusBar extends SystemUI implements } if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) { - final boolean allowed = 0 != Settings.Secure.getIntForUser( + final boolean allowedByUser = 0 != Settings.Secure.getIntForUser( mContext.getContentResolver(), Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle); final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(null /* admin */, userHandle); final boolean allowedByDpm = (dpmFlags & DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS) == 0; - mUsersAllowingPrivateNotifications.append(userHandle, allowed && allowedByDpm); + final boolean allowed = allowedByUser && allowedByDpm; + mUsersAllowingPrivateNotifications.append(userHandle, allowed); return allowed; } @@ -1250,50 +1222,14 @@ public abstract class BaseStatusBar extends SystemUI implements case MSG_SHOW_PREV_AFFILIATED_TASK: showRecentsPreviousAffiliatedTask(); break; - case MSG_CLOSE_SEARCH_PANEL: - if (DEBUG) Log.d(TAG, "closing search panel"); - if (mSearchPanelView != null && mSearchPanelView.isShowing()) { - mSearchPanelView.show(false, true); - } - break; } } } - public class TouchOutsideListener implements View.OnTouchListener { - private int mMsg; - private StatusBarPanel mPanel; - - public TouchOutsideListener(int msg, StatusBarPanel panel) { - mMsg = msg; - mPanel = panel; - } - - public boolean onTouch(View v, MotionEvent ev) { - final int action = ev.getAction(); - if (action == MotionEvent.ACTION_OUTSIDE - || (action == MotionEvent.ACTION_DOWN - && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) { - mHandler.removeMessages(mMsg); - mHandler.sendEmptyMessage(mMsg); - return true; - } - return false; - } - } - protected void workAroundBadLayerDrawableOpacity(View v) { } - private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) { - return inflateViews(entry, parent, false); - } - - protected boolean inflateViewsForHeadsUp(NotificationData.Entry entry, ViewGroup parent) { - return inflateViews(entry, parent, true); - } - - private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent, boolean isHeadsUp) { + protected boolean inflateViews(Entry entry, ViewGroup parent) { PackageManager pmUser = getPackageManagerForUser( entry.notification.getUser().getIdentifier()); @@ -1301,12 +1237,7 @@ public abstract class BaseStatusBar extends SystemUI implements final StatusBarNotification sbn = entry.notification; RemoteViews contentView = sbn.getNotification().contentView; RemoteViews bigContentView = sbn.getNotification().bigContentView; - - if (isHeadsUp) { - maxHeight = - mContext.getResources().getDimensionPixelSize(R.dimen.notification_mid_height); - bigContentView = sbn.getNotification().headsUpContentView; - } + RemoteViews headsUpContentView = sbn.getNotification().headsUpContentView; if (contentView == null) { return false; @@ -1342,6 +1273,7 @@ public abstract class BaseStatusBar extends SystemUI implements row = (ExpandableNotificationRow) inflater.inflate(R.layout.status_bar_notification_row, parent, false); row.setExpansionLogger(this, entry.notification.getKey()); + row.setGroupManager(mGroupManager); } workAroundBadLayerDrawableOpacity(row); @@ -1352,17 +1284,17 @@ public abstract class BaseStatusBar extends SystemUI implements // NB: the large icon is now handled entirely by the template // bind the click event to the content area - NotificationContentView expanded = - (NotificationContentView) row.findViewById(R.id.expanded); - NotificationContentView expandedPublic = - (NotificationContentView) row.findViewById(R.id.expandedPublic); + NotificationContentView contentContainer = row.getPrivateLayout(); + NotificationContentView contentContainerPublic = row.getPublicLayout(); row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); + 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); @@ -1371,11 +1303,16 @@ public abstract class BaseStatusBar extends SystemUI implements // set up the adaptive layout View contentViewLocal = null; View bigContentViewLocal = null; + View headsUpContentViewLocal = null; try { - contentViewLocal = contentView.apply(mContext, expanded, + contentViewLocal = contentView.apply(mContext, contentContainer, mOnClickHandler); if (bigContentView != null) { - bigContentViewLocal = bigContentView.apply(mContext, expanded, + bigContentViewLocal = bigContentView.apply(mContext, contentContainer, + mOnClickHandler); + } + if (headsUpContentView != null) { + headsUpContentViewLocal = headsUpContentView.apply(mContext, contentContainer, mOnClickHandler); } } @@ -1387,23 +1324,27 @@ public abstract class BaseStatusBar extends SystemUI implements if (contentViewLocal != null) { contentViewLocal.setIsRootNamespace(true); - expanded.setContractedChild(contentViewLocal); + contentContainer.setContractedChild(contentViewLocal); } if (bigContentViewLocal != null) { bigContentViewLocal.setIsRootNamespace(true); - expanded.setExpandedChild(bigContentViewLocal); + contentContainer.setExpandedChild(bigContentViewLocal); + } + if (headsUpContentViewLocal != null) { + headsUpContentViewLocal.setIsRootNamespace(true); + contentContainer.setHeadsUpChild(headsUpContentViewLocal); } // now the public version View publicViewLocal = null; if (publicNotification != null) { try { - publicViewLocal = publicNotification.contentView.apply(mContext, expandedPublic, - mOnClickHandler); + publicViewLocal = publicNotification.contentView.apply(mContext, + contentContainerPublic, mOnClickHandler); if (publicViewLocal != null) { publicViewLocal.setIsRootNamespace(true); - expandedPublic.setContractedChild(publicViewLocal); + contentContainerPublic.setContractedChild(publicViewLocal); } } catch (RuntimeException e) { @@ -1425,9 +1366,9 @@ public abstract class BaseStatusBar extends SystemUI implements // Add a basic notification template publicViewLocal = LayoutInflater.from(mContext).inflate( R.layout.notification_public_default, - expandedPublic, false); + contentContainerPublic, false); publicViewLocal.setIsRootNamespace(true); - expandedPublic.setContractedChild(publicViewLocal); + contentContainerPublic.setContractedChild(publicViewLocal); final TextView title = (TextView) publicViewLocal.findViewById(R.id.title); try { @@ -1506,9 +1447,7 @@ public abstract class BaseStatusBar extends SystemUI implements entry.row = row; entry.row.setHeightRange(mRowMinHeight, maxHeight); entry.row.setOnActivatedListener(this); - entry.expanded = contentViewLocal; - entry.expandedPublic = publicViewLocal; - entry.setBigContentView(bigContentViewLocal); + entry.row.setExpandable(bigContentViewLocal != null); applyColorsAndBackgrounds(sbn, entry); @@ -1520,23 +1459,117 @@ 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; + 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 + if (remoteInput != null) { + View bigContentView = entry.getExpandedContentView(); + if (bigContentView != null) { + inflateRemoteInput(bigContentView, remoteInput, actions); + } + View headsUpContentView = entry.getHeadsUpContentView(); + if (headsUpContentView != null) { + inflateRemoteInput(headsUpContentView, remoteInput, actions); + } + } + + } + + private void inflateRemoteInput(View view, RemoteInput remoteInput, + Notification.Action[] actions) { + View actionContainerCandidate = view.findViewById(com.android.internal.R.id.actions); + if (actionContainerCandidate instanceof ViewGroup) { + ViewGroup actionContainer = (ViewGroup) actionContainerCandidate; + 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 +1582,12 @@ public abstract class BaseStatusBar extends SystemUI implements mCurrentUserId); dismissKeyguardThenExecute(new OnDismissAction() { public boolean onDismiss() { - if (mIsHeadsUp) { + if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(mNotificationKey)) { // Release the HUN notification to the shade. // // In most cases, when FLAG_AUTO_CANCEL is set, the notification will // become canceled shortly by NoMan, but we can't assume that. - mHeadsUpNotificationView.releaseAndClose(); + mHeadsUpManager.releaseImmediately(mNotificationKey); } new Thread() { @Override @@ -1599,7 +1632,7 @@ public abstract class BaseStatusBar extends SystemUI implements // close the shade if it was open animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, - true /* force */); + true /* force */, true /* delayed */); visibilityChanged(false); return mIntent != null && mIntent.isActivity(); @@ -1611,6 +1644,9 @@ public abstract class BaseStatusBar extends SystemUI implements public void animateCollapsePanels(int flags, boolean force) { } + public void animateCollapsePanels(int flags, boolean force, boolean delayed) { + } + public void overrideActivityPendingAppTransition(boolean keyguardShowing) { if (keyguardShowing) { try { @@ -1693,6 +1729,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 +1760,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 +1800,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 +1861,12 @@ public abstract class BaseStatusBar extends SystemUI implements setShowLockscreenNotifications(show && allowedByDpm); } - protected abstract void haltTicker(); protected abstract void setAreThereNotifications(); protected abstract void updateNotifications(); - protected abstract void tick(StatusBarNotification n, boolean firstTime); - protected abstract void updateExpandedViewPos(int expandedPosition); - protected abstract boolean shouldDisableNavbarGestures(); + public abstract boolean shouldDisableNavbarGestures(); public abstract void addNotification(StatusBarNotification notification, - RankingMap ranking); + RankingMap ranking, Entry oldEntry); protected abstract void updateNotificationRanking(RankingMap ranking); public abstract void removeNotification(String key, RankingMap ranking); @@ -1829,26 +1874,109 @@ public abstract class BaseStatusBar extends SystemUI implements if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")"); final String key = notification.getKey(); - boolean wasHeadsUp = isHeadsUp(key); - Entry oldEntry; - if (wasHeadsUp) { - oldEntry = mHeadsUpNotificationView.getEntry(); - } else { - oldEntry = mNotificationData.get(key); - } - if (oldEntry == null) { + Entry entry = mNotificationData.get(key); + if (entry == null) { return; } - final StatusBarNotification oldNotification = oldEntry.notification; + Notification n = notification.getNotification(); + if (DEBUG) { + logUpdate(entry, n); + } + boolean applyInPlace = shouldApplyInPlace(entry, n); + boolean shouldInterrupt = shouldInterrupt(notification); + boolean alertAgain = alertAgain(entry, n); + + entry.notification = notification; + mGroupManager.onEntryUpdated(entry, entry.notification); + boolean updateSuccessful = false; + if (applyInPlace) { + if (DEBUG) Log.d(TAG, "reusing notification for key: " + key); + try { + if (entry.icon != null) { + // Update the icon + final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(), + notification.getUser(), + n.icon, + n.iconLevel, + n.number, + n.tickerText); + entry.icon.setNotification(n); + if (!entry.icon.set(ic)) { + handleNotificationError(notification, "Couldn't update icon: " + ic); + return; + } + } + updateNotificationViews(entry, notification); + updateSuccessful = true; + } + catch (RuntimeException e) { + // It failed to apply cleanly. + Log.w(TAG, "Couldn't reapply views for package " + n.contentView.getPackage(), e); + } + } + if (!updateSuccessful) { + if (DEBUG) Log.d(TAG, "not reusing notification for key: " + key); + final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(), + notification.getUser(), + n.icon, + n.iconLevel, + n.number, + n.tickerText); + entry.icon.setNotification(n); + entry.icon.set(ic); + inflateViews(entry, mStackScroller); + } + updateHeadsUp(key, entry, shouldInterrupt, alertAgain); + mNotificationData.updateRanking(ranking); + updateNotifications(); + + // Update the veto button accordingly (and as a result, whether this row is + // swipe-dismissable) + updateNotificationVetoButton(entry.row, notification); + + if (DEBUG) { + // Is this for you? + boolean isForCurrentUser = isNotificationForCurrentProfiles(notification); + Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you"); + } + + setAreThereNotifications(); + } + + protected abstract void updateHeadsUp(String key, Entry entry, boolean shouldInterrupt, + boolean alertAgain); + + private void logUpdate(Entry oldEntry, Notification n) { + StatusBarNotification oldNotification = oldEntry.notification; + Log.d(TAG, "old notification: when=" + oldNotification.getNotification().when + + " ongoing=" + oldNotification.isOngoing() + + " expanded=" + oldEntry.getContentView() + + " contentView=" + oldNotification.getNotification().contentView + + " bigContentView=" + oldNotification.getNotification().bigContentView + + " publicView=" + oldNotification.getNotification().publicVersion + + " rowParent=" + oldEntry.row.getParent()); + Log.d(TAG, "new notification: when=" + n.when + + " ongoing=" + oldNotification.isOngoing() + + " contentView=" + n.contentView + + " bigContentView=" + n.bigContentView + + " publicView=" + n.publicVersion); + } + + /** + * @return whether we can just reapply the RemoteViews from a notification in-place when it is + * updated + */ + private boolean shouldApplyInPlace(Entry entry, Notification n) { + StatusBarNotification oldNotification = entry.notification; // XXX: modify when we do something more intelligent with the two content views final RemoteViews oldContentView = oldNotification.getNotification().contentView; - Notification n = notification.getNotification(); final RemoteViews contentView = n.contentView; final RemoteViews oldBigContentView = oldNotification.getNotification().bigContentView; final RemoteViews bigContentView = n.bigContentView; - final RemoteViews oldHeadsUpContentView = oldNotification.getNotification().headsUpContentView; + final RemoteViews oldHeadsUpContentView + = oldNotification.getNotification().headsUpContentView; final RemoteViews headsUpContentView = n.headsUpContentView; final Notification oldPublicNotification = oldNotification.getNotification().publicVersion; final RemoteViews oldPublicContentView = oldPublicNotification != null @@ -1856,34 +1984,15 @@ public abstract class BaseStatusBar extends SystemUI implements final Notification publicNotification = n.publicVersion; final RemoteViews publicContentView = publicNotification != null ? publicNotification.contentView : null; - - if (DEBUG) { - Log.d(TAG, "old notification: when=" + oldNotification.getNotification().when - + " ongoing=" + oldNotification.isOngoing() - + " expanded=" + oldEntry.expanded - + " contentView=" + oldContentView - + " bigContentView=" + oldBigContentView - + " publicView=" + oldPublicContentView - + " rowParent=" + oldEntry.row.getParent()); - Log.d(TAG, "new notification: when=" + n.when - + " ongoing=" + oldNotification.isOngoing() - + " contentView=" + contentView - + " bigContentView=" + bigContentView - + " publicView=" + publicContentView); - } - - // Can we just reapply the RemoteViews in place? - - // 1U is never null - boolean contentsUnchanged = oldEntry.expanded != null + boolean contentsUnchanged = entry.getContentView() != null && contentView.getPackage() != null && oldContentView.getPackage() != null && oldContentView.getPackage().equals(contentView.getPackage()) && oldContentView.getLayoutId() == contentView.getLayoutId(); // large view may be null boolean bigContentsUnchanged = - (oldEntry.getBigContentView() == null && bigContentView == null) - || ((oldEntry.getBigContentView() != null && bigContentView != null) + (entry.getExpandedContentView() == null && bigContentView == null) + || ((entry.getExpandedContentView() != null && bigContentView != null) && bigContentView.getPackage() != null && oldBigContentView.getPackage() != null && oldBigContentView.getPackage().equals(bigContentView.getPackage()) @@ -1902,159 +2011,35 @@ public abstract class BaseStatusBar extends SystemUI implements && oldPublicContentView.getPackage() != null && oldPublicContentView.getPackage().equals(publicContentView.getPackage()) && oldPublicContentView.getLayoutId() == publicContentView.getLayoutId()); - boolean updateTicker = n.tickerText != null - && !TextUtils.equals(n.tickerText, - oldEntry.notification.getNotification().tickerText); - - final boolean shouldInterrupt = shouldInterrupt(notification); - final boolean alertAgain = alertAgain(oldEntry, n); - boolean updateSuccessful = false; - if (contentsUnchanged && bigContentsUnchanged && headsUpContentsUnchanged - && publicUnchanged) { - if (DEBUG) Log.d(TAG, "reusing notification for key: " + key); - oldEntry.notification = notification; - try { - if (oldEntry.icon != null) { - // Update the icon - final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(), - notification.getUser(), - n.icon, - n.iconLevel, - n.number, - n.tickerText); - oldEntry.icon.setNotification(n); - if (!oldEntry.icon.set(ic)) { - handleNotificationError(notification, "Couldn't update icon: " + ic); - return; - } - } - - if (wasHeadsUp) { - if (shouldInterrupt) { - updateHeadsUpViews(oldEntry, notification); - if (alertAgain) { - resetHeadsUpDecayTimer(); - } - } else { - // we updated the notification above, so release to build a new shade entry - mHeadsUpNotificationView.releaseAndClose(); - return; - } - } else { - if (shouldInterrupt && alertAgain) { - removeNotificationViews(key, ranking); - addNotification(notification, ranking); //this will pop the headsup - } else { - updateNotificationViews(oldEntry, notification); - } - } - mNotificationData.updateRanking(ranking); - updateNotifications(); - updateSuccessful = true; - } - catch (RuntimeException e) { - // It failed to add cleanly. Log, and remove the view from the panel. - Log.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e); - } - } - if (!updateSuccessful) { - if (DEBUG) Log.d(TAG, "not reusing notification for key: " + key); - if (wasHeadsUp) { - if (shouldInterrupt) { - if (DEBUG) Log.d(TAG, "rebuilding heads up for key: " + key); - Entry newEntry = new Entry(notification, null); - ViewGroup holder = mHeadsUpNotificationView.getHolder(); - if (inflateViewsForHeadsUp(newEntry, holder)) { - mHeadsUpNotificationView.showNotification(newEntry); - if (alertAgain) { - resetHeadsUpDecayTimer(); - } - } else { - Log.w(TAG, "Couldn't create new updated headsup for package " - + contentView.getPackage()); - } - } else { - if (DEBUG) Log.d(TAG, "releasing heads up for key: " + key); - oldEntry.notification = notification; - mHeadsUpNotificationView.releaseAndClose(); - return; - } - } else { - if (shouldInterrupt && alertAgain) { - if (DEBUG) Log.d(TAG, "reposting to invoke heads up for key: " + key); - removeNotificationViews(key, ranking); - addNotification(notification, ranking); //this will pop the headsup - } else { - if (DEBUG) Log.d(TAG, "rebuilding update in place for key: " + key); - oldEntry.notification = notification; - final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(), - notification.getUser(), - n.icon, - n.iconLevel, - n.number, - n.tickerText); - oldEntry.icon.setNotification(n); - oldEntry.icon.set(ic); - inflateViews(oldEntry, mStackScroller, wasHeadsUp); - mNotificationData.updateRanking(ranking); - updateNotifications(); - } - } - } - - // Update the veto button accordingly (and as a result, whether this row is - // swipe-dismissable) - updateNotificationVetoButton(oldEntry.row, notification); - - // Is this for you? - boolean isForCurrentUser = isNotificationForCurrentProfiles(notification); - if (DEBUG) Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you"); - - // Restart the ticker if it's still running - if (updateTicker && isForCurrentUser) { - haltTicker(); - tick(notification, false); - } - - // Recalculate the position of the sliding windows and the titles. - setAreThereNotifications(); - updateExpandedViewPos(EXPANDED_LEAVE_ALONE); - } - - private void updateNotificationViews(NotificationData.Entry entry, - StatusBarNotification notification) { - updateNotificationViews(entry, notification, false); - } - - private void updateHeadsUpViews(NotificationData.Entry entry, - StatusBarNotification notification) { - updateNotificationViews(entry, notification, true); + return contentsUnchanged && bigContentsUnchanged && headsUpContentsUnchanged + && publicUnchanged; } - private void updateNotificationViews(NotificationData.Entry entry, - StatusBarNotification notification, boolean isHeadsUp) { + private void updateNotificationViews(Entry entry, StatusBarNotification notification) { final RemoteViews contentView = notification.getNotification().contentView; - final RemoteViews bigContentView = isHeadsUp - ? notification.getNotification().headsUpContentView - : notification.getNotification().bigContentView; + final RemoteViews bigContentView = notification.getNotification().bigContentView; + final RemoteViews headsUpContentView = notification.getNotification().headsUpContentView; final Notification publicVersion = notification.getNotification().publicVersion; final RemoteViews publicContentView = publicVersion != null ? publicVersion.contentView : null; // Reapply the RemoteViews - contentView.reapply(mContext, entry.expanded, mOnClickHandler); - if (bigContentView != null && entry.getBigContentView() != null) { - bigContentView.reapply(mContext, entry.getBigContentView(), + contentView.reapply(mContext, entry.getContentView(), mOnClickHandler); + if (bigContentView != null && entry.getExpandedContentView() != null) { + bigContentView.reapply(mContext, entry.getExpandedContentView(), mOnClickHandler); } + View headsUpChild = entry.getHeadsUpContentView(); + if (headsUpContentView != null && headsUpChild != null) { + headsUpContentView.reapply(mContext, headsUpChild, mOnClickHandler); + } if (publicContentView != null && entry.getPublicContentView() != null) { publicContentView.reapply(mContext, entry.getPublicContentView(), mOnClickHandler); } // update the contentIntent final PendingIntent contentIntent = notification.getNotification().contentIntent; if (contentIntent != null) { - final View.OnClickListener listener = makeClicker(contentIntent, notification.getKey(), - isHeadsUp); + final View.OnClickListener listener = makeClicker(contentIntent, notification.getKey()); entry.row.setOnClickListener(listener); } else { entry.row.setOnClickListener(null); @@ -2062,12 +2047,12 @@ public abstract class BaseStatusBar extends SystemUI implements entry.row.setStatusBarNotification(notification); entry.row.notifyContentUpdated(); entry.row.resetHeight(); + + applyRemoteInput(entry); } - protected void notifyHeadsUpScreenOn(boolean screenOn) { - if (!screenOn) { - scheduleHeadsUpEscalation(); - } + protected void notifyHeadsUpScreenOff() { + maybeEscalateHeadsUp(); } private boolean alertAgain(Entry oldEntry, Notification newNotification) { @@ -2083,7 +2068,7 @@ public abstract class BaseStatusBar extends SystemUI implements return false; } - if (mHeadsUpNotificationView.isSnoozed(sbn.getPackageName())) { + if (isSnoozedPackage(sbn)) { return false; } @@ -2117,6 +2102,8 @@ public abstract class BaseStatusBar extends SystemUI implements return interrupt; } + protected abstract boolean isSnoozedPackage(StatusBarNotification sbn); + public void setInteracting(int barWindow, boolean interacting) { // hook for subclasses } @@ -2133,9 +2120,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..4542054 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; @@ -79,7 +83,7 @@ public class CommandQueue extends IStatusBar.Stub { public void updateIcon(String slot, int index, int viewIndex, StatusBarIcon old, StatusBarIcon icon); public void removeIcon(String slot, int index, int viewIndex); - public void disable(int state, boolean animate); + public void disable(int state1, int state2, boolean animate); public void animateExpandNotificationsPanel(); public void animateCollapsePanels(int flags); public void animateExpandSettingsPanel(); @@ -92,13 +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) { @@ -122,10 +127,10 @@ public class CommandQueue extends IStatusBar.Stub { } } - public void disable(int state) { + public void disable(int state1, int state2) { synchronized (mList) { mHandler.removeMessages(MSG_DISABLE); - mHandler.obtainMessage(MSG_DISABLE, state, 0, null).sendToTarget(); + mHandler.obtainMessage(MSG_DISABLE, state1, state2, null).sendToTarget(); } } @@ -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; @@ -277,7 +304,7 @@ public class CommandQueue extends IStatusBar.Stub { break; } case MSG_DISABLE: - mCallbacks.disable(msg.arg1, true /* animate */); + mCallbacks.disable(msg.arg1, msg.arg2, true /* animate */); break; case MSG_EXPAND_NOTIFICATIONS: mCallbacks.animateExpandNotificationsPanel(); @@ -328,6 +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..9ef495d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -16,9 +16,13 @@ package com.android.systemui.statusbar; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; import android.content.Context; import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.AnimationDrawable; +import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.service.notification.StatusBarNotification; import android.util.AttributeSet; @@ -26,12 +30,25 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewStub; import android.view.accessibility.AccessibilityEvent; +import android.view.animation.LinearInterpolator; import android.widget.ImageView; + import com.android.systemui.R; +import com.android.systemui.statusbar.phone.NotificationGroupManager; +import com.android.systemui.statusbar.phone.PhoneStatusBar; +import com.android.systemui.statusbar.stack.NotificationChildrenContainer; +import com.android.systemui.statusbar.stack.StackScrollState; +import com.android.systemui.statusbar.stack.StackStateAnimator; +import com.android.systemui.statusbar.stack.StackViewState; + +import java.util.List; public class ExpandableNotificationRow extends ActivatableNotificationView { + + private static final int DEFAULT_DIVIDER_ALPHA = 0x29; + private static final int COLORED_DIVIDER_ALPHA = 0x7B; + private final LinearInterpolator mLinearInterpolator = new LinearInterpolator(); private int mRowMinHeight; - private int mRowMaxHeight; /** Does this row contain layouts that can adapt to row expansion */ private boolean mExpandable; @@ -45,7 +62,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { private boolean mShowingPublic; private boolean mSensitive; private boolean mShowingPublicInitialized; - private boolean mShowingPublicForIntrinsicHeight; + private boolean mHideSensitiveForIntrinsicHeight; /** * Is this notification expanded by the system. The expansion state can be overridden by the @@ -61,15 +78,45 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { private NotificationContentView mPublicLayout; private NotificationContentView mPrivateLayout; private int mMaxExpandHeight; + private int mHeadsUpHeight; private View mVetoButton; private boolean mClearable; private ExpansionLogger mLogger; private String mLoggingKey; private boolean mWasReset; private NotificationGuts mGuts; - private StatusBarNotification mStatusBarNotification; private boolean mIsHeadsUp; + private View mExpandButton; + private View mExpandButtonDivider; + private ViewStub mExpandButtonStub; + private ViewStub mChildrenContainerStub; + private NotificationGroupManager mGroupManager; + private View mExpandButtonContainer; + private boolean mChildrenExpanded; + private NotificationChildrenContainer mChildrenContainer; + private ValueAnimator mChildExpandAnimator; + private float mChildrenExpandProgress; + private float mExpandButtonStart; + private ViewStub mGutsStub; + private boolean mHasExpandAction; + private boolean mIsSystemChildExpanded; + private boolean mIsPinned; + private OnClickListener mExpandClickListener = new OnClickListener() { + @Override + public void onClick(View v) { + mGroupManager.setGroupExpanded(mStatusBarNotification, + !mChildrenExpanded); + } + }; + + public NotificationContentView getPrivateLayout() { + return mPrivateLayout; + } + + public NotificationContentView getPublicLayout() { + return mPublicLayout; + } public void setIconAnimationRunning(boolean running) { setIconAnimationRunning(running, mPublicLayout); @@ -80,8 +127,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { if (layout != null) { View contractedChild = layout.getContractedChild(); View expandedChild = layout.getExpandedChild(); + View headsUpChild = layout.getHeadsUpChild(); setIconAnimationRunningForChild(running, contractedChild); setIconAnimationRunningForChild(running, expandedChild); + setIconAnimationRunningForChild(running, headsUpChild); } } @@ -119,14 +168,137 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { public void setStatusBarNotification(StatusBarNotification statusBarNotification) { mStatusBarNotification = statusBarNotification; updateVetoButton(); + updateExpandButton(); } public StatusBarNotification getStatusBarNotification() { return mStatusBarNotification; } + public boolean isHeadsUp() { + return mIsHeadsUp; + } + public void setHeadsUp(boolean isHeadsUp) { + int intrinsicBefore = getIntrinsicHeight(); mIsHeadsUp = isHeadsUp; + mPrivateLayout.setHeadsUp(isHeadsUp); + if (intrinsicBefore != getIntrinsicHeight()) { + notifyHeightChanged(false /* needsAnimation */); + } + } + + public void setGroupManager(NotificationGroupManager groupManager) { + mGroupManager = groupManager; + } + + public void addChildNotification(ExpandableNotificationRow row) { + addChildNotification(row, -1); + } + + /** + * Add a child notification to this view. + * + * @param row the row to add + * @param childIndex the index to add it at, if -1 it will be added at the end + */ + public void addChildNotification(ExpandableNotificationRow row, int childIndex) { + if (mChildrenContainer == null) { + mChildrenContainerStub.inflate(); + } + mChildrenContainer.addNotification(row, childIndex); + } + + public void removeChildNotification(ExpandableNotificationRow row) { + if (mChildrenContainer != null) { + mChildrenContainer.removeNotification(row); + } + } + + @Override + public boolean areChildrenExpanded() { + return mChildrenExpanded; + } + + public List<ExpandableNotificationRow> getNotificationChildren() { + return mChildrenContainer == null ? null : mChildrenContainer.getNotificationChildren(); + } + + /** + * Apply the order given in the list to the children. + * + * @param childOrder the new list order + * @return whether the list order has changed + */ + public boolean applyChildOrder(List<ExpandableNotificationRow> childOrder) { + return mChildrenContainer != null && mChildrenContainer.applyChildOrder(childOrder); + } + + public void getChildrenStates(StackScrollState resultState) { + if (mChildrenExpanded) { + StackViewState parentState = resultState.getViewStateForView(this); + mChildrenContainer.getState(resultState, parentState); + } + } + + public void applyChildrenState(StackScrollState state) { + if (mChildrenExpanded) { + mChildrenContainer.applyState(state); + } + } + + public void prepareExpansionChanged(StackScrollState state) { + if (mChildrenExpanded) { + mChildrenContainer.prepareExpansionChanged(state); + } + } + + public void startChildAnimation(StackScrollState finalState, + StackStateAnimator stateAnimator, boolean withDelays, long delay, long duration) { + if (mChildrenExpanded) { + mChildrenContainer.startAnimationToState(finalState, stateAnimator, withDelays, delay, + duration); + } + } + + public ExpandableNotificationRow getViewAtPosition(float y) { + if (!mChildrenExpanded) { + return this; + } else { + ExpandableNotificationRow view = mChildrenContainer.getViewAtPosition(y); + return view == null ? this : view; + } + } + + public NotificationGuts getGuts() { + return mGuts; + } + + protected int calculateContentHeightFromActualHeight(int actualHeight) { + int realActualHeight = actualHeight; + if (hasBottomDecor()) { + realActualHeight -= getBottomDecorHeight(); + } + realActualHeight = Math.max(getMinHeight(), realActualHeight); + return realActualHeight; + } + + /** + * Set this notification to be pinned to the top if {@link #isHeadsUp()} is true. By doing this + * the notification will be rendered on top of the screen. + * + * @param pinned whether it is pinned + */ + public void setPinned(boolean pinned) { + mIsPinned = pinned; + } + + public boolean isPinned() { + return mIsPinned; + } + + public int getHeadsUpHeight() { + return mHeadsUpHeight; } public interface ExpansionLogger { @@ -145,7 +317,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { super.reset(); mRowMinHeight = 0; final boolean wasExpanded = isExpanded(); - mRowMaxHeight = 0; + mMaxViewHeight = 0; mExpandable = false; mHasUserChangedExpansion = false; mUserLocked = false; @@ -165,6 +337,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { resetActualHeight(); } mMaxExpandHeight = 0; + mHeadsUpHeight = 0; mWasReset = true; onHeightReset(); requestLayout(); @@ -180,21 +353,97 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { super.onFinishInflate(); mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic); mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded); - ViewStub gutsStub = (ViewStub) findViewById(R.id.notification_guts_stub); - gutsStub.setOnInflateListener(new ViewStub.OnInflateListener() { + mGutsStub = (ViewStub) findViewById(R.id.notification_guts_stub); + mGutsStub.setOnInflateListener(new ViewStub.OnInflateListener() { @Override public void onInflate(ViewStub stub, View inflated) { mGuts = (NotificationGuts) inflated; mGuts.setClipTopAmount(getClipTopAmount()); mGuts.setActualHeight(getActualHeight()); + mGutsStub = null; + } + }); + mExpandButtonStub = (ViewStub) findViewById(R.id.more_button_stub); + mExpandButtonStub.setOnInflateListener(new ViewStub.OnInflateListener() { + + @Override + public void onInflate(ViewStub stub, View inflated) { + mExpandButtonContainer = inflated; + mExpandButton = inflated.findViewById(R.id.notification_expand_button); + mExpandButtonDivider = inflated.findViewById(R.id.notification_expand_divider); + mExpandButtonContainer.setOnClickListener(mExpandClickListener); + } + }); + mChildrenContainerStub = (ViewStub) findViewById(R.id.child_container_stub); + mChildrenContainerStub.setOnInflateListener(new ViewStub.OnInflateListener() { + + @Override + public void onInflate(ViewStub stub, View inflated) { + mChildrenContainer = (NotificationChildrenContainer) inflated; + mChildrenContainer.setCollapseClickListener(mExpandClickListener); + updateChildrenVisibility(false); } }); mVetoButton = findViewById(R.id.veto); } + public void inflateGuts() { + if (mGuts == null) { + mGutsStub.inflate(); + } + } + + private void updateChildrenVisibility(boolean animated) { + if (mChildrenContainer == null) { + return; + } + if (mChildExpandAnimator != null) { + mChildExpandAnimator.cancel(); + } + float targetProgress = mChildrenExpanded ? 1.0f : 0.0f; + if (animated) { + if (mChildrenExpanded) { + mChildrenContainer.setVisibility(VISIBLE); + } + mExpandButtonStart = mExpandButtonContainer.getTranslationY(); + mChildExpandAnimator = ValueAnimator.ofFloat(mChildrenExpandProgress, targetProgress); + mChildExpandAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + setChildrenExpandProgress((float) animation.getAnimatedValue()); + } + }); + mChildExpandAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mChildExpandAnimator = null; + if (!mChildrenExpanded) { + mChildrenContainer.setVisibility(INVISIBLE); + } + } + }); + mChildExpandAnimator.setInterpolator(mLinearInterpolator); + mChildExpandAnimator.setDuration( + StackStateAnimator.ANIMATION_DURATION_EXPAND_CLICKED); + mChildExpandAnimator.start(); + } else { + setChildrenExpandProgress(targetProgress); + mChildrenContainer.setVisibility(mChildrenExpanded ? VISIBLE : INVISIBLE); + } + } + + private void setChildrenExpandProgress(float progress) { + mChildrenExpandProgress = progress; + updateExpandButtonAppearance(); + NotificationContentView showingLayout = getShowingLayout(); + float alpha = 1.0f - mChildrenExpandProgress; + alpha = PhoneStatusBar.ALPHA_OUT.getInterpolation(alpha); + showingLayout.setAlpha(alpha); + } + @Override - public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) { - if (super.onRequestSendAccessibilityEvent(child, event)) { + public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) { + if (super.onRequestSendAccessibilityEventInternal(child, event)) { // Add a record for the entire layout since its content is somehow small. // The event comes from a leaf view that is interacted with. AccessibilityEvent record = AccessibilityEvent.obtain(); @@ -217,7 +466,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { public void setHeightRange(int rowMinHeight, int rowMaxHeight) { mRowMinHeight = rowMinHeight; - mRowMaxHeight = rowMaxHeight; + mMaxViewHeight = rowMaxHeight; } public boolean isExpandable() { @@ -281,7 +530,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { if (expand != mIsSystemExpanded) { final boolean wasExpanded = isExpanded(); mIsSystemExpanded = expand; - notifyHeightChanged(); + notifyHeightChanged(false /* needsAnimation */); logExpansionEvent(false, wasExpanded); } } @@ -295,7 +544,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { mExpansionDisabled = expansionDisabled; logExpansionEvent(false, wasExpanded); if (wasExpanded != isExpanded()) { - notifyHeightChanged(); + notifyHeightChanged(false /* needsAnimation */); } } } @@ -313,9 +562,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { public void applyExpansionToLayout() { boolean expand = isExpanded(); if (expand && mExpandable) { - setActualHeight(mMaxExpandHeight); + setContentHeight(mMaxExpandHeight); } else { - setActualHeight(mRowMinHeight); + setContentHeight(mRowMinHeight); } } @@ -325,12 +574,34 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { return getActualHeight(); } boolean inExpansionState = isExpanded(); - if (!inExpansionState) { - // not expanded, so we return the collapsed size + int maxContentHeight; + if (mSensitive && mHideSensitiveForIntrinsicHeight) { return mRowMinHeight; + } else if (mIsHeadsUp) { + if (inExpansionState) { + maxContentHeight = Math.max(mMaxExpandHeight, mHeadsUpHeight); + } else { + maxContentHeight = Math.max(mRowMinHeight, mHeadsUpHeight); + } + } else if ((!inExpansionState && !mChildrenExpanded)) { + maxContentHeight = mRowMinHeight; + } else if (mChildrenExpanded) { + maxContentHeight = mChildrenContainer.getIntrinsicHeight(); + } else { + maxContentHeight = getMaxExpandHeight(); } + return maxContentHeight + getBottomDecorHeight(); + } - return mShowingPublicForIntrinsicHeight ? mRowMinHeight : getMaxExpandHeight(); + @Override + protected boolean hasBottomDecor() { + return BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS + && !mIsHeadsUp && mGroupManager.hasGroupChildren(mStatusBarNotification); + } + + @Override + protected boolean canHaveBottomDecor() { + return BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS && !mIsHeadsUp; } /** @@ -343,25 +614,52 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { */ private boolean isExpanded() { return !mExpansionDisabled - && (!hasUserChangedExpansion() && isSystemExpanded() || isUserExpanded()); + && (!hasUserChangedExpansion() && (isSystemExpanded() || isSystemChildExpanded()) + || isUserExpanded()); + } + + private boolean isSystemChildExpanded() { + return mIsSystemChildExpanded; + } + + public void setSystemChildExpanded(boolean expanded) { + mIsSystemChildExpanded = expanded; } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); boolean updateExpandHeight = mMaxExpandHeight == 0 && !mWasReset; - updateMaxExpandHeight(); + updateMaxHeights(); if (updateExpandHeight) { applyExpansionToLayout(); } mWasReset = false; } - private void updateMaxExpandHeight() { + @Override + protected boolean isChildInvisible(View child) { + + // We don't want to layout the ChildrenContainer if this is a heads-up view, otherwise the + // view will get too high and the shadows will be off. + boolean isInvisibleChildContainer = child == mChildrenContainer && mIsHeadsUp; + return super.isChildInvisible(child) || isInvisibleChildContainer; + } + + private void updateMaxHeights() { int intrinsicBefore = getIntrinsicHeight(); - mMaxExpandHeight = mPrivateLayout.getMaxHeight(); + View expandedChild = mPrivateLayout.getExpandedChild(); + if (expandedChild == null) { + expandedChild = mPrivateLayout.getContractedChild(); + } + mMaxExpandHeight = expandedChild.getHeight(); + View headsUpChild = mPrivateLayout.getHeadsUpChild(); + if (headsUpChild == null) { + headsUpChild = mPrivateLayout.getContractedChild(); + } + mHeadsUpHeight = headsUpChild.getHeight(); if (intrinsicBefore != getIntrinsicHeight()) { - notifyHeightChanged(); + notifyHeightChanged(false /* needsAnimation */); } } @@ -370,7 +668,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { } public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) { - mShowingPublicForIntrinsicHeight = mSensitive && hideSensitive; + mHideSensitiveForIntrinsicHeight = hideSensitive; } public void setHideSensitive(boolean hideSensitive, boolean animated, long delay, @@ -428,8 +726,127 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { mVetoButton.setVisibility(isClearable() && !mShowingPublic ? View.VISIBLE : View.GONE); } + public void setChildrenExpanded(boolean expanded, boolean animate) { + mChildrenExpanded = expanded; + updateChildrenVisibility(animate); + } + + public void updateExpandButton() { + boolean hasExpand = hasBottomDecor(); + if (hasExpand != mHasExpandAction) { + if (hasExpand) { + if (mExpandButtonContainer == null) { + mExpandButtonStub.inflate(); + } + mExpandButtonContainer.setVisibility(View.VISIBLE); + updateExpandButtonAppearance(); + updateExpandButtonColor(); + } else if (mExpandButtonContainer != null) { + mExpandButtonContainer.setVisibility(View.GONE); + } + notifyHeightChanged(true /* needsAnimation */); + } + mHasExpandAction = hasExpand; + } + + private void updateExpandButtonAppearance() { + if (mExpandButtonContainer == null) { + return; + } + float expandButtonAlpha = 0.0f; + float expandButtonTranslation = 0.0f; + float containerTranslation = 0.0f; + int minHeight = getMinHeight(); + if (!mChildrenExpanded || mChildExpandAnimator != null) { + int expandActionHeight = getBottomDecorHeight(); + int translationY = getActualHeight() - expandActionHeight; + if (translationY > minHeight) { + containerTranslation = translationY; + expandButtonAlpha = 1.0f; + expandButtonTranslation = 0.0f; + } else { + containerTranslation = minHeight; + float progress = expandActionHeight != 0 + ? (minHeight - translationY) / (float) expandActionHeight + : 1.0f; + expandButtonTranslation = -progress * expandActionHeight * 0.7f; + float alphaProgress = Math.min(progress / 0.7f, 1.0f); + alphaProgress = PhoneStatusBar.ALPHA_OUT.getInterpolation(alphaProgress); + expandButtonAlpha = 1.0f - alphaProgress; + } + } + if (mChildExpandAnimator != null || mChildrenExpanded) { + expandButtonAlpha = (1.0f - mChildrenExpandProgress) + * expandButtonAlpha; + expandButtonTranslation = (1.0f - mChildrenExpandProgress) + * expandButtonTranslation; + float newTranslation = -getBottomDecorHeight(); + + // We don't want to take the actual height of the view as this is already + // interpolated by a custom interpolator leading to a confusing animation. We want + // to have a stable end value to interpolate in between + float collapsedHeight = !mChildrenExpanded + ? Math.max(StackStateAnimator.getFinalActualHeight(this) + - getBottomDecorHeight(), minHeight) + : mExpandButtonStart; + float translationProgress = mFastOutSlowInInterpolator.getInterpolation( + mChildrenExpandProgress); + containerTranslation = (1.0f - translationProgress) * collapsedHeight + + translationProgress * newTranslation; + } + mExpandButton.setAlpha(expandButtonAlpha); + mExpandButtonDivider.setAlpha(expandButtonAlpha); + mExpandButton.setTranslationY(expandButtonTranslation); + mExpandButtonContainer.setTranslationY(containerTranslation); + NotificationContentView showingLayout = getShowingLayout(); + float layoutTranslation = + mExpandButtonContainer.getTranslationY() - showingLayout.getContentHeight(); + layoutTranslation = Math.min(layoutTranslation, 0); + if (!mChildrenExpanded && mChildExpandAnimator == null) { + // Needed for the DragDownHelper in order not to jump there, as the position + // can be negative for a short time. + layoutTranslation = 0; + } + showingLayout.setTranslationY(layoutTranslation); + if (mChildrenContainer != null) { + mChildrenContainer.setTranslationY( + mExpandButtonContainer.getTranslationY() + getBottomDecorHeight()); + } + } + + private void updateExpandButtonColor() { + // TODO: This needs some more baking, currently only the divider is colored according to + // the tint, but legacy black doesn't work yet perfectly for the button etc. + int color = getRippleColor(); + if (color == mNormalRippleColor) { + color = 0; + } + if (mExpandButtonDivider != null) { + applyTint(mExpandButtonDivider, color); + } + if (mChildrenContainer != null) { + mChildrenContainer.setTintColor(color); + } + } + + public static void applyTint(View v, int color) { + int alpha; + if (color != 0) { + alpha = COLORED_DIVIDER_ALPHA; + } else { + color = 0xff000000; + alpha = DEFAULT_DIVIDER_ALPHA; + } + if (v.getBackground() instanceof ColorDrawable) { + ColorDrawable background = (ColorDrawable) v.getBackground(); + background.mutate(); + background.setColor(color); + background.setAlpha(alpha); + } + } + public int getMaxExpandHeight() { - return mShowingPublicForIntrinsicHeight ? mRowMinHeight : mMaxExpandHeight; + return mMaxExpandHeight; } @Override @@ -440,17 +857,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..e632cc8 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,9 +115,10 @@ public abstract class ExpandableView extends FrameLayout { if (!mActualHeightInitialized && mActualHeight == 0) { int initialHeight = getInitialHeight(); if (initialHeight != 0) { - setActualHeight(initialHeight); + setContentHeight(initialHeight); } } + updateClipping(); } /** @@ -140,13 +157,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 +177,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 +288,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 +341,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 +384,9 @@ public abstract class ExpandableView extends FrameLayout { /** * @param view the view for which the height changed, or {@code null} if just the top * padding or the padding between the elements changed + * @param needsAnimation whether the view height needs to be animated */ - void onHeightChanged(ExpandableView view); + void onHeightChanged(ExpandableView view, boolean needsAnimation); /** * Called when the view is reset and therefore the height will change abruptly diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java index 9f0f84e..0fa088b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java @@ -17,7 +17,6 @@ package com.android.systemui.statusbar; import android.animation.Animator; -import android.animation.ValueAnimator; import android.content.Context; import android.view.ViewPropertyAnimator; import android.view.animation.AnimationUtils; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java index e2464c2..9ccff72 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java @@ -34,6 +34,7 @@ import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import android.widget.ImageView; import com.android.systemui.R; +import com.android.systemui.statusbar.phone.KeyguardAffordanceHelper; /** * An ImageView which does not have overlapping renderings commands and therefore does not need a @@ -75,6 +76,7 @@ public class KeyguardAffordanceView extends ImageView { private float mCircleStartRadius; private float mMaxCircleSize; private Animator mPreviewClipper; + private float mRestingAlpha = KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT; private AnimatorListenerAdapter mClipEndListener = new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { @@ -202,7 +204,7 @@ public class KeyguardAffordanceView extends ImageView { private void updateCircleColor() { float fraction = 0.5f + 0.5f * Math.max(0.0f, Math.min(1.0f, (mCircleRadius - mMinBackgroundRadius) / (0.5f * mMinBackgroundRadius))); - if (mPreviewView != null) { + if (mPreviewView != null && mPreviewView.getVisibility() == VISIBLE) { float finishingFraction = 1 - Math.max(0, mCircleRadius - mCircleStartRadius) / (mMaxCircleSize - mCircleStartRadius); fraction *= finishingFraction; @@ -394,6 +396,17 @@ public class KeyguardAffordanceView extends ImageView { } } + public void setRestingAlpha(float alpha) { + mRestingAlpha = alpha; + + // TODO: Handle the case an animation is playing. + setImageAlpha(alpha, false); + } + + public float getRestingAlpha() { + return mRestingAlpha; + } + public void setImageAlpha(float alpha, boolean animate) { setImageAlpha(alpha, animate, -1, null, null); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 58067c3..07a055c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -26,6 +26,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.graphics.Color; import android.os.BatteryManager; import android.os.BatteryStats; import android.os.Handler; @@ -53,6 +54,7 @@ public class KeyguardIndicationController { private String mRestingIndication; private String mTransientIndication; + private int mTransientTextColor; private boolean mVisible; private boolean mPowerPluggedIn; @@ -105,7 +107,15 @@ public class KeyguardIndicationController { * Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}. */ public void showTransientIndication(String transientIndication) { + showTransientIndication(transientIndication, Color.WHITE); + } + + /** + * Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}. + */ + public void showTransientIndication(String transientIndication, int textColor) { mTransientIndication = transientIndication; + mTransientTextColor = textColor; mHandler.removeMessages(MSG_HIDE_TRANSIENT); updateIndication(); } @@ -124,7 +134,15 @@ public class KeyguardIndicationController { private void updateIndication() { if (mVisible) { mTextView.switchIndication(computeIndication()); + mTextView.setTextColor(computeColor()); + } + } + + private int computeColor() { + if (!TextUtils.isEmpty(mTransientIndication)) { + return mTransientTextColor; } + return Color.WHITE; } private String computeIndication() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java index 0fc46e9..01aa8d1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java @@ -44,7 +44,7 @@ public class NotificationBackgroundView extends View { } private void draw(Canvas canvas, Drawable drawable) { - if (drawable != null) { + if (drawable != null && mActualHeight > mClipTopAmount) { drawable.setBounds(0, mClipTopAmount, getWidth(), mActualHeight); drawable.draw(canvas); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java index 914b3d8..110b14c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java @@ -17,50 +17,49 @@ 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.ViewGroup; import android.view.ViewTreeObserver; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; import android.widget.FrameLayout; -import android.widget.ImageView; + import com.android.systemui.R; /** - * A frame layout containing the actual payload of the notification, including the contracted and - * expanded layout. This class is responsible for clipping the content and and switching between the - * expanded and contracted view depending on its clipped size. + * A frame layout containing the actual payload of the notification, including the contracted, + * expanded and heads up layout. This class is responsible for clipping the content and and + * switching between the expanded, contracted and the heads up view depending on its clipped size. */ public class NotificationContentView extends FrameLayout { private static final long ANIMATION_DURATION_LENGTH = 170; + private static final int VISIBLE_TYPE_CONTRACTED = 0; + private static final int VISIBLE_TYPE_EXPANDED = 1; + private static final int VISIBLE_TYPE_HEADSUP = 2; private final Rect mClipBounds = new Rect(); + private final int mSmallHeight; + private final int mHeadsUpHeight; + private final Interpolator mLinearInterpolator = new LinearInterpolator(); private View mContractedChild; private View mExpandedChild; + private View mHeadsUpChild; private NotificationViewWrapper mContractedWrapper; - - private int mSmallHeight; private int mClipTopAmount; - private int mActualHeight; - - private final Interpolator mLinearInterpolator = new LinearInterpolator(); - - private boolean mContractedVisible = true; + private int mContentHeight; + private int mVisibleType = VISIBLE_TYPE_CONTRACTED; private boolean mDark; - private final Paint mFadePaint = new Paint(); private boolean mAnimate; + private boolean mIsHeadsUp; private ViewTreeObserver.OnPreDrawListener mEnableAnimationPredrawListener = new ViewTreeObserver.OnPreDrawListener() { @Override @@ -74,10 +73,57 @@ public class NotificationContentView extends FrameLayout { public NotificationContentView(Context context, AttributeSet attrs) { super(context, attrs); mFadePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD)); + mSmallHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height); + mHeadsUpHeight = getResources().getDimensionPixelSize(R.dimen.notification_mid_height); reset(true); } @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY; + boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST; + int maxSize = Integer.MAX_VALUE; + if (hasFixedHeight || isHeightLimited) { + maxSize = MeasureSpec.getSize(heightMeasureSpec); + } + int maxChildHeight = 0; + if (mContractedChild != null) { + int size = Math.min(maxSize, mSmallHeight); + mContractedChild.measure(widthMeasureSpec, + MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY)); + maxChildHeight = Math.max(maxChildHeight, mContractedChild.getMeasuredHeight()); + } + if (mExpandedChild != null) { + int size = maxSize; + ViewGroup.LayoutParams layoutParams = mExpandedChild.getLayoutParams(); + if (layoutParams.height >= 0) { + // An actual height is set + size = Math.min(maxSize, layoutParams.height); + } + int spec = size == Integer.MAX_VALUE + ? MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED) + : MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST); + mExpandedChild.measure(widthMeasureSpec, spec); + maxChildHeight = Math.max(maxChildHeight, mExpandedChild.getMeasuredHeight()); + } + if (mHeadsUpChild != null) { + int size = Math.min(maxSize, mHeadsUpHeight); + ViewGroup.LayoutParams layoutParams = mHeadsUpChild.getLayoutParams(); + if (layoutParams.height >= 0) { + // An actual height is set + size = Math.min(maxSize, layoutParams.height); + } + mHeadsUpChild.measure(widthMeasureSpec, + MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST)); + maxChildHeight = Math.max(maxChildHeight, mHeadsUpChild.getMeasuredHeight()); + } + int ownHeight = Math.min(maxChildHeight, maxSize); + int width = MeasureSpec.getSize(widthMeasureSpec); + setMeasuredDimension(width, ownHeight); + } + + @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); updateClipping(); @@ -96,13 +142,16 @@ public class NotificationContentView extends FrameLayout { if (mExpandedChild != null) { mExpandedChild.animate().cancel(); } + if (mHeadsUpChild != null) { + mHeadsUpChild.animate().cancel(); + } removeAllViews(); mContractedChild = null; mExpandedChild = null; - mSmallHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height); - mContractedVisible = true; + mHeadsUpChild = null; + mVisibleType = VISIBLE_TYPE_CONTRACTED; if (resetActualHeight) { - mActualHeight = mSmallHeight; + mContentHeight = mSmallHeight; } } @@ -114,12 +163,15 @@ public class NotificationContentView extends FrameLayout { return mExpandedChild; } + public View getHeadsUpChild() { + return mHeadsUpChild; + } + public void setContractedChild(View child) { if (mContractedChild != null) { mContractedChild.animate().cancel(); removeView(mContractedChild); } - sanitizeContractedLayoutParams(child); addView(child); mContractedChild = child; mContractedWrapper = NotificationViewWrapper.wrap(getContext(), child); @@ -137,6 +189,16 @@ public class NotificationContentView extends FrameLayout { selectLayout(false /* animate */, true /* force */); } + public void setHeadsUpChild(View child) { + if (mHeadsUpChild != null) { + mHeadsUpChild.animate().cancel(); + removeView(mHeadsUpChild); + } + addView(child); + mHeadsUpChild = child; + selectLayout(false /* animate */, true /* force */); + } + @Override protected void onVisibilityChanged(View changedView, int visibility) { super.onVisibilityChanged(changedView, visibility); @@ -159,16 +221,24 @@ 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 getMaxHeight() { + public int getContentHeight() { + return mContentHeight; + } - // The maximum height is just the laid out height. - return getHeight(); + public int getMaxHeight() { + if (mIsHeadsUp && mHeadsUpChild != null) { + return mHeadsUpChild.getHeight(); + } else if (mExpandedChild != null) { + return mExpandedChild.getHeight(); + } + return mSmallHeight; } public int getMinHeight() { @@ -181,66 +251,105 @@ public class NotificationContentView extends FrameLayout { } private void updateClipping() { - mClipBounds.set(0, mClipTopAmount, getWidth(), mActualHeight); + mClipBounds.set(0, mClipTopAmount, getWidth(), mContentHeight); setClipBounds(mClipBounds); } - private void sanitizeContractedLayoutParams(View contractedChild) { - LayoutParams lp = (LayoutParams) contractedChild.getLayoutParams(); - lp.height = mSmallHeight; - contractedChild.setLayoutParams(lp); - } - private void selectLayout(boolean animate, boolean force) { if (mContractedChild == null) { return; } - boolean showContractedChild = showContractedChild(); - if (showContractedChild != mContractedVisible || force) { - if (animate && mExpandedChild != null) { - runSwitchAnimation(showContractedChild); - } else if (mExpandedChild != null) { - mContractedChild.setVisibility(showContractedChild ? View.VISIBLE : View.INVISIBLE); - mContractedChild.setAlpha(showContractedChild ? 1f : 0f); - mExpandedChild.setVisibility(showContractedChild ? View.INVISIBLE : View.VISIBLE); - mExpandedChild.setAlpha(showContractedChild ? 0f : 1f); + int visibleType = calculateVisibleType(); + if (visibleType != mVisibleType || force) { + if (animate && (visibleType == VISIBLE_TYPE_EXPANDED && mExpandedChild != null) + || (visibleType == VISIBLE_TYPE_HEADSUP && mHeadsUpChild != null) + || visibleType == VISIBLE_TYPE_CONTRACTED) { + runSwitchAnimation(visibleType); + } else { + updateViewVisibilities(visibleType); } + mVisibleType = visibleType; } - mContractedVisible = showContractedChild; } - private void runSwitchAnimation(final boolean showContractedChild) { - mContractedChild.setVisibility(View.VISIBLE); - mExpandedChild.setVisibility(View.VISIBLE); - mContractedChild.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint); - mExpandedChild.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint); + private void updateViewVisibilities(int visibleType) { + boolean contractedVisible = visibleType == VISIBLE_TYPE_CONTRACTED; + mContractedChild.setVisibility(contractedVisible ? View.VISIBLE : View.INVISIBLE); + mContractedChild.setAlpha(contractedVisible ? 1f : 0f); + mContractedChild.setLayerType(LAYER_TYPE_NONE, null); + if (mExpandedChild != null) { + boolean expandedVisible = visibleType == VISIBLE_TYPE_EXPANDED; + mExpandedChild.setVisibility(expandedVisible ? View.VISIBLE : View.INVISIBLE); + mExpandedChild.setAlpha(expandedVisible ? 1f : 0f); + mExpandedChild.setLayerType(LAYER_TYPE_NONE, null); + } + if (mHeadsUpChild != null) { + boolean headsUpVisible = visibleType == VISIBLE_TYPE_HEADSUP; + mHeadsUpChild.setVisibility(headsUpVisible ? View.VISIBLE : View.INVISIBLE); + mHeadsUpChild.setAlpha(headsUpVisible ? 1f : 0f); + mHeadsUpChild.setLayerType(LAYER_TYPE_NONE, null); + } + setLayerType(LAYER_TYPE_NONE, null); + } + + private void runSwitchAnimation(int visibleType) { + View shownView = getViewForVisibleType(visibleType); + View hiddenView = getViewForVisibleType(mVisibleType); + shownView.setVisibility(View.VISIBLE); + hiddenView.setVisibility(View.VISIBLE); + shownView.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint); + hiddenView.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint); setLayerType(LAYER_TYPE_HARDWARE, null); - mContractedChild.animate() - .alpha(showContractedChild ? 1f : 0f) + hiddenView.animate() + .alpha(0f) .setDuration(ANIMATION_DURATION_LENGTH) - .setInterpolator(mLinearInterpolator); - mExpandedChild.animate() - .alpha(showContractedChild ? 0f : 1f) + .setInterpolator(mLinearInterpolator) + .withEndAction(null); // In case we have multiple changes in one frame. + shownView.animate() + .alpha(1f) .setDuration(ANIMATION_DURATION_LENGTH) .setInterpolator(mLinearInterpolator) .withEndAction(new Runnable() { @Override public void run() { - mContractedChild.setLayerType(LAYER_TYPE_NONE, null); - mExpandedChild.setLayerType(LAYER_TYPE_NONE, null); - setLayerType(LAYER_TYPE_NONE, null); - mContractedChild.setVisibility(showContractedChild - ? View.VISIBLE - : View.INVISIBLE); - mExpandedChild.setVisibility(showContractedChild - ? View.INVISIBLE - : View.VISIBLE); + updateViewVisibilities(mVisibleType); } }); } - private boolean showContractedChild() { - return mActualHeight <= mSmallHeight || mExpandedChild == null; + /** + * @param visibleType one of the static enum types in this view + * @return the corresponding view according to the given visible type + */ + private View getViewForVisibleType(int visibleType) { + switch (visibleType) { + case VISIBLE_TYPE_EXPANDED: + return mExpandedChild; + case VISIBLE_TYPE_HEADSUP: + return mHeadsUpChild; + default: + return mContractedChild; + } + } + + /** + * @return one of the static enum types in this view, calculated form the current state + */ + private int calculateVisibleType() { + boolean noExpandedChild = mExpandedChild == null; + if (mIsHeadsUp && mHeadsUpChild != null) { + if (mContentHeight <= mHeadsUpChild.getHeight() || noExpandedChild) { + return VISIBLE_TYPE_HEADSUP; + } else { + return VISIBLE_TYPE_EXPANDED; + } + } else { + if (mContentHeight <= mSmallHeight || noExpandedChild) { + return VISIBLE_TYPE_CONTRACTED; + } else { + return VISIBLE_TYPE_EXPANDED; + } + } } public void notifyContentUpdated() { @@ -261,6 +370,11 @@ public class NotificationContentView extends FrameLayout { mContractedWrapper.setDark(dark, fade, delay); } + public void setHeadsUp(boolean headsUp) { + mIsHeadsUp = headsUp; + selectLayout(false /* animate */, true /* force */); + } + @Override public boolean hasOverlappingRendering() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java index 34c458a..2a8b4ac 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java @@ -22,9 +22,11 @@ import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.StatusBarNotification; import android.util.ArrayMap; -import android.util.ArraySet; import android.view.View; +import com.android.systemui.statusbar.phone.NotificationGroupManager; +import com.android.systemui.statusbar.policy.HeadsUpManager; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; @@ -36,15 +38,13 @@ import java.util.Comparator; public class NotificationData { private final Environment mEnvironment; + private HeadsUpManager mHeadsUpManager; public static final class Entry { public String key; public StatusBarNotification notification; public StatusBarIconView icon; public ExpandableNotificationRow row; // the outer expanded view - public View expanded; // the inflated RemoteViews - public View expandedPublic; // for insecure lockscreens - public View expandedBig; private boolean interruption; public boolean autoRedacted; // whether the redacted notification was generated by us public boolean legacy; // whether the notification has a legacy, dark background @@ -55,14 +55,6 @@ public class NotificationData { this.notification = n; this.icon = ic; } - public void setBigContentView(View bigContentView) { - this.expandedBig = bigContentView; - row.setExpandable(bigContentView != null); - } - public View getBigContentView() { - return expandedBig; - } - public View getPublicContentView() { return expandedPublic; } public void setInterruption() { interruption = true; @@ -78,60 +70,79 @@ public class NotificationData { public void reset() { // NOTE: Icon needs to be preserved for now. // We should fix this at some point. - expanded = null; - expandedPublic = null; - expandedBig = null; autoRedacted = false; legacy = false; if (row != null) { row.reset(); } } + + public View getContentView() { + return row.getPrivateLayout().getContractedChild(); + } + + public View getExpandedContentView() { + return row.getPrivateLayout().getExpandedChild(); + } + + public View getHeadsUpContentView() { + return row.getPrivateLayout().getHeadsUpChild(); + } + + public View getPublicContentView() { + return row.getPublicLayout().getContractedChild(); + } } private final ArrayMap<String, Entry> mEntries = new ArrayMap<>(); private final ArrayList<Entry> mSortedAndFiltered = new ArrayList<>(); - private ArraySet<String> mGroupsWithSummaries = new ArraySet<>(); + + private NotificationGroupManager mGroupManager; private RankingMap mRankingMap; private final Ranking mTmpRanking = new Ranking(); + + public void setHeadsUpManager(HeadsUpManager headsUpManager) { + mHeadsUpManager = headsUpManager; + } + private final Comparator<Entry> mRankingComparator = new Comparator<Entry>() { private final Ranking mRankingA = new Ranking(); private final Ranking mRankingB = new Ranking(); @Override public int compare(Entry a, Entry b) { - // Upsort current media notification. String mediaNotification = mEnvironment.getCurrentMediaNotificationKey(); boolean aMedia = a.key.equals(mediaNotification); boolean bMedia = b.key.equals(mediaNotification); - if (aMedia != bMedia) { - return aMedia ? -1 : 1; - } final StatusBarNotification na = a.notification; final StatusBarNotification nb = b.notification; - // Upsort PRIORITY_MAX system notifications boolean aSystemMax = na.getNotification().priority >= Notification.PRIORITY_MAX && isSystemNotification(na); boolean bSystemMax = nb.getNotification().priority >= Notification.PRIORITY_MAX && isSystemNotification(nb); - if (aSystemMax != bSystemMax) { - return aSystemMax ? -1 : 1; - } + int d = nb.getScore() - na.getScore(); - // RankingMap as received from NoMan. - if (mRankingMap != null) { + boolean isHeadsUp = a.row.isHeadsUp(); + if (isHeadsUp != b.row.isHeadsUp()) { + return isHeadsUp ? -1 : 1; + } else if (isHeadsUp) { + // Provide consistent ranking with headsUpManager + return mHeadsUpManager.compare(a, b); + } else if (aMedia != bMedia) { + // Upsort current media notification. + return aMedia ? -1 : 1; + } else if (aSystemMax != bSystemMax) { + // Upsort PRIORITY_MAX system notifications + return aSystemMax ? -1 : 1; + } else if (mRankingMap != null) { + // RankingMap as received from NoMan mRankingMap.getRanking(a.key, mRankingA); mRankingMap.getRanking(b.key, mRankingB); return mRankingA.getRank() - mRankingB.getRank(); - } - - int d = nb.getScore() - na.getScore(); - if (a.interruption != b.interruption) { - return a.interruption ? -1 : 1; - } else if (d != 0) { + } if (d != 0) { return d; } else { return (int) (nb.getNotification().when - na.getNotification().when); @@ -141,6 +152,7 @@ public class NotificationData { public NotificationData(Environment environment) { mEnvironment = environment; + mGroupManager = environment.getGroupManager(); } /** @@ -163,12 +175,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 +217,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 +227,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 +247,11 @@ public class NotificationData { mEnvironment.shouldHideSensitiveContents(sbn.getUserId())) { return true; } + + if (!BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS + && mGroupManager.isChildInGroupWithSummary(sbn)) { + return true; + } return false; } @@ -262,7 +260,7 @@ public class NotificationData { */ public boolean hasActiveClearableNotifications() { for (Entry e : mSortedAndFiltered) { - if (e.expanded != null) { // the view successfully inflated + if (e.getContentView() != null) { // the view successfully inflated if (e.notification.isClearable()) { return true; } @@ -328,5 +326,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..b2bb021 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java @@ -17,6 +17,11 @@ package com.android.systemui.statusbar; import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.graphics.drawable.Animatable; +import android.graphics.drawable.Drawable; import android.telephony.SubscriptionInfo; import android.util.AttributeSet; import android.util.Log; @@ -55,9 +60,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 +118,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 +130,7 @@ public class SignalClusterView } apply(); + applyIconTint(); } @Override @@ -157,12 +167,13 @@ public class SignalClusterView } @Override - public void setMobileDataIndicators(boolean visible, int strengthIcon, int typeIcon, - String contentDescription, String typeContentDescription, boolean isTypeIconWide, - int subId) { + public void setMobileDataIndicators(boolean visible, int strengthIcon, int darkStrengthIcon, + int typeIcon, String contentDescription, String typeContentDescription, + boolean isTypeIconWide, int subId) { PhoneState state = getOrInflateState(subId); state.mMobileVisible = visible; state.mMobileStrengthId = strengthIcon; + state.mMobileDarkStrengthId = darkStrengthIcon; state.mMobileTypeId = typeIcon; state.mMobileDescription = contentDescription; state.mMobileTypeDescription = typeContentDescription; @@ -187,6 +198,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 +231,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 +239,7 @@ public class SignalClusterView for (PhoneState state : mPhoneStates) { state.populateAccessibilityEvent(event); } - return super.dispatchPopulateAccessibilityEvent(event); + return super.dispatchPopulateAccessibilityEventInternal(event); } @Override @@ -265,6 +279,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,21 +324,51 @@ 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; - private int mMobileStrengthId = 0, mMobileTypeId = 0; + private int mMobileStrengthId = 0, mMobileDarkStrengthId = 0, mMobileTypeId = 0; private boolean mIsMobileTypeIconWide; 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 +380,30 @@ public class SignalClusterView public void setViews(ViewGroup root) { mMobileGroup = root; mMobile = (ImageView) root.findViewById(R.id.mobile_signal); + mMobileDark = (ImageView) root.findViewById(R.id.mobile_signal_dark); mMobileType = (ImageView) root.findViewById(R.id.mobile_type); } public boolean apply(boolean isSecondaryIcon) { if (mMobileVisible && !mIsAirplaneMode) { mMobile.setImageResource(mMobileStrengthId); + Drawable mobileDrawable = mMobile.getDrawable(); + if (mobileDrawable instanceof Animatable) { + Animatable ad = (Animatable) mobileDrawable; + if (!ad.isRunning()) { + ad.start(); + } + } + + mMobileDark.setImageResource(mMobileStrengthId); + Drawable mobileDarkDrawable = mMobileDark.getDrawable(); + if (mobileDarkDrawable instanceof Animatable) { + Animatable ad = (Animatable) mobileDarkDrawable; + if (!ad.isRunning()) { + ad.start(); + } + } + mMobileType.setImageResource(mMobileTypeId); mMobileGroup.setContentDescription(mMobileTypeDescription + " " + mMobileDescription); @@ -354,9 +417,12 @@ 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)); + if (DEBUG) Log.d(TAG, String.format("mobile: %s sig=%d dark=%d typ=%d", + (mMobileVisible ? "VISIBLE" : "GONE"), mMobileStrengthId, + mMobileDarkStrengthId, mMobileTypeId)); mMobileType.setVisibility(mMobileTypeId != 0 ? View.VISIBLE : View.GONE); @@ -369,6 +435,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/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java new file mode 100644 index 0000000..fe7bc97 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.phone; + +import android.content.Context; +import android.view.MotionEvent; +import android.view.ViewConfiguration; + +import com.android.systemui.Gefingerpoken; +import com.android.systemui.statusbar.ExpandableNotificationRow; +import com.android.systemui.statusbar.ExpandableView; +import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; + +/** + * A helper class to handle touches on the heads-up views. + */ +public class HeadsUpTouchHelper implements Gefingerpoken { + + private HeadsUpManager mHeadsUpManager; + private NotificationStackScrollLayout mStackScroller; + private int mTrackingPointer; + private float mTouchSlop; + private float mInitialTouchX; + private float mInitialTouchY; + private boolean mTouchingHeadsUpView; + private boolean mTrackingHeadsUp; + private boolean mCollapseSnoozes; + private NotificationPanelView mPanel; + private ExpandableNotificationRow mPickedChild; + + public HeadsUpTouchHelper(HeadsUpManager headsUpManager, + NotificationStackScrollLayout stackScroller, + NotificationPanelView notificationPanelView) { + mHeadsUpManager = headsUpManager; + mStackScroller = stackScroller; + mPanel = notificationPanelView; + Context context = stackScroller.getContext(); + final ViewConfiguration configuration = ViewConfiguration.get(context); + mTouchSlop = configuration.getScaledTouchSlop(); + } + + public boolean isTrackingHeadsUp() { + return mTrackingHeadsUp; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + if (!mTouchingHeadsUpView && event.getActionMasked() != MotionEvent.ACTION_DOWN) { + return false; + } + int pointerIndex = event.findPointerIndex(mTrackingPointer); + if (pointerIndex < 0) { + pointerIndex = 0; + mTrackingPointer = event.getPointerId(pointerIndex); + } + final float x = event.getX(pointerIndex); + final float y = event.getY(pointerIndex); + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + mInitialTouchY = y; + mInitialTouchX = x; + setTrackingHeadsUp(false); + ExpandableView child = mStackScroller.getChildAtPosition(x, y); + mTouchingHeadsUpView = false; + if (child instanceof ExpandableNotificationRow) { + mPickedChild = (ExpandableNotificationRow) child; + mTouchingHeadsUpView = mPickedChild.isHeadsUp() && mPickedChild.isPinned(); + } + break; + case MotionEvent.ACTION_POINTER_UP: + final int upPointer = event.getPointerId(event.getActionIndex()); + if (mTrackingPointer == upPointer) { + // gesture is ongoing, find a new pointer to track + final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; + mTrackingPointer = event.getPointerId(newIndex); + mInitialTouchX = event.getX(newIndex); + mInitialTouchY = event.getY(newIndex); + } + break; + + case MotionEvent.ACTION_MOVE: + final float h = y - mInitialTouchY; + if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX)) { + setTrackingHeadsUp(true); + mCollapseSnoozes = h < 0; + mInitialTouchX = x; + mInitialTouchY = y; + int expandedHeight = mPickedChild.getActualHeight(); + mPanel.startExpandMotion(x, y, true /* startTracking */, expandedHeight); + return true; + } + break; + + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + if (mPickedChild != null && mTouchingHeadsUpView) { + // We may swallow this click if the heads up just came in. + if (mHeadsUpManager.shouldSwallowClick( + mPickedChild.getStatusBarNotification().getKey())) { + endMotion(); + return true; + } + } + endMotion(); + break; + } + return false; + } + + private void setTrackingHeadsUp(boolean tracking) { + mTrackingHeadsUp = tracking; + mHeadsUpManager.setTrackingHeadsUp(tracking); + mPanel.setTrackingHeadsUp(tracking); + } + + public void notifyFling(boolean collapse) { + if (collapse && mCollapseSnoozes) { + mHeadsUpManager.snooze(); + } + mCollapseSnoozes = false; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (!mTrackingHeadsUp) { + return false; + } + switch (event.getActionMasked()) { + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + endMotion(); + setTrackingHeadsUp(false); + break; + } + return true; + } + + private void endMotion() { + mTrackingPointer = -1; + mPickedChild = null; + mTouchingHeadsUpView = false; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java index 3b8fccc..8343497 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java @@ -39,7 +39,7 @@ public class KeyguardAffordanceHelper { public static final float SWIPE_RESTING_ALPHA_AMOUNT = 0.5f; public static final long HINT_PHASE1_DURATION = 200; private static final long HINT_PHASE2_DURATION = 350; - private static final float BACKGROUND_RADIUS_SCALE_FACTOR = 0.15f; + private static final float BACKGROUND_RADIUS_SCALE_FACTOR = 0.25f; private static final int HINT_CIRCLE_OPEN_DURATION = 500; private final Context mContext; @@ -63,13 +63,30 @@ public class KeyguardAffordanceHelper { private Interpolator mDisappearInterpolator; private Animator mSwipeAnimator; private int mMinBackgroundRadius; - private boolean mMotionPerformedByUser; private boolean mMotionCancelled; + private int mTouchTargetSize; + private View mTargetedView; + private boolean mTouchSlopExeeded; private AnimatorListenerAdapter mFlingEndListener = new AnimatorListenerAdapter() { + public boolean mCancelled; + @Override public void onAnimationEnd(Animator animation) { mSwipeAnimator = null; - setSwipingInProgress(false); + mSwipingInProgress = false; + if (!mCancelled) { + mTargetedView = null; + } + } + + @Override + public void onAnimationCancel(Animator animation) { + mCancelled = true; + } + + @Override + public void onAnimationStart(Animator animation) { + mCancelled = false; } }; private Runnable mAnimationEndRunnable = new Runnable() { @@ -83,9 +100,9 @@ public class KeyguardAffordanceHelper { mContext = context; mCallback = callback; initIcons(); - updateIcon(mLeftIcon, 0.0f, SWIPE_RESTING_ALPHA_AMOUNT, false, false); - updateIcon(mCenterIcon, 0.0f, SWIPE_RESTING_ALPHA_AMOUNT, false, false); - updateIcon(mRightIcon, 0.0f, SWIPE_RESTING_ALPHA_AMOUNT, false, false); + updateIcon(mLeftIcon, 0.0f, mLeftIcon.getRestingAlpha(), false, false); + updateIcon(mCenterIcon, 0.0f, mCenterIcon.getRestingAlpha(), false, false); + updateIcon(mRightIcon, 0.0f, mRightIcon.getRestingAlpha(), false, false); initDimens(); } @@ -97,6 +114,8 @@ public class KeyguardAffordanceHelper { R.dimen.keyguard_min_swipe_amount); mMinBackgroundRadius = mContext.getResources().getDimensionPixelSize( R.dimen.keyguard_affordance_min_background_radius); + mTouchTargetSize = mContext.getResources().getDimensionPixelSize( + R.dimen.keyguard_affordance_touch_target_size); mHintGrowAmount = mContext.getResources().getDimensionPixelSize(R.dimen.hint_grow_amount_sideways); mFlingAnimationUtils = new FlingAnimationUtils(mContext, 0.4f); @@ -117,61 +136,100 @@ public class KeyguardAffordanceHelper { } public boolean onTouchEvent(MotionEvent event) { - if (mMotionCancelled && event.getActionMasked() != MotionEvent.ACTION_DOWN) { + int action = event.getActionMasked(); + if (mMotionCancelled && action != MotionEvent.ACTION_DOWN) { return false; } final float y = event.getY(); final float x = event.getX(); boolean isUp = false; - switch (event.getActionMasked()) { + switch (action) { case MotionEvent.ACTION_DOWN: - if (mSwipingInProgress) { + View targetView = getIconAtPosition(x, y); + if (targetView == null || (mTargetedView != null && mTargetedView != targetView)) { + mMotionCancelled = true; + return false; + } + if (mTargetedView != null) { cancelAnimation(); + } else { + mTouchSlopExeeded = false; } - mInitialTouchY = y; + mCallback.onSwipingStarted(targetView == mLeftIcon); + mSwipingInProgress = true; + mTargetedView = targetView; mInitialTouchX = x; + mInitialTouchY = y; mTranslationOnDown = mTranslation; initVelocityTracker(); trackMovement(event); - mMotionPerformedByUser = false; mMotionCancelled = false; break; case MotionEvent.ACTION_POINTER_DOWN: mMotionCancelled = true; - endMotion(event, true /* forceSnapBack */); + endMotion(true /* forceSnapBack */, x, y); break; case MotionEvent.ACTION_MOVE: - final float w = x - mInitialTouchX; trackMovement(event); - if (((leftSwipePossible() && w > mTouchSlop) - || (rightSwipePossible() && w < -mTouchSlop)) - && Math.abs(w) > Math.abs(y - mInitialTouchY) - && !mSwipingInProgress) { - cancelAnimation(); - mInitialTouchY = y; - mInitialTouchX = x; - mTranslationOnDown = mTranslation; - setSwipingInProgress(true); + float xDist = x - mInitialTouchX; + float yDist = y - mInitialTouchY; + float distance = (float) Math.hypot(xDist, yDist); + if (!mTouchSlopExeeded && distance > mTouchSlop) { + mTouchSlopExeeded = true; } if (mSwipingInProgress) { - setTranslation(mTranslationOnDown + x - mInitialTouchX, false, false); + if (mTargetedView == mRightIcon) { + distance = mTranslationOnDown - distance; + distance = Math.min(0, distance); + } else { + distance = mTranslationOnDown + distance; + distance = Math.max(0, distance); + } + setTranslation(distance, false /* isReset */, false /* animateReset */); } break; case MotionEvent.ACTION_UP: isUp = true; case MotionEvent.ACTION_CANCEL: + boolean hintOnTheRight = mTargetedView == mRightIcon; trackMovement(event); - endMotion(event, !isUp); + endMotion(!isUp, x, y); + if (!mTouchSlopExeeded && isUp) { + mCallback.onIconClicked(hintOnTheRight); + } break; } return true; } - private void endMotion(MotionEvent event, boolean forceSnapBack) { + private View getIconAtPosition(float x, float y) { + if (leftSwipePossible() && isOnIcon(mLeftIcon, x, y)) { + return mLeftIcon; + } + if (rightSwipePossible() && isOnIcon(mRightIcon, x, y)) { + return mRightIcon; + } + return null; + } + + public boolean isOnAffordanceIcon(float x, float y) { + return isOnIcon(mLeftIcon, x, y) || isOnIcon(mRightIcon, x, y); + } + + private boolean isOnIcon(View icon, float x, float y) { + float iconX = icon.getX() + icon.getWidth() / 2.0f; + float iconY = icon.getY() + icon.getHeight() / 2.0f; + double distance = Math.hypot(x - iconX, y - iconY); + return distance <= mTouchTargetSize / 2; + } + + private void endMotion(boolean forceSnapBack, float lastX, float lastY) { if (mSwipingInProgress) { - flingWithCurrentVelocity(forceSnapBack); + flingWithCurrentVelocity(forceSnapBack, lastX, lastY); + } else { + mTargetedView = null; } if (mVelocityTracker != null) { mVelocityTracker.recycle(); @@ -179,13 +237,6 @@ public class KeyguardAffordanceHelper { } } - private void setSwipingInProgress(boolean inProgress) { - mSwipingInProgress = inProgress; - if (inProgress) { - mCallback.onSwipingStarted(); - } - } - private boolean rightSwipePossible() { return mRightIcon.getVisibility() == View.VISIBLE; } @@ -198,8 +249,9 @@ public class KeyguardAffordanceHelper { return false; } - public void startHintAnimation(boolean right, Runnable onFinishedListener) { - + public void startHintAnimation(boolean right, + Runnable onFinishedListener) { + cancelAnimation(); startHintAnimationPhase1(right, onFinishedListener); } @@ -219,6 +271,7 @@ public class KeyguardAffordanceHelper { public void onAnimationEnd(Animator animation) { if (mCancelled) { mSwipeAnimator = null; + mTargetedView = null; onFinishedListener.run(); targetView.showArrow(false); } else { @@ -230,6 +283,7 @@ public class KeyguardAffordanceHelper { animator.setDuration(HINT_PHASE1_DURATION); animator.start(); mSwipeAnimator = animator; + mTargetedView = targetView; } /** @@ -242,6 +296,7 @@ public class KeyguardAffordanceHelper { @Override public void onAnimationEnd(Animator animation) { mSwipeAnimator = null; + mTargetedView = null; targetView.showArrow(false); onFinishedListener.run(); } @@ -268,7 +323,7 @@ public class KeyguardAffordanceHelper { targetView.setCircleRadiusWithoutAnimation(newRadius); float translation = getTranslationFromRadius(newRadius); mTranslation = right ? -translation : translation; - updateIconsFromRadius(targetView, newRadius); + updateIconsFromTranslation(targetView); } }); return animator; @@ -280,8 +335,8 @@ public class KeyguardAffordanceHelper { } } - private void flingWithCurrentVelocity(boolean forceSnapBack) { - float vel = getCurrentVelocity(); + private void flingWithCurrentVelocity(boolean forceSnapBack, float lastX, float lastY) { + float vel = getCurrentVelocity(lastX, lastY); // We snap back if the current translation is not far enough boolean snapBack = isBelowFalsingThreshold(); @@ -303,7 +358,9 @@ public class KeyguardAffordanceHelper { } private void fling(float vel, final boolean snapBack) { - float target = mTranslation < 0 ? -mCallback.getPageWidth() : mCallback.getPageWidth(); + float target = mTranslation < 0 + ? -mCallback.getMaxTranslationDistance() + : mCallback.getMaxTranslationDistance(); target = snapBack ? 0 : target; ValueAnimator animator = ValueAnimator.ofFloat(mTranslation, target); @@ -323,6 +380,9 @@ public class KeyguardAffordanceHelper { } animator.start(); mSwipeAnimator = animator; + if (snapBack) { + mCallback.onSwipingAborted(); + } } private void startFinishingCircleAnimation(float velocity, Runnable mAnimationEndRunnable) { @@ -334,62 +394,64 @@ public class KeyguardAffordanceHelper { translation = rightSwipePossible() ? translation : Math.max(0, translation); translation = leftSwipePossible() ? translation : Math.min(0, translation); float absTranslation = Math.abs(translation); - if (absTranslation > Math.abs(mTranslationOnDown) + getMinTranslationAmount() || - mMotionPerformedByUser) { - mMotionPerformedByUser = true; - } if (translation != mTranslation || isReset) { KeyguardAffordanceView targetView = translation > 0 ? mLeftIcon : mRightIcon; KeyguardAffordanceView otherView = translation > 0 ? mRightIcon : mLeftIcon; float alpha = absTranslation / getMinTranslationAmount(); // We interpolate the alpha of the other icons to 0 - float fadeOutAlpha = SWIPE_RESTING_ALPHA_AMOUNT * (1.0f - alpha); - fadeOutAlpha = Math.max(0.0f, fadeOutAlpha); - - // We interpolate the alpha of the targetView to 1 - alpha = fadeOutAlpha + alpha; + float fadeOutAlpha = 1.0f - alpha; + fadeOutAlpha = Math.max(fadeOutAlpha, 0.0f); boolean animateIcons = isReset && animateReset; float radius = getRadiusFromTranslation(absTranslation); boolean slowAnimation = isReset && isBelowFalsingThreshold(); if (!isReset) { - updateIcon(targetView, radius, alpha, false, false); + updateIcon(targetView, radius, alpha + fadeOutAlpha * targetView.getRestingAlpha(), + false, false); } else { - updateIcon(targetView, 0.0f, fadeOutAlpha, animateIcons, slowAnimation); + updateIcon(targetView, 0.0f, fadeOutAlpha * targetView.getRestingAlpha(), + animateIcons, slowAnimation); } - updateIcon(otherView, 0.0f, fadeOutAlpha, animateIcons, slowAnimation); - updateIcon(mCenterIcon, 0.0f, fadeOutAlpha, animateIcons, slowAnimation); + updateIcon(otherView, 0.0f, fadeOutAlpha * otherView.getRestingAlpha(), + animateIcons, slowAnimation); + updateIcon(mCenterIcon, 0.0f, fadeOutAlpha * mCenterIcon.getRestingAlpha(), + animateIcons, slowAnimation); mTranslation = translation; } } - private void updateIconsFromRadius(KeyguardAffordanceView targetView, float newRadius) { - float alpha = newRadius / mMinBackgroundRadius; + private void updateIconsFromTranslation(KeyguardAffordanceView targetView) { + float absTranslation = Math.abs(mTranslation); + float alpha = absTranslation / getMinTranslationAmount(); // We interpolate the alpha of the other icons to 0 - float fadeOutAlpha = SWIPE_RESTING_ALPHA_AMOUNT * (1.0f - alpha); + float fadeOutAlpha = 1.0f - alpha; fadeOutAlpha = Math.max(0.0f, fadeOutAlpha); // We interpolate the alpha of the targetView to 1 - alpha = fadeOutAlpha + alpha; KeyguardAffordanceView otherView = targetView == mRightIcon ? mLeftIcon : mRightIcon; - updateIconAlpha(targetView, alpha, false); - updateIconAlpha(otherView, fadeOutAlpha, false); - updateIconAlpha(mCenterIcon, fadeOutAlpha, false); + updateIconAlpha(targetView, alpha + fadeOutAlpha * targetView.getRestingAlpha(), false); + updateIconAlpha(otherView, fadeOutAlpha * otherView.getRestingAlpha(), false); + updateIconAlpha(mCenterIcon, fadeOutAlpha * mCenterIcon.getRestingAlpha(), false); } private float getTranslationFromRadius(float circleSize) { - float translation = (circleSize - mMinBackgroundRadius) / BACKGROUND_RADIUS_SCALE_FACTOR; - return Math.max(0, translation); + float translation = (circleSize - mMinBackgroundRadius) + / BACKGROUND_RADIUS_SCALE_FACTOR; + return translation > 0.0f ? translation + mTouchSlop : 0.0f; } private float getRadiusFromTranslation(float translation) { - return translation * BACKGROUND_RADIUS_SCALE_FACTOR + mMinBackgroundRadius; + if (translation <= mTouchSlop) { + return 0.0f; + } + return (translation - mTouchSlop) * BACKGROUND_RADIUS_SCALE_FACTOR + mMinBackgroundRadius; } public void animateHideLeftRightIcon() { + cancelAnimation(); updateIcon(mRightIcon, 0f, 0f, true, false); updateIcon(mLeftIcon, 0f, 0f, true, false); } @@ -404,14 +466,14 @@ public class KeyguardAffordanceHelper { } private void updateIconAlpha(KeyguardAffordanceView view, float alpha, boolean animate) { - float scale = getScale(alpha); + float scale = getScale(alpha, view); alpha = Math.min(1.0f, alpha); view.setImageAlpha(alpha, animate); view.setImageScale(scale, animate); } - private float getScale(float alpha) { - float scale = alpha / SWIPE_RESTING_ALPHA_AMOUNT * 0.2f + + private float getScale(float alpha, KeyguardAffordanceView icon) { + float scale = alpha / icon.getRestingAlpha() * 0.2f + KeyguardAffordanceView.MIN_ICON_SCALE_AMOUNT; return Math.min(scale, KeyguardAffordanceView.MAX_ICON_SCALE_AMOUNT); } @@ -429,12 +491,22 @@ public class KeyguardAffordanceHelper { mVelocityTracker = VelocityTracker.obtain(); } - private float getCurrentVelocity() { + private float getCurrentVelocity(float lastX, float lastY) { if (mVelocityTracker == null) { return 0; } mVelocityTracker.computeCurrentVelocity(1000); - return mVelocityTracker.getXVelocity(); + float aX = mVelocityTracker.getXVelocity(); + float aY = mVelocityTracker.getYVelocity(); + float bX = lastX - mInitialTouchX; + float bY = lastY - mInitialTouchY; + float bLen = (float) Math.hypot(bX, bY); + // Project the velocity onto the distance vector: a * b / |b| + float projectedVelocity = (aX * bX + aY * bY) / bLen; + if (mTargetedView == mRightIcon) { + projectedVelocity = -projectedVelocity; + } + return projectedVelocity; } public void onConfigurationChanged() { @@ -451,7 +523,11 @@ public class KeyguardAffordanceHelper { mSwipeAnimator.cancel(); } setTranslation(0.0f, true, animate); - setSwipingInProgress(false); + mMotionCancelled = true; + if (mSwipingInProgress) { + mCallback.onSwipingAborted(); + } + mSwipingInProgress = false; } public interface Callback { @@ -468,9 +544,13 @@ public class KeyguardAffordanceHelper { */ void onAnimationToSideEnded(); - float getPageWidth(); + float getMaxTranslationDistance(); + + void onSwipingStarted(boolean isRightwardMotion); + + void onSwipingAborted(); - void onSwipingStarted(); + void onIconClicked(boolean rightIcon); KeyguardAffordanceView getLeftIcon(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index 0c21b20..fabc1a6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -25,13 +25,17 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.graphics.drawable.InsetDrawable; +import android.hardware.fingerprint.FingerprintManager; import android.os.AsyncTask; import android.os.Bundle; import android.os.RemoteException; import android.os.UserHandle; +import android.os.Vibrator; import android.provider.MediaStore; +import android.provider.Settings; import android.telecom.TelecomManager; import android.util.AttributeSet; import android.util.Log; @@ -78,10 +82,11 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL private static final Intent PHONE_INTENT = new Intent(Intent.ACTION_DIAL); private static final int DOZE_ANIMATION_STAGGER_DELAY = 48; private static final int DOZE_ANIMATION_ELEMENT_DURATION = 250; + private static final long TRANSIENT_FP_ERROR_TIMEOUT = 1300; private KeyguardAffordanceView mCameraImageView; private KeyguardAffordanceView mPhoneImageView; - private KeyguardAffordanceView mLockIcon; + private LockIcon mLockIcon; private TextView mIndicationText; private ViewGroup mPreviewContainer; @@ -97,9 +102,8 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL private AccessibilityController mAccessibilityController; private PhoneStatusBar mPhoneStatusBar; - private final TrustDrawable mTrustDrawable; private final Interpolator mLinearOutSlowInInterpolator; - private int mLastUnlockIconRes = 0; + private boolean mPrewarmSent; public KeyguardBottomAreaView(Context context) { this(context, null); @@ -116,7 +120,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL public KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - mTrustDrawable = new TrustDrawable(mContext); mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in); } @@ -162,20 +165,19 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL mPreviewContainer = (ViewGroup) findViewById(R.id.preview_container); mCameraImageView = (KeyguardAffordanceView) findViewById(R.id.camera_button); mPhoneImageView = (KeyguardAffordanceView) findViewById(R.id.phone_button); - mLockIcon = (KeyguardAffordanceView) findViewById(R.id.lock_icon); + mLockIcon = (LockIcon) findViewById(R.id.lock_icon); mIndicationText = (TextView) findViewById(R.id.keyguard_indication_text); watchForCameraPolicyChanges(); updateCameraVisibility(); updatePhoneVisibility(); mUnlockMethodCache = UnlockMethodCache.getInstance(getContext()); mUnlockMethodCache.addListener(this); - updateLockIcon(); + mLockIcon.update(); setClipChildren(false); setClipToPadding(false); mPreviewInflater = new PreviewInflater(mContext, new LockPatternUtils(mContext)); inflatePreviews(); mLockIcon.setOnClickListener(this); - mLockIcon.setBackground(mTrustDrawable); mLockIcon.setOnLongClickListener(this); mCameraImageView.setOnClickListener(this); mPhoneImageView.setOnClickListener(this); @@ -215,6 +217,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL public void setAccessibilityController(AccessibilityController accessibilityController) { mAccessibilityController = accessibilityController; + mLockIcon.setAccessibilityController(accessibilityController); accessibilityController.addStateChangedCallback(this); } @@ -226,9 +229,9 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL private Intent getCameraIntent() { KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext); boolean currentUserHasTrust = updateMonitor.getUserHasTrust( - mLockPatternUtils.getCurrentUser()); - return mLockPatternUtils.isSecure() && !currentUserHasTrust - ? SECURE_CAMERA_INTENT : INSECURE_CAMERA_INTENT; + KeyguardUpdateMonitor.getCurrentUser()); + boolean secure = mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser()); + return (secure && !currentUserHasTrust) ? SECURE_CAMERA_INTENT : INSECURE_CAMERA_INTENT; } private void updateCameraVisibility() { @@ -238,7 +241,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } ResolveInfo resolved = mContext.getPackageManager().resolveActivityAsUser(getCameraIntent(), PackageManager.MATCH_DEFAULT_ONLY, - mLockPatternUtils.getCurrentUser()); + KeyguardUpdateMonitor.getCurrentUser()); boolean visible = !isCameraDisabledByDpm() && resolved != null && getResources().getBoolean(R.bool.config_keyguardShowCameraAffordance); mCameraImageView.setVisibility(visible ? View.VISIBLE : View.GONE); @@ -287,21 +290,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL mPhoneImageView.setClickable(touchExplorationEnabled); mCameraImageView.setFocusable(accessibilityEnabled); mPhoneImageView.setFocusable(accessibilityEnabled); - updateLockIconClickability(); - } - - private void updateLockIconClickability() { - if (mAccessibilityController == null) { - return; - } - boolean clickToUnlock = mAccessibilityController.isTouchExplorationEnabled(); - boolean clickToForceLock = mUnlockMethodCache.isTrustManaged() - && !mAccessibilityController.isAccessibilityEnabled(); - boolean longClickToForceLock = mUnlockMethodCache.isTrustManaged() - && !clickToForceLock; - mLockIcon.setClickable(clickToForceLock || clickToUnlock); - mLockIcon.setLongClickable(longClickToForceLock); - mLockIcon.setFocusable(mAccessibilityController.isAccessibilityEnabled()); + mLockIcon.update(); } @Override @@ -332,16 +321,50 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL 0 /* velocityDp - N/A */); mIndicationController.showTransientIndication( R.string.keyguard_indication_trust_disabled); - mLockPatternUtils.requireCredentialEntry(mLockPatternUtils.getCurrentUser()); + mLockPatternUtils.requireCredentialEntry(KeyguardUpdateMonitor.getCurrentUser()); } - public void launchCamera() { - mFlashlightController.killFlashlight(); + public void prewarmCamera() { + Intent intent = getCameraIntent(); + String targetPackage = PreviewInflater.getTargetPackage(mContext, intent, + KeyguardUpdateMonitor.getCurrentUser()); + if (targetPackage != null) { + Intent prewarm = new Intent(MediaStore.ACTION_STILL_IMAGE_CAMERA_PREWARM); + prewarm.setPackage(targetPackage); + mPrewarmSent = true; + mContext.sendBroadcast(prewarm); + } + } + + public void maybeCooldownCamera() { + if (!mPrewarmSent) { + return; + } + mPrewarmSent = false; Intent intent = getCameraIntent(); + String targetPackage = PreviewInflater.getTargetPackage(mContext, intent, + KeyguardUpdateMonitor.getCurrentUser()); + if (targetPackage != null) { + Intent prewarm = new Intent(MediaStore.ACTION_STILL_IMAGE_CAMERA_COOLDOWN); + prewarm.setPackage(targetPackage); + mContext.sendBroadcast(prewarm); + } + } + + public void launchCamera() { + + // Reset prewarm state. + mPrewarmSent = false; + final Intent intent = getCameraIntent(); boolean wouldLaunchResolverActivity = PreviewInflater.wouldLaunchResolverActivity( - mContext, intent, mLockPatternUtils.getCurrentUser()); + mContext, intent, KeyguardUpdateMonitor.getCurrentUser()); if (intent == SECURE_CAMERA_INTENT && !wouldLaunchResolverActivity) { - mContext.startActivityAsUser(intent, UserHandle.CURRENT); + AsyncTask.execute(new Runnable() { + @Override + public void run() { + mContext.startActivityAsUser(intent, UserHandle.CURRENT); + } + }); } else { // We need to delay starting the activity because ResolverActivity finishes itself if @@ -368,56 +391,12 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL @Override protected void onVisibilityChanged(View changedView, int visibility) { super.onVisibilityChanged(changedView, visibility); - if (isShown()) { - mTrustDrawable.start(); - } else { - mTrustDrawable.stop(); - } if (changedView == this && visibility == VISIBLE) { - updateLockIcon(); + mLockIcon.update(); updateCameraVisibility(); } } - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mTrustDrawable.stop(); - } - - private void updateLockIcon() { - boolean visible = isShown() && KeyguardUpdateMonitor.getInstance(mContext).isScreenOn(); - if (visible) { - mTrustDrawable.start(); - } else { - mTrustDrawable.stop(); - } - if (!visible) { - return; - } - // TODO: Real icon for facelock. - int iconRes = mUnlockMethodCache.isFaceUnlockRunning() - ? com.android.internal.R.drawable.ic_account_circle - : mUnlockMethodCache.isCurrentlyInsecure() ? R.drawable.ic_lock_open_24dp - : R.drawable.ic_lock_24dp; - if (mLastUnlockIconRes != iconRes) { - Drawable icon = mContext.getDrawable(iconRes); - int iconHeight = getResources().getDimensionPixelSize( - R.dimen.keyguard_affordance_icon_height); - int iconWidth = getResources().getDimensionPixelSize( - R.dimen.keyguard_affordance_icon_width); - if (icon.getIntrinsicHeight() != iconHeight || icon.getIntrinsicWidth() != iconWidth) { - icon = new IntrinsicSizeDrawable(icon, iconWidth, iconHeight); - } - mLockIcon.setImageDrawable(icon); - } - boolean trustManaged = mUnlockMethodCache.isTrustManaged(); - mTrustDrawable.setTrustManaged(trustManaged); - updateLockIconClickability(); - } - - - public KeyguardAffordanceView getPhoneView() { return mPhoneImageView; } @@ -449,7 +428,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL @Override public void onUnlockMethodStateChanged() { - updateLockIcon(); + mLockIcon.update(); updateCameraVisibility(); } @@ -506,6 +485,14 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } }; + private final Runnable mTransientFpErrorClearRunnable = new Runnable() { + @Override + public void run() { + mLockIcon.setTransientFpError(false); + mIndicationController.hideTransientIndication(); + } + }; + private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() { @Override @@ -515,48 +502,45 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL @Override public void onScreenTurnedOn() { - updateLockIcon(); + mLockIcon.update(); } @Override public void onScreenTurnedOff(int why) { - updateLockIcon(); + mLockIcon.update(); } @Override public void onKeyguardVisibilityChanged(boolean showing) { - updateLockIcon(); + mLockIcon.update(); } - }; - - public void setKeyguardIndicationController( - KeyguardIndicationController keyguardIndicationController) { - mIndicationController = keyguardIndicationController; - } + @Override + public void onFingerprintAuthenticated(int userId) { + } - /** - * A wrapper around another Drawable that overrides the intrinsic size. - */ - private static class IntrinsicSizeDrawable extends InsetDrawable { - - private final int mIntrinsicWidth; - private final int mIntrinsicHeight; - - public IntrinsicSizeDrawable(Drawable drawable, int intrinsicWidth, int intrinsicHeight) { - super(drawable, 0); - mIntrinsicWidth = intrinsicWidth; - mIntrinsicHeight = intrinsicHeight; + @Override + public void onFingerprintRunningStateChanged(boolean running) { + mLockIcon.update(); } @Override - public int getIntrinsicWidth() { - return mIntrinsicWidth; + public void onFingerprintHelp(int msgId, String helpString) { + mLockIcon.setTransientFpError(true); + mIndicationController.showTransientIndication(helpString, + getResources().getColor(R.color.system_warning_color, null)); + removeCallbacks(mTransientFpErrorClearRunnable); + postDelayed(mTransientFpErrorClearRunnable, TRANSIENT_FP_ERROR_TIMEOUT); } @Override - public int getIntrinsicHeight() { - return mIntrinsicHeight; + public void onFingerprintError(int msgId, String errString) { + // TODO: Go to bouncer if this is "too many attempts" (lockout) error. } + }; + + public void setKeyguardIndicationController( + KeyguardIndicationController keyguardIndicationController) { + mIndicationController = keyguardIndicationController; } } 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/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java new file mode 100644 index 0000000..66f3232 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java @@ -0,0 +1,212 @@ +/* + * 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.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.graphics.drawable.AnimatedVectorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.InsetDrawable; +import android.util.AttributeSet; +import android.view.View; + +import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.systemui.R; +import com.android.systemui.statusbar.KeyguardAffordanceView; +import com.android.systemui.statusbar.policy.AccessibilityController; + +/** + * Manages the different states and animations of the unlock icon. + */ +public class LockIcon extends KeyguardAffordanceView { + + + private static final int STATE_LOCKED = 0; + private static final int STATE_LOCK_OPEN = 1; + private static final int STATE_FACE_UNLOCK = 2; + private static final int STATE_FINGERPRINT = 3; + private static final int STATE_FINGERPRINT_ERROR = 4; + + private int mLastState = 0; + private boolean mTransientFpError; + private final TrustDrawable mTrustDrawable; + private final UnlockMethodCache mUnlockMethodCache; + private AccessibilityController mAccessibilityController; + + public LockIcon(Context context, AttributeSet attrs) { + super(context, attrs); + mTrustDrawable = new TrustDrawable(context); + setBackground(mTrustDrawable); + mUnlockMethodCache = UnlockMethodCache.getInstance(context); + } + + @Override + protected void onVisibilityChanged(View changedView, int visibility) { + super.onVisibilityChanged(changedView, visibility); + if (isShown()) { + mTrustDrawable.start(); + } else { + mTrustDrawable.stop(); + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mTrustDrawable.stop(); + } + + public void setTransientFpError(boolean transientFpError) { + mTransientFpError = transientFpError; + update(); + } + + public void update() { + boolean visible = isShown() && KeyguardUpdateMonitor.getInstance(mContext).isScreenOn(); + if (visible) { + mTrustDrawable.start(); + } else { + mTrustDrawable.stop(); + } + if (!visible) { + return; + } + // TODO: Real icon for facelock. + int state = getState(); + boolean anyFingerprintIcon = state == STATE_FINGERPRINT || state == STATE_FINGERPRINT_ERROR; + if (state != mLastState) { + int iconRes = getAnimationResForTransition(mLastState, state); + if (iconRes == -1) { + iconRes = getIconForState(state); + } + Drawable icon = mContext.getDrawable(iconRes); + AnimatedVectorDrawable animation = null; + if (icon instanceof AnimatedVectorDrawable) { + animation = (AnimatedVectorDrawable) icon; + } + int iconHeight = getResources().getDimensionPixelSize( + R.dimen.keyguard_affordance_icon_height); + int iconWidth = getResources().getDimensionPixelSize( + R.dimen.keyguard_affordance_icon_width); + if (!anyFingerprintIcon && (icon.getIntrinsicHeight() != iconHeight + || icon.getIntrinsicWidth() != iconWidth)) { + icon = new IntrinsicSizeDrawable(icon, iconWidth, iconHeight); + } + setPaddingRelative(0, 0, 0, anyFingerprintIcon + ? getResources().getDimensionPixelSize( + R.dimen.fingerprint_icon_additional_padding) + : 0); + setRestingAlpha( + anyFingerprintIcon ? 1f : KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT); + setImageDrawable(icon); + if (animation != null) { + animation.start(); + } + } + + // Hide trust circle when fingerprint is running. + boolean trustManaged = mUnlockMethodCache.isTrustManaged() && !anyFingerprintIcon; + mTrustDrawable.setTrustManaged(trustManaged); + mLastState = state; + updateClickability(); + } + + private void updateClickability() { + if (mAccessibilityController == null) { + return; + } + boolean clickToUnlock = mAccessibilityController.isTouchExplorationEnabled(); + boolean clickToForceLock = mUnlockMethodCache.isTrustManaged() + && !mAccessibilityController.isAccessibilityEnabled(); + boolean longClickToForceLock = mUnlockMethodCache.isTrustManaged() + && !clickToForceLock; + setClickable(clickToForceLock || clickToUnlock); + setLongClickable(longClickToForceLock); + setFocusable(mAccessibilityController.isAccessibilityEnabled()); + } + + public void setAccessibilityController(AccessibilityController accessibilityController) { + mAccessibilityController = accessibilityController; + } + + private int getIconForState(int state) { + switch (state) { + case STATE_LOCKED: + return R.drawable.ic_lock_24dp; + case STATE_LOCK_OPEN: + return R.drawable.ic_lock_open_24dp; + case STATE_FACE_UNLOCK: + return com.android.internal.R.drawable.ic_account_circle; + case STATE_FINGERPRINT: + return R.drawable.ic_fingerprint; + case STATE_FINGERPRINT_ERROR: + return R.drawable.ic_fingerprint_error; + default: + throw new IllegalArgumentException(); + } + } + + private int getAnimationResForTransition(int oldState, int newState) { + if (oldState == STATE_FINGERPRINT && newState == STATE_FINGERPRINT_ERROR) { + return R.drawable.lockscreen_fingerprint_error_state_animation; + } else { + return -1; + } + } + + private int getState() { + boolean fingerprintRunning = + KeyguardUpdateMonitor.getInstance(mContext).isFingerprintDetectionRunning(); + if (mTransientFpError) { + return STATE_FINGERPRINT_ERROR; + } else if (fingerprintRunning) { + return STATE_FINGERPRINT; + } else if (mUnlockMethodCache.isFaceUnlockRunning()) { + return STATE_FACE_UNLOCK; + } else if (mUnlockMethodCache.isCurrentlyInsecure()) { + return STATE_LOCK_OPEN; + } else { + return STATE_LOCKED; + } + } + + /** + * A wrapper around another Drawable that overrides the intrinsic size. + */ + private static class IntrinsicSizeDrawable extends InsetDrawable { + + private final int mIntrinsicWidth; + private final int mIntrinsicHeight; + + public IntrinsicSizeDrawable(Drawable drawable, int intrinsicWidth, int intrinsicHeight) { + super(drawable, 0); + mIntrinsicWidth = intrinsicWidth; + mIntrinsicHeight = intrinsicHeight; + } + + @Override + public int getIntrinsicWidth() { + return mIntrinsicWidth; + } + + @Override + public int getIntrinsicHeight() { + return mIntrinsicHeight; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/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..a8ecc42 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -32,6 +32,7 @@ import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewTreeObserver; +import android.view.WindowInsets; import android.view.accessibility.AccessibilityEvent; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; @@ -39,16 +40,19 @@ import android.widget.FrameLayout; import android.widget.TextView; import com.android.keyguard.KeyguardStatusView; -import com.android.systemui.EventLogTags; import com.android.systemui.EventLogConstants; +import com.android.systemui.EventLogTags; import com.android.systemui.R; import com.android.systemui.qs.QSContainer; import com.android.systemui.qs.QSPanel; +import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.ExpandableView; import com.android.systemui.statusbar.FlingAnimationUtils; import com.android.systemui.statusbar.GestureRecorder; import com.android.systemui.statusbar.KeyguardAffordanceView; +import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.KeyguardUserSwitcher; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.stack.StackStateAnimator; @@ -56,7 +60,8 @@ import com.android.systemui.statusbar.stack.StackStateAnimator; public class NotificationPanelView extends PanelView implements ExpandableView.OnHeightChangedListener, ObservableScrollView.Listener, View.OnClickListener, NotificationStackScrollLayout.OnOverscrollTopChangedListener, - KeyguardAffordanceHelper.Callback, NotificationStackScrollLayout.OnEmptySpaceClickListener { + KeyguardAffordanceHelper.Callback, NotificationStackScrollLayout.OnEmptySpaceClickListener, + HeadsUpManager.OnHeadsUpChangedListener { private static final boolean DEBUG = false; @@ -81,7 +86,7 @@ public class NotificationPanelView extends PanelView implements private TextView mClockView; private View mReserveNotificationSpace; private View mQsNavbarScrim; - private View mNotificationContainerParent; + private NotificationsQuickSettingsContainer mNotificationContainerParent; private NotificationStackScrollLayout mNotificationStackScroller; private int mNotificationTopPadding; private boolean mAnimateNextTopPaddingChange; @@ -112,6 +117,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 +169,7 @@ public class NotificationPanelView extends PanelView implements private Runnable mLaunchAnimationEndRunnable; private boolean mOnlyAffordanceInThisMotion; private boolean mKeyguardStatusViewAnimating; - private boolean mHeaderAnimatingIn; + private boolean mHeaderAnimating; private ObjectAnimator mQsContainerAnimator; private ValueAnimator mQsSizeChangeAnimator; @@ -176,6 +182,20 @@ public class NotificationPanelView extends PanelView implements private float mKeyguardStatusBarAnimateAlpha = 1f; private int mOldLayoutDirection; + private HeadsUpTouchHelper mHeadsUpTouchHelper; + private boolean mIsExpansionFromHeadsUp; + private int mNavigationBarBottomHeight; + private boolean mExpandingFromHeadsUp; + private boolean mCollapsedOnDown; + private int mPositionMinSideMargin; + private int mLastOrientation = -1; + + private Runnable mHeadsUpExistenceChangedRunnable = new Runnable() { + @Override + public void run() { + notifyBarPanelExpansionChanged(); + } + }; public NotificationPanelView(Context context, AttributeSet attrs) { super(context, attrs); @@ -200,7 +220,8 @@ public class NotificationPanelView extends PanelView implements mScrollView.setListener(this); mScrollView.setFocusable(false); mReserveNotificationSpace = findViewById(R.id.reserve_notification_space); - mNotificationContainerParent = findViewById(R.id.notification_container_parent); + mNotificationContainerParent = (NotificationsQuickSettingsContainer) + findViewById(R.id.notification_container_parent); mNotificationStackScroller = (NotificationStackScrollLayout) findViewById(R.id.notification_stack_scroller); mNotificationStackScroller.setOnHeightChangedListener(this); @@ -218,13 +239,13 @@ public class NotificationPanelView extends PanelView implements mAfforanceHelper = new KeyguardAffordanceHelper(this, getContext()); mSecureCameraLaunchManager = new SecureCameraLaunchManager(getContext(), mKeyguardBottomArea); + mLastOrientation = getResources().getConfiguration().orientation; // recompute internal state when qspanel height changes mQsContainer.addOnLayoutChangeListener(new OnLayoutChangeListener() { @Override - public void onLayoutChange(View v, int left, int top, int right, - int bottom, int oldLeft, int oldTop, int oldRight, - int oldBottom) { + public void onLayoutChange(View v, int left, int top, int right, int bottom, + int oldLeft, int oldTop, int oldRight, int oldBottom) { final int height = bottom - top; final int oldHeight = oldBottom - oldTop; if (height != oldHeight) { @@ -251,6 +272,8 @@ public class NotificationPanelView extends PanelView implements getResources().getDimensionPixelSize(R.dimen.notification_scrim_wait_distance); mQsFalsingThreshold = getResources().getDimensionPixelSize( R.dimen.qs_falsing_threshold); + mPositionMinSideMargin = getResources().getDimensionPixelSize( + R.dimen.notification_panel_min_side_margin); } public void updateResources() { @@ -304,7 +327,7 @@ public class NotificationPanelView extends PanelView implements } else if (!mQsExpanded) { setQsExpansion(mQsMinExpansionHeight + mLastOverscroll); } - mNotificationStackScroller.setStackHeight(getExpandedHeight()); + updateStackHeight(getExpandedHeight()); updateHeader(); mNotificationStackScroller.updateIsSmallScreen( mHeader.getCollapsedHeight() + mQsPeekHeight); @@ -317,6 +340,7 @@ public class NotificationPanelView extends PanelView implements if (mQsSizeChangeAnimator == null) { mQsContainer.setHeightOverride(mQsContainer.getDesiredHeight()); } + updateMaxHeadsUpTranslation(); } @Override @@ -493,14 +517,20 @@ public class NotificationPanelView extends PanelView implements } @Override - public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + protected void flingToHeight(float vel, boolean expand, float target, + float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) { + mHeadsUpTouchHelper.notifyFling(!expand); + super.flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing); + } + + @Override + public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) { if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { event.getText().add(getKeyguardOrLockScreenString()); mLastAnnouncementWasQuickSettings = false; return true; } - - return super.dispatchPopulateAccessibilityEvent(event); + return super.dispatchPopulateAccessibilityEventInternal(event); } @Override @@ -508,7 +538,18 @@ public class NotificationPanelView extends PanelView implements if (mBlockTouches) { return false; } - resetDownStates(event); + initDownStates(event); + if (mHeadsUpTouchHelper.onInterceptTouchEvent(event)) { + mIsExpansionFromHeadsUp = true; + return true; + } + if (!isFullyCollapsed() && onQsIntercept(event)) { + return true; + } + return super.onInterceptTouchEvent(event); + } + + private boolean onQsIntercept(MotionEvent event) { int pointerIndex = event.findPointerIndex(mTrackingPointer); if (pointerIndex < 0) { pointerIndex = 0; @@ -583,7 +624,7 @@ public class NotificationPanelView extends PanelView implements mIntercepting = false; break; } - return super.onInterceptTouchEvent(event); + return false; } @Override @@ -594,10 +635,12 @@ 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(); + mCollapsedOnDown = isFullyCollapsed(); } } @@ -642,7 +685,7 @@ public class NotificationPanelView extends PanelView implements if (mBlockTouches) { return false; } - resetDownStates(event); + initDownStates(event); if ((!mIsExpanding || mHintAnimationRunning) && !mQsExpanded && mStatusBar.getBarState() != StatusBarState.SHADE) { @@ -651,6 +694,18 @@ public class NotificationPanelView extends PanelView implements if (mOnlyAffordanceInThisMotion) { return true; } + mHeadsUpTouchHelper.onTouchEvent(event); + if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && handleQsTouch(event)) { + return true; + } + if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) { + updateVerticalPanelPosition(event.getX()); + } + super.onTouchEvent(event); + return true; + } + + private boolean handleQsTouch(MotionEvent event) { if (event.getActionMasked() == MotionEvent.ACTION_DOWN && getExpandedFraction() == 1f && mStatusBar.getBarState() != StatusBarState.KEYGUARD && !mQsExpanded && mQsExpansionEnabled) { @@ -663,7 +718,7 @@ public class NotificationPanelView extends PanelView implements mInitialTouchY = event.getX(); mInitialTouchX = event.getY(); } - if (mExpandedHeight != 0) { + if (!isFullyCollapsed()) { handleQsDown(event); } if (!mQsExpandImmediate && mQsTracking) { @@ -676,7 +731,7 @@ public class NotificationPanelView extends PanelView implements || event.getActionMasked() == MotionEvent.ACTION_UP) { mConflictingQsExpansionGesture = false; } - if (event.getActionMasked() == MotionEvent.ACTION_DOWN && mExpandedHeight == 0 + if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed() && mQsExpansionEnabled) { mTwoFingerQsExpandPossible = true; } @@ -690,12 +745,11 @@ public class NotificationPanelView extends PanelView implements // earlier so the state is already up to date when dragging down. setListening(true); } - super.onTouchEvent(event); - return true; + return false; } private boolean isInQsArea(float x, float y) { - return (x >= mScrollView.getLeft() && x <= mScrollView.getRight()) && + return (x >= mScrollView.getX() && x <= mScrollView.getX() + mScrollView.getWidth()) && (y <= mNotificationStackScroller.getBottomMostNotificationBottom() || y <= mQsContainer.getY() + mQsContainer.getHeight()); } @@ -717,8 +771,8 @@ public class NotificationPanelView extends PanelView implements } @Override - protected boolean flingExpands(float vel, float vectorVel) { - boolean expands = super.flingExpands(vel, vectorVel); + protected boolean flingExpands(float vel, float vectorVel, float x, float y) { + boolean expands = super.flingExpands(vel, vectorVel, x, y); // If we are already running a QS expansion, make sure that we keep the panel open. if (mQsExpansionAnimator != null) { @@ -732,6 +786,11 @@ public class NotificationPanelView extends PanelView implements return mStatusBar.getBarState() != StatusBarState.SHADE; } + @Override + protected boolean shouldGestureIgnoreXTouchSlop(float x, float y) { + return !mAfforanceHelper.isOnAffordanceIcon(x, y); + } + private void onQsTouch(MotionEvent event) { int pointerIndex = event.findPointerIndex(mTrackingPointer); if (pointerIndex < 0) { @@ -833,13 +892,13 @@ public class NotificationPanelView extends PanelView implements setQsExpansion(mQsExpansionHeight); flingSettings(!mQsExpansionEnabled && open ? 0f : velocity, open && mQsExpansionEnabled, new Runnable() { - @Override - public void run() { - mStackScrollerOverscrolling = false; - mQsExpansionFromOverscroll = false; - updateQsState(); - } - }); + @Override + public void run() { + mStackScrollerOverscrolling = false; + mQsExpansionFromOverscroll = false; + updateQsState(); + } + }); } private void onQsExpansionStarted() { @@ -868,31 +927,34 @@ public class NotificationPanelView extends PanelView implements mNotificationStackScroller.setInterceptDelegateEnabled(expanded); mStatusBar.setQsExpanded(expanded); mQsPanel.setExpanded(expanded); + mNotificationContainerParent.setQsExpanded(expanded); } } public void setBarState(int statusBarState, boolean keyguardFadingAway, boolean goingToFullShade) { - boolean keyguardShowing = statusBarState == StatusBarState.KEYGUARD - || statusBarState == StatusBarState.SHADE_LOCKED; - if (!mKeyguardShowing && keyguardShowing) { - setQsTranslation(mQsExpansionHeight); - mHeader.setTranslationY(0f); - } + int oldState = mStatusBarState; + boolean keyguardShowing = statusBarState == StatusBarState.KEYGUARD; setKeyguardStatusViewVisibility(statusBarState, keyguardFadingAway, goingToFullShade); setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade); - if (goingToFullShade) { + + mStatusBarState = statusBarState; + mKeyguardShowing = keyguardShowing; + + if (goingToFullShade || (oldState == StatusBarState.KEYGUARD + && statusBarState == StatusBarState.SHADE_LOCKED)) { animateKeyguardStatusBarOut(); + animateHeaderSlidingIn(); + } else if (oldState == StatusBarState.SHADE_LOCKED + && statusBarState == StatusBarState.KEYGUARD) { + animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD); + animateHeaderSlidingOut(); } else { mKeyguardStatusBar.setAlpha(1f); mKeyguardStatusBar.setVisibility(keyguardShowing ? View.VISIBLE : View.INVISIBLE); } - mStatusBarState = statusBarState; - mKeyguardShowing = keyguardShowing; + resetVerticalPanelPosition(); updateQsState(); - if (goingToFullShade) { - animateHeaderSlidingIn(); - } } private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = new Runnable() { @@ -914,7 +976,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 +1004,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 +1019,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 +1028,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 +1069,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 +1089,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(); @@ -1024,7 +1115,7 @@ public class NotificationPanelView extends PanelView implements mKeyguardBottomArea.animate() .alpha(0f) .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay()) - .setDuration(mStatusBar.getKeyguardFadingAwayDuration()/2) + .setDuration(mStatusBar.getKeyguardFadingAwayDuration() / 2) .setInterpolator(PhoneStatusBar.ALPHA_OUT) .withEndAction(mAnimateKeyguardBottomAreaInvisibleEndRunnable) .start(); @@ -1084,9 +1175,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 +1218,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 +1262,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)); } } @@ -1288,11 +1386,11 @@ public class NotificationPanelView extends PanelView implements * @return Whether we should intercept a gesture to open Quick Settings. */ private boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) { - if (!mQsExpansionEnabled) { + if (!mQsExpansionEnabled || mCollapsedOnDown) { return false; } View header = mKeyguardShowing ? mKeyguardStatusBar : mHeader; - boolean onHeader = x >= header.getLeft() && x <= header.getRight() + boolean onHeader = x >= header.getX() && x <= header.getX() + header.getWidth() && y >= header.getTop() && y <= header.getBottom(); if (mQsExpanded) { return onHeader || (mScrollView.isScrolledToBottom() && yDiff < 0) && isInQsArea(x, y); @@ -1359,10 +1457,12 @@ public class NotificationPanelView extends PanelView implements setQsExpansion(mQsMinExpansionHeight + t * (getTempQsMaxExpansion() - mQsMinExpansionHeight)); } - mNotificationStackScroller.setStackHeight(expandedHeight); + updateStackHeight(expandedHeight); updateHeader(); updateUnlockIcon(); updateNotificationTranslucency(); + mHeadsUpManager.setIsExpanded(!isFullyCollapsed()); + mNotificationStackScroller.setShadeExpanded(!isFullyCollapsed()); if (DEBUG) { invalidate(); } @@ -1432,11 +1532,17 @@ public class NotificationPanelView extends PanelView implements } } private void updateNotificationTranslucency() { - float alpha = (getNotificationsTopY() + mNotificationStackScroller.getItemHeight()) - / (mQsMinExpansionHeight + mNotificationStackScroller.getBottomStackPeekSize() - - mNotificationStackScroller.getCollapseSecondCardPadding()); - alpha = Math.max(0, Math.min(alpha, 1)); - alpha = (float) Math.pow(alpha, 0.75); + float alpha; + if (mExpandingFromHeadsUp || mHeadsUpManager.hasPinnedHeadsUp()) { + alpha = 1f; + } else { + alpha = (getNotificationsTopY() + mNotificationStackScroller.getItemHeight()) + / (mQsMinExpansionHeight + mNotificationStackScroller.getBottomStackPeekSize() + - mNotificationStackScroller.getCollapseSecondCardPadding()); + alpha = Math.max(0, Math.min(alpha, 1)); + alpha = (float) Math.pow(alpha, 0.75); + } + if (alpha != 1f && mNotificationStackScroller.getLayerType() != LAYER_TYPE_HARDWARE) { mNotificationStackScroller.setLayerType(LAYER_TYPE_HARDWARE, null); } else if (alpha == 1f @@ -1466,7 +1572,7 @@ public class NotificationPanelView extends PanelView implements lockIcon.setImageScale(LOCK_ICON_ACTIVE_SCALE, true, 150, mFastOutLinearInterpolator); } else if (!active && mUnlockIconActive && mTracking) { - lockIcon.setImageAlpha(KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT, true, + lockIcon.setImageAlpha(lockIcon.getRestingAlpha(), true /* animate */, 150, mFastOutLinearInterpolator, null); lockIcon.setImageScale(1.0f, true, 150, mFastOutLinearInterpolator); @@ -1479,8 +1585,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 +1594,14 @@ public class NotificationPanelView extends PanelView implements } private void updateHeaderShade() { - if (!mHeaderAnimatingIn) { + if (!mHeaderAnimating) { mHeader.setTranslationY(getHeaderTranslation()); } setQsTranslation(mQsExpansionHeight); } private float getHeaderTranslation() { - if (mStatusBar.getBarState() == StatusBarState.KEYGUARD - || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) { + if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) { return 0; } if (mNotificationStackScroller.getNotGoneChildCount() == 0) { @@ -1507,33 +1611,51 @@ public class NotificationPanelView extends PanelView implements return mExpandedHeight / HEADER_RUBBERBAND_FACTOR - mQsMinExpansionHeight; } } - return Math.min(0, mNotificationStackScroller.getTranslationY()) / HEADER_RUBBERBAND_FACTOR; + float stackTranslation = mNotificationStackScroller.getStackTranslation(); + float translation = stackTranslation / HEADER_RUBBERBAND_FACTOR; + if (mHeadsUpManager.hasPinnedHeadsUp() || mIsExpansionFromHeadsUp) { + translation = mNotificationStackScroller.getTopPadding() + stackTranslation + - mNotificationTopPadding - mQsMinExpansionHeight; + } + return Math.min(0, translation); } - private void updateHeaderKeyguard() { - float alphaNotifications; + /** + * @return the alpha to be used to fade out the contents on Keyguard (status bar, bottom area) + * during swiping up + */ + private float getKeyguardContentsAlpha() { + float alpha; if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) { // When on Keyguard, we hide the header as soon as the top card of the notification // stack scroller is close enough (collision distance) to the bottom of the header. - alphaNotifications = getNotificationsTopY() + alpha = getNotificationsTopY() / (mKeyguardStatusBar.getHeight() + mNotificationsHeaderCollideDistance); } else { // In SHADE_LOCKED, the top card is already really close to the header. Hide it as // soon as we start translating the stack. - alphaNotifications = getNotificationsTopY() / mKeyguardStatusBar.getHeight(); + alpha = getNotificationsTopY() / mKeyguardStatusBar.getHeight(); } - alphaNotifications = MathUtils.constrain(alphaNotifications, 0, 1); - alphaNotifications = (float) Math.pow(alphaNotifications, 0.75); + alpha = MathUtils.constrain(alpha, 0, 1); + alpha = (float) Math.pow(alpha, 0.75); + return alpha; + } + + private void 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(); @@ -1556,15 +1678,19 @@ public class NotificationPanelView extends PanelView implements protected void onExpandingFinished() { super.onExpandingFinished(); mNotificationStackScroller.onExpansionStopped(); + mHeadsUpManager.onExpandingFinished(); mIsExpanding = false; mScrollYOverride = -1; - if (mExpandedHeight == 0f) { + if (isFullyCollapsed()) { setListening(false); } else { setListening(true); } mQsExpandImmediate = false; mTwoFingerQsExpandPossible = false; + mIsExpansionFromHeadsUp = false; + mNotificationStackScroller.setTrackingHeadsUp(false); + mExpandingFromHeadsUp = false; } private void setListening(boolean listening) { @@ -1631,7 +1757,7 @@ public class NotificationPanelView extends PanelView implements } @Override - public void onHeightChanged(ExpandableView view) { + public void onHeightChanged(ExpandableView view, boolean needsAnimation) { // Block update if we are in quick settings and just the top padding changed // (i.e. view == null). @@ -1657,6 +1783,21 @@ public class NotificationPanelView extends PanelView implements protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); mAfforanceHelper.onConfigurationChanged(); + if (newConfig.orientation != mLastOrientation) { + resetVerticalPanelPosition(); + } + mLastOrientation = newConfig.orientation; + } + + @Override + public WindowInsets onApplyWindowInsets(WindowInsets insets) { + mNavigationBarBottomHeight = insets.getSystemWindowInsetBottom(); + updateMaxHeadsUpTranslation(); + return insets; + } + + private void updateMaxHeadsUpTranslation() { + mNotificationStackScroller.setHeadsUpBoundaries(getHeight(), mNavigationBarBottomHeight); } @Override @@ -1711,29 +1852,6 @@ public class NotificationPanelView extends PanelView implements } @Override - protected void onEdgeClicked(boolean right) { - if ((right && getRightIcon().getVisibility() != View.VISIBLE) - || (!right && getLeftIcon().getVisibility() != View.VISIBLE) - || isDozing()) { - return; - } - mHintAnimationRunning = true; - mAfforanceHelper.startHintAnimation(right, new Runnable() { - @Override - public void run() { - mHintAnimationRunning = false; - mStatusBar.onHintFinished(); - } - }); - boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? right : !right; - if (start) { - mStatusBar.onPhoneHintStarted(); - } else { - mStatusBar.onCameraHintStarted(); - } - } - - @Override protected void startUnlockHintAnimation() { super.startUnlockHintAnimation(); startHighlightIconAnimation(getCenterIcon()); @@ -1747,23 +1865,55 @@ public class NotificationPanelView extends PanelView implements mFastOutSlowInInterpolator, new Runnable() { @Override public void run() { - icon.setImageAlpha(KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT, - true, KeyguardAffordanceHelper.HINT_PHASE1_DURATION, + icon.setImageAlpha(icon.getRestingAlpha(), + true /* animate */, KeyguardAffordanceHelper.HINT_PHASE1_DURATION, mFastOutSlowInInterpolator, null); } }); } @Override - public float getPageWidth() { - return getWidth(); + public float getMaxTranslationDistance() { + return (float) Math.hypot(getWidth(), getHeight()); } @Override - public void onSwipingStarted() { - mSecureCameraLaunchManager.onSwipingStarted(); + public void onSwipingStarted(boolean isRightwardMotion) { + boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? isRightwardMotion + : !isRightwardMotion; + if (!start) { + mSecureCameraLaunchManager.onSwipingStarted(); + mKeyguardBottomArea.prewarmCamera(); + } requestDisallowInterceptTouchEvent(true); mOnlyAffordanceInThisMotion = true; + mQsTracking = false; + } + + @Override + public void onSwipingAborted() { + mKeyguardBottomArea.maybeCooldownCamera(); + } + + @Override + public void onIconClicked(boolean rightIcon) { + if (mHintAnimationRunning) { + return; + } + mHintAnimationRunning = true; + mAfforanceHelper.startHintAnimation(rightIcon, new Runnable() { + @Override + public void run() { + mHintAnimationRunning = false; + mStatusBar.onHintFinished(); + } + }); + rightIcon = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? !rightIcon : rightIcon; + if (rightIcon) { + mStatusBar.onCameraHintStarted(); + } else { + mStatusBar.onPhoneHintStarted(); + } } @Override @@ -1905,7 +2055,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 +2105,32 @@ public class NotificationPanelView extends PanelView implements onEmptySpaceClick(x); } + protected boolean onMiddleClicked() { + switch (mStatusBar.getBarState()) { + case StatusBarState.KEYGUARD: + if (!mDozingOnDown) { + EventLogTags.writeSysuiLockscreenGesture( + EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_TAP_UNLOCK_HINT, + 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */); + startUnlockHintAnimation(); + } + return true; + case StatusBarState.SHADE_LOCKED: + if (!mQsExpanded) { + mStatusBar.goToKeyguard(); + } + return true; + case StatusBarState.SHADE: + + // This gets called in the middle of the touch handling, where the state is still + // that we are tracking the panel. Collapse the panel after this is done. + post(mPostCollapseRunnable); + return false; + default: + return true; + } + } + @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); @@ -1980,4 +2156,87 @@ public class NotificationPanelView extends PanelView implements mNotificationStackScroller.getTopPadding(), p); } } + + @Override + public void onHeadsUpPinnedModeChanged(final boolean inPinnedMode) { + if (inPinnedMode) { + mHeadsUpExistenceChangedRunnable.run(); + updateNotificationTranslucency(); + } else { + mNotificationStackScroller.runAfterAnimationFinished( + mHeadsUpExistenceChangedRunnable); + } + } + + @Override + public void onHeadsUpPinned(ExpandableNotificationRow headsUp) { + mNotificationStackScroller.generateHeadsUpAnimation(headsUp, true); + } + + @Override + public void onHeadsUpUnPinned(ExpandableNotificationRow headsUp) { + } + + @Override + public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) { + mNotificationStackScroller.generateHeadsUpAnimation(entry.row, isHeadsUp); + } + + @Override + public void setHeadsUpManager(HeadsUpManager headsUpManager) { + super.setHeadsUpManager(headsUpManager); + mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager, mNotificationStackScroller, + this); + } + + public void setTrackingHeadsUp(boolean tracking) { + if (tracking) { + mNotificationStackScroller.setTrackingHeadsUp(true); + mExpandingFromHeadsUp = true; + } + // otherwise we update the state when the expansion is finished + } + + @Override + protected void onClosingFinished() { + super.onClosingFinished(); + resetVerticalPanelPosition(); + } + + /** + * Updates the vertical position of the panel so it is positioned closer to the touch + * responsible for opening the panel. + * + * @param x the x-coordinate the touch event + */ + private void updateVerticalPanelPosition(float x) { + if (mNotificationStackScroller.getWidth() * 1.75f > getWidth()) { + resetVerticalPanelPosition(); + return; + } + float leftMost = mPositionMinSideMargin + mNotificationStackScroller.getWidth() / 2; + float rightMost = getWidth() - mPositionMinSideMargin + - mNotificationStackScroller.getWidth() / 2; + if (Math.abs(x - getWidth() / 2) < mNotificationStackScroller.getWidth() / 4) { + x = getWidth() / 2; + } + x = Math.min(rightMost, Math.max(leftMost, x)); + setVerticalPanelTranslation(x - + (mNotificationStackScroller.getLeft() + mNotificationStackScroller.getWidth()/2)); + } + + private void resetVerticalPanelPosition() { + setVerticalPanelTranslation(0f); + } + + private void setVerticalPanelTranslation(float translation) { + mNotificationStackScroller.setTranslationX(translation); + mScrollView.setTranslationX(translation); + mHeader.setTranslationX(translation); + } + + private void updateStackHeight(float stackHeight) { + mNotificationStackScroller.setStackHeight(stackHeight); + updateKeyguardBottomAreaAlpha(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java index e7b0c4c..cbb71c5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java @@ -18,7 +18,6 @@ package com.android.systemui.statusbar.phone; import android.content.Context; import android.graphics.Canvas; -import android.graphics.Rect; import android.util.AttributeSet; import android.view.View; import android.view.ViewStub; @@ -38,6 +37,7 @@ public class NotificationsQuickSettingsContainer extends FrameLayout private View mStackScroller; private View mKeyguardStatusBar; private boolean mInflated; + private boolean mQsExpanded; public NotificationsQuickSettingsContainer(Context context, AttributeSet attrs) { super(context, attrs); @@ -65,26 +65,29 @@ public class NotificationsQuickSettingsContainer extends FrameLayout boolean userSwitcherVisible = mInflated && mUserSwitcher.getVisibility() == View.VISIBLE; boolean statusBarVisible = mKeyguardStatusBar.getVisibility() == View.VISIBLE; + View stackQsTop = mQsExpanded ? mStackScroller : mScrollView; + View stackQsBottom = !mQsExpanded ? mStackScroller : mScrollView; // Invert the order of the scroll view and user switcher such that the notifications receive // touches first but the panel gets drawn above. if (child == mScrollView) { - return super.drawChild(canvas, mStackScroller, drawingTime); - } else if (child == mStackScroller) { - return super.drawChild(canvas, - userSwitcherVisible && statusBarVisible ? mUserSwitcher + return super.drawChild(canvas, userSwitcherVisible && statusBarVisible ? mUserSwitcher : statusBarVisible ? mKeyguardStatusBar : userSwitcherVisible ? mUserSwitcher - : mScrollView, + : stackQsBottom, drawingTime); + } else if (child == mStackScroller) { + return super.drawChild(canvas, + userSwitcherVisible && statusBarVisible ? mKeyguardStatusBar + : statusBarVisible || userSwitcherVisible ? stackQsBottom + : stackQsTop, drawingTime); } else if (child == mUserSwitcher) { return super.drawChild(canvas, - userSwitcherVisible && statusBarVisible ? mKeyguardStatusBar - : mScrollView, + userSwitcherVisible && statusBarVisible ? stackQsBottom + : stackQsTop, drawingTime); } else if (child == mKeyguardStatusBar) { return super.drawChild(canvas, - userSwitcherVisible && statusBarVisible ? mScrollView - : mScrollView, + stackQsTop, drawingTime); }else { return super.drawChild(canvas, child, drawingTime); @@ -98,4 +101,11 @@ public class NotificationsQuickSettingsContainer extends FrameLayout mInflated = true; } } + + public void setQsExpanded(boolean expanded) { + if (mQsExpanded != expanded) { + mQsExpanded = expanded; + invalidate(); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java index 3efaaff..f3d4c7f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java @@ -140,7 +140,7 @@ public class PanelBar extends FrameLayout { mPanelHolder.setSelectedPanel(mTouchingPanel); for (PanelView pv : mPanels) { if (pv != panel) { - pv.collapse(false /* delayed */); + pv.collapse(false /* delayed */, 1.0f /* speedUpFactor */); } } } @@ -157,8 +157,7 @@ public class PanelBar extends FrameLayout { if (DEBUG) LOG("panelExpansionChanged: start state=%d panel=%s", mState, panel.getName()); mPanelExpandedFractionSum = 0f; for (PanelView pv : mPanels) { - boolean visible = pv.getExpandedHeight() > 0; - pv.setVisibility(visible ? View.VISIBLE : View.GONE); + pv.setVisibility(expanded ? View.VISIBLE : View.INVISIBLE); // adjust any other panels that may be partially visible if (expanded) { if (mState == STATE_CLOSED) { @@ -167,7 +166,7 @@ public class PanelBar extends FrameLayout { } fullyClosed = false; final float thisFrac = pv.getExpandedFraction(); - mPanelExpandedFractionSum += (visible ? thisFrac : 0); + mPanelExpandedFractionSum += thisFrac; if (DEBUG) LOG("panelExpansionChanged: -> %s: f=%.1f", pv.getName(), thisFrac); if (panel == pv) { if (thisFrac == 1f) fullyOpenedPanel = panel; @@ -187,16 +186,15 @@ public class PanelBar extends FrameLayout { (fullyOpenedPanel!=null)?" fullyOpened":"", fullyClosed?" fullyClosed":""); } - public void collapseAllPanels(boolean animate) { + public void collapseAllPanels(boolean animate, boolean delayed, float speedUpFactor) { boolean waiting = false; for (PanelView pv : mPanels) { if (animate && !pv.isFullyCollapsed()) { - pv.collapse(true /* delayed */); + pv.collapse(delayed, speedUpFactor); waiting = true; } else { pv.resetViews(); pv.setExpandedFraction(0); // just in case - pv.setVisibility(View.GONE); pv.cancelPeek(); } } 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..85f312c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java @@ -38,6 +38,7 @@ import com.android.systemui.R; import com.android.systemui.doze.DozeLog; import com.android.systemui.statusbar.FlingAnimationUtils; import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.policy.HeadsUpManager; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -45,12 +46,13 @@ import java.io.PrintWriter; public abstract class PanelView extends FrameLayout { public static final boolean DEBUG = PanelBar.DEBUG; public static final String TAG = PanelView.class.getSimpleName(); - private final void logf(String fmt, Object... args) { Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args)); } protected PhoneStatusBar mStatusBar; + protected HeadsUpManager mHeadsUpManager; + private float mPeekHeight; private float mHintDistance; private int mEdgeTapAreaWidth; @@ -73,6 +75,8 @@ public abstract class PanelView extends FrameLayout { private boolean mTouchAboveFalsingThreshold; private int mUnlockFalsingThreshold; private boolean mTouchStartedInEmptyArea; + private boolean mMotionAborted; + private boolean mUpwardsWhenTresholdReached; private ValueAnimator mHeightAnimator; private ObjectAnimator mPeekAnimator; @@ -98,9 +102,15 @@ public abstract class PanelView extends FrameLayout { private boolean mPeekPending; private boolean mCollapseAfterPeek; + + /** + * Speed-up factor to be used when {@link #mFlingCollapseRunnable} runs the next time. + */ + private float mNextCollapseSpeedUpFactor = 1.0f; + private boolean mExpanding; private boolean mGestureWaitForTouchSlop; - private boolean mDozingOnDown; + private boolean mIgnoreXTouchSlop; private Runnable mPeekRunnable = new Runnable() { @Override public void run() { @@ -162,7 +172,7 @@ public abstract class PanelView extends FrameLayout { postOnAnimation(new Runnable() { @Override public void run() { - collapse(false /* delayed */); + collapse(false /* delayed */, 1.0f /* speedUpFactor */); } }); } @@ -209,7 +219,8 @@ public abstract class PanelView extends FrameLayout { @Override public boolean onTouchEvent(MotionEvent event) { - if (mInstantExpanding || mTouchDisabled) { + if (mInstantExpanding || mTouchDisabled + || (mMotionAborted && event.getActionMasked() != MotionEvent.ACTION_DOWN)) { return false; } @@ -227,32 +238,29 @@ public abstract class PanelView extends FrameLayout { pointerIndex = 0; mTrackingPointer = event.getPointerId(pointerIndex); } - final float y = event.getY(pointerIndex); final float x = event.getX(pointerIndex); + final float y = event.getY(pointerIndex); if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { - mGestureWaitForTouchSlop = mExpandedHeight == 0f; + mGestureWaitForTouchSlop = isFullyCollapsed() || hasConflictingGestures(); + mIgnoreXTouchSlop = isFullyCollapsed() || shouldGestureIgnoreXTouchSlop(x, y); } - boolean waitForTouchSlop = hasConflictingGestures() || mGestureWaitForTouchSlop; switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: - mInitialTouchY = y; - mInitialTouchX = x; - mInitialOffsetOnTouch = mExpandedHeight; - mTouchSlopExceeded = false; + startExpandMotion(x, y, false /* startTracking */, mExpandedHeight); mJustPeeked = false; - mPanelClosedOnDown = mExpandedHeight == 0.0f; + mPanelClosedOnDown = isFullyCollapsed(); mHasLayoutedSinceDown = false; mUpdateFlingOnLayout = false; + mMotionAborted = false; mPeekTouching = mPanelClosedOnDown; mTouchAboveFalsingThreshold = false; - mDozingOnDown = isDozing(); if (mVelocityTracker == null) { initVelocityTracker(); } trackMovement(event); - if (!waitForTouchSlop || (mHeightAnimator != null && !mHintAnimationRunning) || + if (!mGestureWaitForTouchSlop || (mHeightAnimator != null && !mHintAnimationRunning) || mPeekPending || mPeekAnimator != null) { cancelHeightAnimator(); cancelPeek(); @@ -260,7 +268,7 @@ public abstract class PanelView extends FrameLayout { || mPeekPending || mPeekAnimator != null; onTrackingStarted(); } - if (mExpandedHeight == 0) { + if (isFullyCollapsed()) { schedulePeek(); } break; @@ -273,12 +281,16 @@ public abstract class PanelView extends FrameLayout { final float newY = event.getY(newIndex); final float newX = event.getX(newIndex); mTrackingPointer = event.getPointerId(newIndex); - mInitialOffsetOnTouch = mExpandedHeight; - mInitialTouchY = newY; - mInitialTouchX = newX; + startExpandMotion(newX, newY, true /* startTracking */, mExpandedHeight); + } + break; + case MotionEvent.ACTION_POINTER_DOWN: + if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) { + mMotionAborted = true; + endMotionEvent(event, x, y, true /* forceCancel */); + return false; } break; - case MotionEvent.ACTION_MOVE: float h = y - mInitialTouchY; @@ -286,13 +298,11 @@ public abstract class PanelView extends FrameLayout { // y-component of the gesture, as we have no conflicting horizontal gesture. if (Math.abs(h) > mTouchSlop && (Math.abs(h) > Math.abs(x - mInitialTouchX) - || mInitialOffsetOnTouch == 0f)) { + || mIgnoreXTouchSlop)) { mTouchSlopExceeded = true; - if (waitForTouchSlop && !mTracking) { + if (mGestureWaitForTouchSlop && !mTracking) { if (!mJustPeeked && mInitialOffsetOnTouch != 0f) { - mInitialOffsetOnTouch = mExpandedHeight; - mInitialTouchX = x; - mInitialTouchY = y; + startExpandMotion(x, y, false /* startTracking */, mExpandedHeight); h = 0; } cancelHeightAnimator(); @@ -310,8 +320,9 @@ public abstract class PanelView extends FrameLayout { } if (-h >= getFalsingThreshold()) { mTouchAboveFalsingThreshold = true; + mUpwardsWhenTresholdReached = isDirectionUpwards(x, y); } - if (!mJustPeeked && (!waitForTouchSlop || mTracking) && !isTrackingBlocked()) { + if (!mJustPeeked && (!mGestureWaitForTouchSlop || mTracking) && !isTrackingBlocked()) { setExpandedHeightInternal(newHeight); } @@ -320,26 +331,59 @@ public abstract class PanelView extends FrameLayout { case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: - mTrackingPointer = -1; trackMovement(event); - if ((mTracking && mTouchSlopExceeded) - || Math.abs(x - mInitialTouchX) > mTouchSlop - || Math.abs(y - mInitialTouchY) > mTouchSlop - || event.getActionMasked() == MotionEvent.ACTION_CANCEL) { - float vel = 0f; - float vectorVel = 0f; - if (mVelocityTracker != null) { - mVelocityTracker.computeCurrentVelocity(1000); - vel = mVelocityTracker.getYVelocity(); - vectorVel = (float) Math.hypot( - mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity()); - } - boolean expand = flingExpands(vel, vectorVel) - || event.getActionMasked() == MotionEvent.ACTION_CANCEL; - onTrackingStopped(expand); - DozeLog.traceFling(expand, mTouchAboveFalsingThreshold, - mStatusBar.isFalsingThresholdNeeded(), - mStatusBar.isScreenOnComingFromTouch()); + endMotionEvent(event, x, y, false /* forceCancel */); + break; + } + return !mGestureWaitForTouchSlop || mTracking; + } + + /** + * @return whether the swiping direction is upwards and above a 45 degree angle compared to the + * horizontal direction + */ + private boolean isDirectionUpwards(float x, float y) { + float xDiff = x - mInitialTouchX; + float yDiff = y - mInitialTouchY; + if (yDiff >= 0) { + return false; + } + return Math.abs(yDiff) >= Math.abs(xDiff); + } + + protected void startExpandMotion(float newX, float newY, boolean startTracking, + float expandedHeight) { + mInitialOffsetOnTouch = expandedHeight; + mInitialTouchY = newY; + mInitialTouchX = newX; + if (startTracking) { + mTouchSlopExceeded = true; + onTrackingStarted(); + } + } + + private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) { + mTrackingPointer = -1; + if ((mTracking && mTouchSlopExceeded) + || Math.abs(x - mInitialTouchX) > mTouchSlop + || Math.abs(y - mInitialTouchY) > mTouchSlop + || event.getActionMasked() == MotionEvent.ACTION_CANCEL + || forceCancel) { + float vel = 0f; + float vectorVel = 0f; + if (mVelocityTracker != null) { + mVelocityTracker.computeCurrentVelocity(1000); + vel = mVelocityTracker.getYVelocity(); + vectorVel = (float) Math.hypot( + mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity()); + } + boolean expand = flingExpands(vel, vectorVel, x, y) + || event.getActionMasked() == MotionEvent.ACTION_CANCEL + || forceCancel; + 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 +393,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, isFalseTouch(x, y)); + mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown; + if (mUpdateFlingOnLayout) { + mUpdateFlingVelocity = vel; + } + } else { + boolean expands = onEmptySpaceClick(mInitialTouchX); + onTrackingStopped(expands); + } - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); - mVelocityTracker = null; - } - mPeekTouching = false; - break; + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; } - return !waitForTouchSlop || mTracking; + mPeekTouching = false; } private int getFalsingThreshold() { @@ -376,6 +417,8 @@ public abstract class PanelView extends FrameLayout { protected abstract boolean hasConflictingGestures(); + protected abstract boolean shouldGestureIgnoreXTouchSlop(float x, float y); + protected void onTrackingStopped(boolean expand) { mTracking = false; mBar.onTrackingStopped(PanelView.this, expand); @@ -391,7 +434,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 +471,11 @@ public abstract class PanelView extends FrameLayout { mTouchStartedInEmptyArea = !isInContentBounds(x, y); mTouchSlopExceeded = false; mJustPeeked = false; - mPanelClosedOnDown = mExpandedHeight == 0.0f; + mMotionAborted = false; + mPanelClosedOnDown = isFullyCollapsed(); mHasLayoutedSinceDown = false; mUpdateFlingOnLayout = false; mTouchAboveFalsingThreshold = false; - mDozingOnDown = isDozing(); initVelocityTracker(); trackMovement(event); break; @@ -445,25 +489,32 @@ public abstract class PanelView extends FrameLayout { mInitialTouchY = event.getY(newIndex); } break; - + case MotionEvent.ACTION_POINTER_DOWN: + if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) { + mMotionAborted = true; + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + } + break; case MotionEvent.ACTION_MOVE: final float h = y - mInitialTouchY; trackMovement(event); if (scrolledToBottom || mTouchStartedInEmptyArea) { if (h < -mTouchSlop && h < -Math.abs(x - mInitialTouchX)) { cancelHeightAnimator(); - mInitialOffsetOnTouch = mExpandedHeight; - mInitialTouchY = y; - mInitialTouchX = x; - mTracking = true; - mTouchSlopExceeded = true; - onTrackingStarted(); + startExpandMotion(x, y, true /* startTracking */, mExpandedHeight); return true; } } break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } break; } return false; @@ -520,8 +571,8 @@ public abstract class PanelView extends FrameLayout { * @param vectorVel the length of the vectorial velocity * @return whether a fling should expands the panel; contracts otherwise */ - protected boolean flingExpands(float vel, float vectorVel) { - if (isBelowFalsingThreshold()) { + protected boolean flingExpands(float vel, float vectorVel, float x, float y) { + if (isFalseTouch(x, y)) { return true; } if (Math.abs(vectorVel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) { @@ -531,14 +582,41 @@ public abstract class PanelView extends FrameLayout { } } - private boolean isBelowFalsingThreshold() { - return !mTouchAboveFalsingThreshold && mStatusBar.isFalsingThresholdNeeded(); + /** + * @param x the final x-coordinate when the finger was lifted + * @param y the final y-coordinate when the finger was lifted + * @return whether this motion should be regarded as a false touch + */ + private boolean isFalseTouch(float x, float y) { + if (!mStatusBar.isFalsingThresholdNeeded()) { + return false; + } + if (!mTouchAboveFalsingThreshold) { + return true; + } + if (mUpwardsWhenTresholdReached) { + return false; + } + return !isDirectionUpwards(x, y); } protected void fling(float vel, boolean expand) { + fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, false); + } + + protected void fling(float vel, boolean expand, boolean expandBecauseOfFalsing) { + fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, expandBecauseOfFalsing); + } + + protected void fling(float vel, boolean expand, float collapseSpeedUpFactor, + boolean expandBecauseOfFalsing) { cancelPeek(); float target = expand ? getMaxPanelHeight() : 0.0f; + flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing); + } + protected void flingToHeight(float vel, boolean expand, float target, + float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) { // Hack to make the expand transition look nice when clear all button is visible - we make // the animation only to the last notification, and then jump to the maximum panel height so // clear all just fades in and the decelerating motion is towards the last notification. @@ -555,12 +633,11 @@ public abstract class PanelView extends FrameLayout { mOverExpandedBeforeFling = getOverExpansionAmount() > 0f; ValueAnimator animator = createHeightAnimator(target); if (expand) { - boolean belowFalsingThreshold = isBelowFalsingThreshold(); - if (belowFalsingThreshold) { + if (expandBecauseOfFalsing) { vel = 0; } mFlingAnimationUtils.apply(animator, mExpandedHeight, target, vel, getHeight()); - if (belowFalsingThreshold) { + if (expandBecauseOfFalsing) { animator.setDuration(350); } } else { @@ -570,7 +647,8 @@ public abstract class PanelView extends FrameLayout { // Make it shorter if we run a canned animation if (vel == 0) { animator.setDuration((long) - (animator.getDuration() * getCannedFlingDurationFactor())); + (animator.getDuration() * getCannedFlingDurationFactor() + / collapseSpeedUpFactor)); } } animator.addListener(new AnimatorListenerAdapter() { @@ -618,7 +696,7 @@ public abstract class PanelView extends FrameLayout { mHasLayoutedSinceDown = true; if (mUpdateFlingOnLayout) { abortAnimations(); - fling(mUpdateFlingVelocity, true); + fling(mUpdateFlingVelocity, true /* expands */); mUpdateFlingOnLayout = false; } } @@ -629,7 +707,7 @@ public abstract class PanelView extends FrameLayout { // If the user isn't actively poking us, let's update the height if ((!mTracking || isTrackingBlocked()) && mHeightAnimator == null - && mExpandedHeight > 0 + && !isFullyCollapsed() && currentMaxPanelHeight != mExpandedHeight && !mPeekPending && mPeekAnimator == null @@ -715,7 +793,7 @@ public abstract class PanelView extends FrameLayout { mBar = panelBar; } - public void collapse(boolean delayed) { + public void collapse(boolean delayed, float speedUpFactor) { if (DEBUG) logf("collapse: " + this); if (mPeekPending || mPeekAnimator != null) { mCollapseAfterPeek = true; @@ -731,9 +809,10 @@ public abstract class PanelView extends FrameLayout { mClosing = true; notifyExpandingStarted(); if (delayed) { + mNextCollapseSpeedUpFactor = speedUpFactor; postDelayed(mFlingCollapseRunnable, 120); } else { - fling(0, false /* expand */); + fling(0, false /* expand */, speedUpFactor, false /* expandBecauseOfFalsing */); } } } @@ -741,7 +820,8 @@ public abstract class PanelView extends FrameLayout { private final Runnable mFlingCollapseRunnable = new Runnable() { @Override public void run() { - fling(0, false /* expand */); + fling(0, false /* expand */, mNextCollapseSpeedUpFactor, + false /* expandBecauseOfFalsing */); } }; @@ -779,7 +859,7 @@ public abstract class PanelView extends FrameLayout { if (mExpanding) { notifyExpandingFinished(); } - setVisibility(VISIBLE); + notifyBarPanelExpansionChanged(); // Wait for window manager to pickup the change, so we know the maximum height of the panel // then. @@ -915,9 +995,9 @@ public abstract class PanelView extends FrameLayout { return animator; } - private void notifyBarPanelExpansionChanged() { + protected void notifyBarPanelExpansionChanged() { mBar.panelExpansionChanged(this, mExpandedFraction, mExpandedFraction > 0f || mPeekPending - || mPeekAnimator != null); + || mPeekAnimator != null || mInstantExpanding || mHeadsUpManager.hasPinnedHeadsUp()); } /** @@ -929,50 +1009,17 @@ public abstract class PanelView extends FrameLayout { if (mHintAnimationRunning) { return true; } - if (x < mEdgeTapAreaWidth - && mStatusBar.getBarState() == StatusBarState.KEYGUARD) { - onEdgeClicked(false /* right */); - return true; - } else if (x > getWidth() - mEdgeTapAreaWidth - && mStatusBar.getBarState() == StatusBarState.KEYGUARD) { - onEdgeClicked(true /* right */); - return true; - } else { - return onMiddleClicked(); - } + return onMiddleClicked(); } - private final Runnable mPostCollapseRunnable = new Runnable() { + protected final Runnable mPostCollapseRunnable = new Runnable() { @Override public void run() { - collapse(false /* delayed */); + collapse(false /* delayed */, 1.0f /* speedUpFactor */); } }; - private boolean onMiddleClicked() { - switch (mStatusBar.getBarState()) { - case StatusBarState.KEYGUARD: - if (!mDozingOnDown) { - EventLogTags.writeSysuiLockscreenGesture( - EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_TAP_UNLOCK_HINT, - 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */); - startUnlockHintAnimation(); - } - return true; - case StatusBarState.SHADE_LOCKED: - mStatusBar.goToKeyguard(); - return true; - case StatusBarState.SHADE: - // This gets called in the middle of the touch handling, where the state is still - // that we are tracking the panel. Collapse the panel after this is done. - post(mPostCollapseRunnable); - return false; - default: - return true; - } - } - - protected abstract void onEdgeClicked(boolean right); + protected abstract boolean onMiddleClicked(); protected abstract boolean isDozing(); @@ -1009,4 +1056,8 @@ public abstract class PanelView extends FrameLayout { * @return the height of the clear all button, in pixels */ protected abstract int getClearAllHeight(); + + public void setHeadsUpManager(HeadsUpManager headsUpManager) { + mHeadsUpManager = headsUpManager; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index f227107..1c46d42 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -17,22 +17,8 @@ package com.android.systemui.statusbar.phone; -import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; -import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN; -import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; -import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; -import static android.app.StatusBarManager.windowStateToString; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSLUCENT; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_WARNING; - import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.animation.TimeInterpolator; import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityManagerNative; @@ -82,40 +68,30 @@ import android.provider.Settings; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.StatusBarNotification; -import android.text.TextUtils; import android.util.ArraySet; import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; import android.view.Display; -import android.view.Gravity; -import android.view.HardwareCanvas; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.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.logging.MetricsLogger; import com.android.internal.statusbar.StatusBarIcon; import com.android.keyguard.KeyguardHostView.OnDismissAction; import com.android.keyguard.ViewMediatorCallback; @@ -123,13 +99,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.Prefs; import com.android.systemui.R; +import com.android.systemui.assist.AssistGestureManager; import com.android.systemui.doze.DozeHost; import com.android.systemui.doze.DozeLog; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.qs.QSPanel; -import com.android.systemui.recent.ScreenPinningRequest; +import com.android.systemui.recents.ScreenPinningRequest; import com.android.systemui.statusbar.ActivatableNotificationView; import com.android.systemui.statusbar.BackDropView; import com.android.systemui.statusbar.BaseStatusBar; @@ -146,7 +123,6 @@ import com.android.systemui.statusbar.NotificationOverflowContainer; import com.android.systemui.statusbar.ScrimView; import com.android.systemui.statusbar.SignalClusterView; import com.android.systemui.statusbar.SpeedBumpView; -import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener; import com.android.systemui.statusbar.policy.AccessibilityController; @@ -156,7 +132,7 @@ import com.android.systemui.statusbar.policy.BluetoothControllerImpl; import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.statusbar.policy.CastControllerImpl; import com.android.systemui.statusbar.policy.FlashlightController; -import com.android.systemui.statusbar.policy.HeadsUpNotificationView; +import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.HotspotControllerImpl; import com.android.systemui.statusbar.policy.KeyButtonView; import com.android.systemui.statusbar.policy.KeyguardMonitor; @@ -172,8 +148,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,11 +156,28 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.TreeSet; + +import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN; +import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; +import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; +import static android.app.StatusBarManager.windowStateToString; +import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT; +import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT; +import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE; +import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT; +import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSLUCENT; +import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT; +import static com.android.systemui.statusbar.phone.BarTransitions.MODE_WARNING; public class PhoneStatusBar extends BaseStatusBar implements DemoMode, - DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener { + DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener, + HeadsUpManager.OnHeadsUpChangedListener { static final String TAG = "PhoneStatusBar"; public static final boolean DEBUG = BaseStatusBar.DEBUG; public static final boolean SPEW = false; @@ -199,9 +191,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 +204,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 +250,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 +266,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 +290,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, int mKeyguardMaxNotificationCount; - // carrier/wifi label - private TextView mCarrierLabel; - private boolean mCarrierLabelVisible = false; - private int mCarrierLabelHeight; - private int mStatusBarHeaderHeight; - - private boolean mShowCarrierInPanel = false; - - // position - int[] mPositionTmp = new int[2]; boolean mExpandedVisible; private int mNavigationBarWindowState = WINDOW_STATE_SHOWING; @@ -342,14 +297,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, // the tracker view int mTrackingPosition; // the position of the top of the tracking view. - // ticker - private boolean mTickerEnabled; - private Ticker mTicker; - private View mTickerView; - private boolean mTicking; - // Tracking finger for opening/closing. - int mEdgeBorder; // corresponds to R.dimen.status_bar_edge_ignore boolean mTracking; VelocityTracker mVelocityTracker; @@ -357,7 +305,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>(); // for disabling the status bar - int mDisabled = 0; + int mDisabled1 = 0; + int mDisabled2 = 0; // tracking calls to View.setSystemUiVisibility() int mSystemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE; @@ -374,6 +323,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()) { @@ -410,11 +361,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, if (wasUsing != mUseHeadsUp) { if (!mUseHeadsUp) { Log.d(TAG, "dismissing any existing heads up notification on disable event"); - setHeadsUpVisibility(false); - mHeadsUpNotificationView.release(); - removeHeadsUpView(); - } else { - addHeadsUpView(); + mHeadsUpManager.releaseAllImmediately(); } } } @@ -442,7 +389,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); @@ -482,7 +428,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } }; - private int mDisabledUnmodified; + private int mDisabledUnmodified1; + private int mDisabledUnmodified2; /** Keys of notifications currently visible to the user. */ private final ArraySet<String> mCurrentlyVisibleNotifications = new ArraySet<String>(); @@ -498,10 +445,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 +524,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, goToLockedShade(null); } }; + private HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>> mTmpChildOrderMap + = new HashMap<>(); + private HashSet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new HashSet<>(); + private RankingMap mLatestRankingMap; @Override public void start() { @@ -585,6 +536,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 +585,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; @@ -648,7 +598,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } } return mStatusBarWindow.onTouchEvent(event); - }}); + } + }); mStatusBarView = (PhoneStatusBarView) mStatusBarWindow.findViewById(R.id.status_bar); mStatusBarView.setBar(this); @@ -662,15 +613,17 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, if (!ActivityManager.isHighEndGfx()) { mStatusBarWindow.setBackground(null); - mNotificationPanel.setBackground(new FastColorDrawable(context.getResources().getColor( + mNotificationPanel.setBackground(new FastColorDrawable(context.getColor( R.color.notification_panel_solid_background))); } - if (ENABLE_HEADS_UP) { - mHeadsUpNotificationView = - (HeadsUpNotificationView) View.inflate(context, R.layout.heads_up, null); - mHeadsUpNotificationView.setVisibility(View.GONE); - mHeadsUpNotificationView.setBar(this); - } + + mHeadsUpManager = new HeadsUpManager(context, mNotificationPanel.getViewTreeObserver()); + mHeadsUpManager.setBar(this); + mHeadsUpManager.addListener(this); + mHeadsUpManager.addListener(mNotificationPanel); + mNotificationPanel.setHeadsUpManager(mHeadsUpManager); + mNotificationData.setHeadsUpManager(mHeadsUpManager); + if (MULTIUSER_DEBUG) { mNotificationPanelDebugText = (TextView) mNotificationPanel.findViewById( R.id.header_debug_info); @@ -686,14 +639,14 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mNavigationBarView = (NavigationBarView) View.inflate(context, R.layout.navigation_bar, null); - mNavigationBarView.setDisabledFlags(mDisabled); + mNavigationBarView.setDisabledFlags(mDisabled1); mNavigationBarView.setBar(this); mNavigationBarView.setOnVerticalChangedListener( new NavigationBarView.OnVerticalChangedListener() { @Override public void onVerticalChanged(boolean isVertical) { - if (mSearchPanelView != null) { - mSearchPanelView.setHorizontal(isVertical); + if (mAssistGestureManager != null) { + mAssistGestureManager.onConfigurationChanged(); } mNotificationPanel.setQsScrimEnabled(!isVertical); } @@ -712,19 +665,13 @@ 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); + mStackScroller.setHeadsUpManager(mHeadsUpManager); + mGroupManager.setOnGroupChangeListener(mStackScroller); mKeyguardIconOverflowContainer = (NotificationOverflowContainer) LayoutInflater.from(mContext).inflate( @@ -744,6 +691,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mDismissView.setOnButtonClickListener(new View.OnClickListener() { @Override public void onClick(View v) { + MetricsLogger.action(mContext, MetricsLogger.ACTION_DISMISS_ALL_NOTES); clearAllNotifications(); } }); @@ -756,7 +704,11 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, ScrimView scrimBehind = (ScrimView) mStatusBarWindow.findViewById(R.id.scrim_behind); ScrimView scrimInFront = (ScrimView) mStatusBarWindow.findViewById(R.id.scrim_in_front); - mScrimController = new ScrimController(scrimBehind, scrimInFront, mScrimSrcModeEnabled); + View headsUpScrim = mStatusBarWindow.findViewById(R.id.heads_up_scrim); + mScrimController = new ScrimController(scrimBehind, scrimInFront, headsUpScrim, + mScrimSrcModeEnabled); + mHeadsUpManager.addListener(mScrimController); + mStackScroller.setScrimController(mScrimController); mScrimController.setBackDropView(mBackdrop); mStatusBarView.setScrimController(mScrimController); mDozeScrimController = new DozeScrimController(mScrimController, context); @@ -764,7 +716,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 +725,12 @@ 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 +789,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 +841,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 +857,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, // listen for USER_SETUP_COMPLETE setting (per-user) resetUserSetupObserver(); - startGlyphRasterizeHack(); return mStatusBarView; } @@ -948,9 +868,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 +938,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 +962,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 +994,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 +1044,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 @@ -1232,75 +1091,17 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, return lp; } - private void addHeadsUpView() { - int headsUpHeight = mContext.getResources() - .getDimensionPixelSize(R.dimen.heads_up_window_height); - WindowManager.LayoutParams lp = new WindowManager.LayoutParams( - LayoutParams.MATCH_PARENT, headsUpHeight, - WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, // above the status bar! - WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN - | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS - | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL - | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM - | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, - PixelFormat.TRANSLUCENT); - lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; - lp.gravity = Gravity.TOP; - lp.setTitle("Heads Up"); - lp.packageName = mContext.getPackageName(); - lp.windowAnimations = R.style.Animation_StatusBar_HeadsUp; - - mWindowManager.addView(mHeadsUpNotificationView, lp); - } - - private void removeHeadsUpView() { - mWindowManager.removeView(mHeadsUpNotificationView); - } - - public void refreshAllStatusBarIcons() { - refreshAllIconsForLayout(mStatusIcons); - refreshAllIconsForLayout(mStatusIconsKeyguard); - refreshAllIconsForLayout(mNotificationIcons); - } - - private void refreshAllIconsForLayout(LinearLayout ll) { - final int count = ll.getChildCount(); - for (int n = 0; n < count; n++) { - View child = ll.getChildAt(n); - if (child instanceof StatusBarIconView) { - ((StatusBarIconView) child).updateDrawable(); - } - } - } - public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) { - if (SPEW) Log.d(TAG, "addIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex - + " icon=" + icon); - StatusBarIconView view = new StatusBarIconView(mContext, slot, null); - view.set(icon); - mStatusIcons.addView(view, viewIndex, new LinearLayout.LayoutParams( - LayoutParams.WRAP_CONTENT, mIconSize)); - view = new StatusBarIconView(mContext, slot, null); - view.set(icon); - mStatusIconsKeyguard.addView(view, viewIndex, new LinearLayout.LayoutParams( - LayoutParams.WRAP_CONTENT, mIconSize)); + mIconController.addSystemIcon(slot, index, viewIndex, icon); } public void updateIcon(String slot, int index, int viewIndex, StatusBarIcon old, StatusBarIcon icon) { - if (SPEW) Log.d(TAG, "updateIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex - + " old=" + old + " icon=" + icon); - StatusBarIconView view = (StatusBarIconView) mStatusIcons.getChildAt(viewIndex); - view.set(icon); - view = (StatusBarIconView) mStatusIconsKeyguard.getChildAt(viewIndex); - view.set(icon); + mIconController.updateSystemIcon(slot, index, viewIndex, old, icon); } public void removeIcon(String slot, int index, int viewIndex) { - if (SPEW) Log.d(TAG, "removeIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex); - mStatusIcons.removeViewAt(viewIndex); - mStatusIconsKeyguard.removeViewAt(viewIndex); + mIconController.removeSystemIcon(slot, index, viewIndex); } public UserHandle getCurrentUserHandle() { @@ -1308,27 +1109,22 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } @Override - public void addNotification(StatusBarNotification notification, RankingMap ranking) { + public void addNotification(StatusBarNotification notification, RankingMap ranking, + Entry oldEntry) { if (DEBUG) Log.d(TAG, "addNotification key=" + notification.getKey()); - if (mUseHeadsUp && shouldInterrupt(notification)) { - if (DEBUG) Log.d(TAG, "launching notification in heads up mode"); - Entry interruptionCandidate = new Entry(notification, null); - ViewGroup holder = mHeadsUpNotificationView.getHolder(); - if (inflateViewsForHeadsUp(interruptionCandidate, holder)) { - // 1. Populate mHeadsUpNotificationView - mHeadsUpNotificationView.showNotification(interruptionCandidate); - - // do not show the notification in the shade, yet. - return; - } - } Entry shadeEntry = createNotificationViews(notification); if (shadeEntry == null) { return; } + boolean isHeadsUped = mUseHeadsUp && shouldInterrupt(notification); + if (isHeadsUped) { + mHeadsUpManager.showNotification(shadeEntry); + // Mark as seen immediately + setNotificationShown(notification); + } - if (notification.getNotification().fullScreenIntent != null) { + if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) { // Stop screensaver if the notification has a full-screen intent. // (like an incoming phone call) awakenDreams(); @@ -1341,58 +1137,10 @@ 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; - } - shadeEntry.setInterruption(); - - addNotificationViews(shadeEntry, null); - // Recalculate the position of the sliding windows and the titles. - setAreThereNotifications(); - updateExpandedViewPos(EXPANDED_LEAVE_ALONE); - } - - @Override - public void resetHeadsUpDecayTimer() { - mHandler.removeMessages(MSG_DECAY_HEADS_UP); - if (mUseHeadsUp && mHeadsUpNotificationDecay > 0 - && mHeadsUpNotificationView.isClearable()) { - mHandler.sendEmptyMessageDelayed(MSG_DECAY_HEADS_UP, mHeadsUpNotificationDecay); - } - } - - @Override - public void scheduleHeadsUpOpen() { - mHandler.removeMessages(MSG_SHOW_HEADS_UP); - mHandler.sendEmptyMessage(MSG_SHOW_HEADS_UP); - } - - @Override - public void scheduleHeadsUpClose() { - mHandler.removeMessages(MSG_HIDE_HEADS_UP); - mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP); - } - - @Override - public void scheduleHeadsUpEscalation() { - mHandler.removeMessages(MSG_ESCALATE_HEADS_UP); - mHandler.sendEmptyMessage(MSG_ESCALATE_HEADS_UP); } @Override @@ -1403,23 +1151,19 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, @Override public void removeNotification(String key, RankingMap ranking) { - if (ENABLE_HEADS_UP && mHeadsUpNotificationView.getEntry() != null - && key.equals(mHeadsUpNotificationView.getEntry().notification.getKey())) { - mHeadsUpNotificationView.clear(); + boolean deferRemoval = false; + if (mHeadsUpManager.isHeadsUp(key)) { + deferRemoval = !mHeadsUpManager.removeNotification(key); + } + if (deferRemoval) { + mLatestRankingMap = ranking; + mHeadsUpEntriesToRemoveOnSwitch.add(mHeadsUpManager.getEntry(key)); + return; } - StatusBarNotification old = removeNotificationViews(key, ranking); if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old); if (old != null) { - // Cancel the ticker if it's still running - if (mTickerEnabled) { - mTicker.removeEntry(old); - } - - // Recalculate the position of the sliding windows and the titles. - updateExpandedViewPos(EXPANDED_LEAVE_ALONE); - if (CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications() && !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()) { if (mState == StatusBarState.SHADE) { @@ -1437,7 +1181,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, if (mNavigationBarView != null) { mNavigationBarView.setLayoutDirection(layoutDirection); } - refreshAllStatusBarIcons(); } private void updateShowSearchHoldoff() { @@ -1483,10 +1226,23 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, ent.row.setShowingLegacyBackground(true); } } - toShow.add(ent.row); + if (mGroupManager.isChildInGroupWithSummary(ent.row.getStatusBarNotification())) { + ExpandableNotificationRow summary = mGroupManager.getGroupSummary( + ent.row.getStatusBarNotification()); + List<ExpandableNotificationRow> orderedChildren = + mTmpChildOrderMap.get(summary); + if (orderedChildren == null) { + orderedChildren = new ArrayList<>(); + mTmpChildOrderMap.put(summary, orderedChildren); + } + orderedChildren.add(ent.row); + } else { + toShow.add(ent.row); + } + } - ArrayList<View> toRemove = new ArrayList<View>(); + ArrayList<View> toRemove = new ArrayList<>(); for (int i=0; i< mStackScroller.getChildCount(); i++) { View child = mStackScroller.getChildAt(i); if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) { @@ -1515,29 +1271,87 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, continue; } - if (child == toShow.get(j)) { - // Everything is well, advance both lists. - j++; - continue; + ExpandableNotificationRow targetChild = toShow.get(j); + if (child != targetChild) { + // Oops, wrong notification at this position. Put the right one + // here and advance both lists. + mStackScroller.changeViewPosition(targetChild, i); } - - // Oops, wrong notification at this position. Put the right one - // here and advance both lists. - mStackScroller.changeViewPosition(toShow.get(j), i); j++; + } + + // lets handle the child notifications now + updateNotificationShadeForChildren(); + + // clear the map again for the next usage + mTmpChildOrderMap.clear(); + updateRowStates(); updateSpeedbump(); updateClearAll(); updateEmptyShadeView(); - // Disable QS if device not provisioned. - // If the user switcher is simple then disable QS during setup because - // the user intends to use the lock screen user switcher, QS in not needed. + updateQsExpansionEnabled(); + mShadeUpdates.check(); + } + + /** + * Disable QS if device not provisioned. + * If the user switcher is simple then disable QS during setup because + * the user intends to use the lock screen user switcher, QS in not needed. + */ + private void updateQsExpansionEnabled() { mNotificationPanel.setQsExpansionEnabled(isDeviceProvisioned() && (mUserSetup || mUserSwitcherController == null - || !mUserSwitcherController.isSimpleUserSwitcher())); - mShadeUpdates.check(); + || !mUserSwitcherController.isSimpleUserSwitcher()) + && ((mDisabled2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) == 0)); + } + + private void updateNotificationShadeForChildren() { + ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>(); + boolean orderChanged = false; + for (int i = 0; i < mStackScroller.getChildCount(); i++) { + View view = mStackScroller.getChildAt(i); + if (!(view instanceof ExpandableNotificationRow)) { + // We don't care about non-notification views. + continue; + } + + ExpandableNotificationRow parent = (ExpandableNotificationRow) view; + List<ExpandableNotificationRow> children = parent.getNotificationChildren(); + List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent); + + // lets first remove all undesired children + if (children != null) { + toRemove.clear(); + for (ExpandableNotificationRow childRow : children) { + if (orderedChildren == null || !orderedChildren.contains(childRow)) { + toRemove.add(childRow); + } + } + for (ExpandableNotificationRow remove : toRemove) { + parent.removeChildNotification(remove); + mStackScroller.notifyGroupChildRemoved(remove); + } + } + + // We now add all the children which are not in there already + for (int childIndex = 0; orderedChildren != null && childIndex < orderedChildren.size(); + childIndex++) { + ExpandableNotificationRow childView = orderedChildren.get(childIndex); + if (children == null || !children.contains(childView)) { + parent.addChildNotification(childView, childIndex); + mStackScroller.notifyGroupChildAdded(childView); + } + } + + // Finally after removing and adding has been beformed we can apply the order. + orderChanged |= parent.applyChildOrder(orderedChildren); + } + if (orderChanged) { + mStackScroller.generateChildOrderChangedEvent(); + } } private boolean packageHasVisibilityOverride(String key) { @@ -1566,6 +1380,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 +1394,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 +1412,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 +1444,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } findAndUpdateMediaNotifications(); - - updateCarrierLabelVisibility(false); } public void findAndUpdateMediaNotifications() { @@ -1973,14 +1687,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } } - public void showClock(boolean show) { - if (mStatusBarView == null) return; - View clock = mStatusBarView.findViewById(R.id.clock); - if (clock != null) { - clock.setVisibility(show ? View.VISIBLE : View.GONE); - } - } - private int adjustDisableFlags(int state) { if (!mLaunchTransitionFadingAway && (mExpandedVisible || mBouncerShowing || mWaitingForKeyguardExit)) { @@ -1993,143 +1699,99 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, /** * State is one or more of the DISABLE constants from StatusBarManager. */ - public void disable(int state, boolean animate) { - mDisabledUnmodified = state; - state = adjustDisableFlags(state); - final int old = mDisabled; - final int diff = state ^ old; - mDisabled = state; + public void disable(int state1, int state2, boolean animate) { + mDisabledUnmodified1 = state1; + mDisabledUnmodified2 = state2; + state1 = adjustDisableFlags(state1); + final int old1 = mDisabled1; + final int diff1 = state1 ^ old1; + mDisabled1 = state1; + + final int old2 = mDisabled2; + final int diff2 = state2 ^ old2; + mDisabled2 = state2; if (DEBUG) { - Log.d(TAG, String.format("disable: 0x%08x -> 0x%08x (diff: 0x%08x)", - old, state, diff)); + Log.d(TAG, String.format("disable1: 0x%08x -> 0x%08x (diff1: 0x%08x)", + old1, state1, diff1)); + Log.d(TAG, String.format("disable2: 0x%08x -> 0x%08x (diff2: 0x%08x)", + old2, state2, diff2)); } StringBuilder flagdbg = new StringBuilder(); flagdbg.append("disable: < "); - flagdbg.append(((state & StatusBarManager.DISABLE_EXPAND) != 0) ? "EXPAND" : "expand"); - flagdbg.append(((diff & StatusBarManager.DISABLE_EXPAND) != 0) ? "* " : " "); - flagdbg.append(((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) ? "ICONS" : "icons"); - flagdbg.append(((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) ? "* " : " "); - flagdbg.append(((state & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) ? "ALERTS" : "alerts"); - flagdbg.append(((diff & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) ? "* " : " "); - flagdbg.append(((state & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) ? "SYSTEM_INFO" : "system_info"); - flagdbg.append(((diff & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) ? "* " : " "); - flagdbg.append(((state & StatusBarManager.DISABLE_BACK) != 0) ? "BACK" : "back"); - flagdbg.append(((diff & StatusBarManager.DISABLE_BACK) != 0) ? "* " : " "); - flagdbg.append(((state & StatusBarManager.DISABLE_HOME) != 0) ? "HOME" : "home"); - flagdbg.append(((diff & StatusBarManager.DISABLE_HOME) != 0) ? "* " : " "); - flagdbg.append(((state & StatusBarManager.DISABLE_RECENT) != 0) ? "RECENT" : "recent"); - flagdbg.append(((diff & StatusBarManager.DISABLE_RECENT) != 0) ? "* " : " "); - flagdbg.append(((state & StatusBarManager.DISABLE_CLOCK) != 0) ? "CLOCK" : "clock"); - flagdbg.append(((diff & StatusBarManager.DISABLE_CLOCK) != 0) ? "* " : " "); - flagdbg.append(((state & StatusBarManager.DISABLE_SEARCH) != 0) ? "SEARCH" : "search"); - flagdbg.append(((diff & StatusBarManager.DISABLE_SEARCH) != 0) ? "* " : " "); + flagdbg.append(((state1 & StatusBarManager.DISABLE_EXPAND) != 0) ? "EXPAND" : "expand"); + flagdbg.append(((diff1 & StatusBarManager.DISABLE_EXPAND) != 0) ? "* " : " "); + flagdbg.append(((state1 & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) ? "ICONS" : "icons"); + flagdbg.append(((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) ? "* " : " "); + flagdbg.append(((state1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) ? "ALERTS" : "alerts"); + flagdbg.append(((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) ? "* " : " "); + flagdbg.append(((state1 & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) ? "SYSTEM_INFO" : "system_info"); + flagdbg.append(((diff1 & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) ? "* " : " "); + flagdbg.append(((state1 & StatusBarManager.DISABLE_BACK) != 0) ? "BACK" : "back"); + flagdbg.append(((diff1 & StatusBarManager.DISABLE_BACK) != 0) ? "* " : " "); + flagdbg.append(((state1 & StatusBarManager.DISABLE_HOME) != 0) ? "HOME" : "home"); + flagdbg.append(((diff1 & StatusBarManager.DISABLE_HOME) != 0) ? "* " : " "); + flagdbg.append(((state1 & StatusBarManager.DISABLE_RECENT) != 0) ? "RECENT" : "recent"); + flagdbg.append(((diff1 & StatusBarManager.DISABLE_RECENT) != 0) ? "* " : " "); + flagdbg.append(((state1 & StatusBarManager.DISABLE_CLOCK) != 0) ? "CLOCK" : "clock"); + flagdbg.append(((diff1 & StatusBarManager.DISABLE_CLOCK) != 0) ? "* " : " "); + flagdbg.append(((state1 & StatusBarManager.DISABLE_SEARCH) != 0) ? "SEARCH" : "search"); + flagdbg.append(((diff1 & StatusBarManager.DISABLE_SEARCH) != 0) ? "* " : " "); + flagdbg.append(((state2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) != 0) ? "QUICK_SETTINGS" + : "quick_settings"); + flagdbg.append(((diff2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) != 0) ? "* " : " "); flagdbg.append(">"); Log.d(TAG, flagdbg.toString()); - if ((diff & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) { - mSystemIconArea.animate().cancel(); - if ((state & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) { - animateStatusBarHide(mSystemIconArea, animate); + if ((diff1 & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) { + if ((state1 & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) { + mIconController.hideSystemIconArea(animate); } else { - animateStatusBarShow(mSystemIconArea, animate); + mIconController.showSystemIconArea(animate); } } - if ((diff & StatusBarManager.DISABLE_CLOCK) != 0) { - boolean show = (state & StatusBarManager.DISABLE_CLOCK) == 0; - showClock(show); + if ((diff1 & StatusBarManager.DISABLE_CLOCK) != 0) { + boolean visible = (state1 & StatusBarManager.DISABLE_CLOCK) == 0; + mIconController.setClockVisibility(visible); } - if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) { - if ((state & StatusBarManager.DISABLE_EXPAND) != 0) { + if ((diff1 & StatusBarManager.DISABLE_EXPAND) != 0) { + if ((state1 & StatusBarManager.DISABLE_EXPAND) != 0) { animateCollapsePanels(); } } - if ((diff & (StatusBarManager.DISABLE_HOME + if ((diff1 & (StatusBarManager.DISABLE_HOME | StatusBarManager.DISABLE_RECENT | StatusBarManager.DISABLE_BACK | StatusBarManager.DISABLE_SEARCH)) != 0) { // the nav bar will take care of these - if (mNavigationBarView != null) mNavigationBarView.setDisabledFlags(state); + if (mNavigationBarView != null) mNavigationBarView.setDisabledFlags(state1); - if ((state & StatusBarManager.DISABLE_RECENT) != 0) { + if ((state1 & StatusBarManager.DISABLE_RECENT) != 0) { // close recents if it's visible mHandler.removeMessages(MSG_HIDE_RECENT_APPS); mHandler.sendEmptyMessage(MSG_HIDE_RECENT_APPS); } } - if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { - if ((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { - if (mTicking) { - haltTicker(); - } - animateStatusBarHide(mNotificationIconArea, animate); + if ((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { + if ((state1 & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { + mIconController.hideNotificationIconArea(animate); } else { - animateStatusBarShow(mNotificationIconArea, animate); + mIconController.showNotificationIconArea(animate); } } - if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) { + if ((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) { mDisableNotificationAlerts = - (state & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0; + (state1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0; mHeadsUpObserver.onChange(true); } - } - /** - * Animates {@code v}, a view that is part of the status bar, out. - */ - private void animateStatusBarHide(final View v, boolean animate) { - v.animate().cancel(); - if (!animate) { - v.setAlpha(0f); - v.setVisibility(View.INVISIBLE); - return; - } - v.animate() - .alpha(0f) - .setDuration(160) - .setStartDelay(0) - .setInterpolator(ALPHA_OUT) - .withEndAction(new Runnable() { - @Override - public void run() { - v.setVisibility(View.INVISIBLE); - } - }); - } - - /** - * Animates {@code v}, a view that is part of the status bar, in. - */ - private void animateStatusBarShow(View v, boolean animate) { - v.animate().cancel(); - v.setVisibility(View.VISIBLE); - if (!animate) { - v.setAlpha(1f); - return; - } - v.animate() - .alpha(1f) - .setDuration(320) - .setInterpolator(ALPHA_IN) - .setStartDelay(50) - - // We need to clean up any pending end action from animateStatusBarHide if we call - // both hide and show in the same frame before the animation actually gets started. - // cancel() doesn't really remove the end action. - .withEndAction(null); - - // Synchronize the motion with the Keyguard fading if necessary. - if (mKeyguardFadingAway) { - v.animate() - .setDuration(mKeyguardFadingAwayDuration) - .setInterpolator(mLinearOutSlowIn) - .setStartDelay(mKeyguardFadingAwayDelay) - .start(); + if ((diff2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) != 0) { + updateQsExpansionEnabled(); } } @@ -2143,10 +1805,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); } @@ -2164,9 +1822,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } public boolean isFalsingThresholdNeeded() { - boolean onKeyguard = getBarState() == StatusBarState.KEYGUARD; - boolean isCurrentlyInsecure = mUnlockMethodCache.isCurrentlyInsecure(); - return onKeyguard && (isCurrentlyInsecure || mDozing || mScreenOnComingFromTouch); + return getBarState() == StatusBarState.KEYGUARD; } public boolean isDozing() { @@ -2194,6 +1850,80 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, logStateToEventlog(); } + @Override + public void onHeadsUpPinnedModeChanged(boolean inPinnedMode) { + if (inPinnedMode) { + mStatusBarWindowManager.setHeadsUpShowing(true); + } else { + Runnable endRunnable = new Runnable() { + @Override + public void run() { + if (!mHeadsUpManager.hasPinnedHeadsUp()) { + mStatusBarWindowManager.setHeadsUpShowing(false); + } + } + }; + if (!mNotificationPanel.isFullyCollapsed()) { + endRunnable.run(); + } else { + mStackScroller.runAfterAnimationFinished(endRunnable); + } + } + } + + @Override + public void onHeadsUpPinned(ExpandableNotificationRow headsUp) { + dismissVolumeDialog(); + } + + @Override + public void onHeadsUpUnPinned(ExpandableNotificationRow headsUp) { + } + + @Override + public void onHeadsUpStateChanged(Entry entry, boolean isHeadsUp) { + if (!isHeadsUp && mHeadsUpEntriesToRemoveOnSwitch.contains(entry)) { + removeNotification(entry.key, mLatestRankingMap); + mHeadsUpEntriesToRemoveOnSwitch.remove(entry); + if (mHeadsUpEntriesToRemoveOnSwitch.isEmpty()) { + mLatestRankingMap = null; + } + } else { + updateNotificationRanking(null); + } + + } + + protected void updateHeadsUp(String key, Entry entry, boolean shouldInterrupt, + boolean alertAgain) { + final boolean wasHeadsUp = isHeadsUp(key); + if (wasHeadsUp) { + if (!shouldInterrupt) { + // We don't want this to be interrupting anymore, lets remove it + mHeadsUpManager.removeNotification(key); + } else { + mHeadsUpManager.updateNotification(entry, alertAgain); + } + } else if (shouldInterrupt && alertAgain) { + // This notification was updated to be a heads-up, show it! + mHeadsUpManager.showNotification(entry); + } + } + + protected void setHeadsUpUser(int newUserId) { + if (mHeadsUpManager != null) { + mHeadsUpManager.setUser(newUserId); + } + } + + public boolean isHeadsUp(String key) { + return mHeadsUpManager.isHeadsUp(key); + } + + protected boolean isSnoozedPackage(StatusBarNotification sbn) { + return mHeadsUpManager.isSnoozed(sbn.getPackageName()); + } + /** * All changes to the status bar and notifications funnel through here and are batched. */ @@ -2210,21 +1940,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, case MSG_CLOSE_PANELS: animateCollapsePanels(); break; - case MSG_SHOW_HEADS_UP: - setHeadsUpVisibility(true); - break; - case MSG_DECAY_HEADS_UP: - mHeadsUpNotificationView.release(); - setHeadsUpVisibility(false); - break; - case MSG_HIDE_HEADS_UP: - mHeadsUpNotificationView.release(); - setHeadsUpVisibility(false); - break; - case MSG_ESCALATE_HEADS_UP: - escalateHeadsUp(); - setHeadsUpVisibility(false); - break; case MSG_LAUNCH_TRANSITION_TIMEOUT: onLaunchTransitionTimeout(); break; @@ -2232,15 +1947,16 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } } - /** if the interrupting notification had a fullscreen intent, fire it now. */ - private void escalateHeadsUp() { - if (mHeadsUpNotificationView.getEntry() != null) { - final StatusBarNotification sbn = mHeadsUpNotificationView.getEntry().notification; - mHeadsUpNotificationView.release(); + @Override + public void maybeEscalateHeadsUp() { + TreeSet<HeadsUpManager.HeadsUpEntry> entries = mHeadsUpManager.getSortedEntries(); + for (HeadsUpManager.HeadsUpEntry entry : entries) { + final StatusBarNotification sbn = entry.entry.notification; final Notification notification = sbn.getNotification(); if (notification.fullScreenIntent != null) { - if (DEBUG) + if (DEBUG) { Log.d(TAG, "converting a heads up to fullScreen"); + } try { EventLog.writeEvent(EventLogTags.SYSUI_HEADS_UP_ESCALATION, sbn.getKey()); @@ -2249,18 +1965,11 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } } } + mHeadsUpManager.releaseAllImmediately(); } - View.OnFocusChangeListener mFocusChangeListener = new View.OnFocusChangeListener() { - public void onFocusChange(View v, boolean hasFocus) { - // Because 'v' is a ViewGroup, all its children will be (un)selected - // too, which allows marqueeing to work. - v.setSelected(hasFocus); - } - }; - boolean panelsEnabled() { - return (mDisabled & StatusBarManager.DISABLE_EXPAND) == 0; + return (mDisabled1 & StatusBarManager.DISABLE_EXPAND) == 0; } void makeExpandedVisible(boolean force) { @@ -2273,10 +1982,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); @@ -2284,7 +1989,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, visibilityChanged(true); mWaitingForKeyguardExit = false; - disable(mDisabledUnmodified, !force /* animate */); + disable(mDisabledUnmodified1, mDisabledUnmodified2, !force /* animate */); setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true); } @@ -2304,10 +2009,20 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } public void animateCollapsePanels(int flags) { - animateCollapsePanels(flags, false /* force */); + animateCollapsePanels(flags, false /* force */, false /* delayed */, + 1.0f /* speedUpFactor */); } public void animateCollapsePanels(int flags, boolean force) { + animateCollapsePanels(flags, force, false /* delayed */, 1.0f /* speedUpFactor */); + } + + public void animateCollapsePanels(int flags, boolean force, boolean delayed) { + animateCollapsePanels(flags, force, delayed, 1.0f /* speedUpFactor */); + } + + public void animateCollapsePanels(int flags, boolean force, boolean delayed, + float speedUpFactor) { if (!force && (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED)) { runPostCollapseRunnables(); @@ -2326,17 +2041,12 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } } - if ((flags & CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL) == 0) { - mHandler.removeMessages(MSG_CLOSE_SEARCH_PANEL); - mHandler.sendEmptyMessage(MSG_CLOSE_SEARCH_PANEL); - } - if (mStatusBarWindow != null) { // release focus immediately to kick off focus change transition mStatusBarWindowManager.setStatusBarFocusable(false); mStatusBarWindow.cancelExpandHelper(); - mStatusBarView.collapseAllPanels(true); + mStatusBarView.collapseAllPanels(true /* animate */, delayed, speedUpFactor); } } @@ -2348,50 +2058,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mPostCollapseRunnables.clear(); } - public ViewPropertyAnimator setVisibilityWhenDone( - final ViewPropertyAnimator a, final View v, final int vis) { - a.setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - v.setVisibility(vis); - a.setListener(null); // oneshot - } - }); - return a; - } - - public Animator setVisibilityWhenDone( - final Animator a, final View v, final int vis) { - a.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - v.setVisibility(vis); - } - }); - return a; - } - - public Animator interpolator(TimeInterpolator ti, Animator a) { - a.setInterpolator(ti); - return a; - } - - public Animator startDelay(int d, Animator a) { - a.setStartDelay(d); - return a; - } - - public Animator start(Animator a) { - a.start(); - return a; - } - - final TimeInterpolator mAccelerateInterpolator = new AccelerateInterpolator(); - final TimeInterpolator mDecelerateInterpolator = new DecelerateInterpolator(); - final int FLIP_DURATION_OUT = 125; - final int FLIP_DURATION_IN = 225; - final int FLIP_DURATION = (FLIP_DURATION_IN + FLIP_DURATION_OUT); - Animator mScrollViewAnim, mClearButtonAnim; @Override @@ -2423,7 +2089,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, public void animateCollapseQuickSettings() { if (mState == StatusBarState.SHADE) { - mStatusBarView.collapseAllPanels(true); + mStatusBarView.collapseAllPanels(true, false /* delayed */, 1.0f /* speedUpFactor */); } } @@ -2436,14 +2102,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } // Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868) - mStatusBarView.collapseAllPanels(/*animate=*/ false); - - // reset things to their proper state - if (mScrollViewAnim != null) mScrollViewAnim.cancel(); - if (mClearButtonAnim != null) mClearButtonAnim.cancel(); - - mStackScroller.setVisibility(View.VISIBLE); - mNotificationPanel.setVisibility(View.GONE); + mStatusBarView.collapseAllPanels(/*animate=*/ false, false /* delayed*/, + 1.0f /* speedUpFactor */); mNotificationPanel.closeQs(); @@ -2462,7 +2122,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, runPostCollapseRunnables(); setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false); showBouncer(); - disable(mDisabledUnmodified, true /* animate */); + disable(mDisabledUnmodified1, mDisabledUnmodified2, true /* animate */); // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in // the bouncer appear animation. @@ -2475,20 +2135,21 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, if (DEBUG_GESTURES) { if (event.getActionMasked() != MotionEvent.ACTION_MOVE) { EventLog.writeEvent(EventLogTags.SYSUI_STATUSBAR_TOUCH, - event.getActionMasked(), (int) event.getX(), (int) event.getY(), mDisabled); + event.getActionMasked(), (int) event.getX(), (int) event.getY(), + mDisabled1, mDisabled2); } } if (SPEW) { - Log.d(TAG, "Touch: rawY=" + event.getRawY() + " event=" + event + " mDisabled=" - + mDisabled + " mTracking=" + mTracking); + Log.d(TAG, "Touch: rawY=" + event.getRawY() + " event=" + event + " mDisabled1=" + + mDisabled1 + " mDisabled2=" + mDisabled2 + " mTracking=" + mTracking); } else if (CHATTY) { if (event.getAction() != MotionEvent.ACTION_MOVE) { Log.d(TAG, String.format( - "panel: %s at (%f, %f) mDisabled=0x%08x", + "panel: %s at (%f, %f) mDisabled1=0x%08x mDisabled2=0x%08x", MotionEvent.actionToString(event.getAction()), - event.getRawX(), event.getRawY(), mDisabled)); + event.getRawX(), event.getRawY(), mDisabled1, mDisabled2)); } } @@ -2533,7 +2194,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mStatusBarWindowState = state; if (DEBUG_WINDOW_STATE) Log.d(TAG, "Status bar " + windowStateToString(state)); if (!showing && mState == StatusBarState.SHADE) { - mStatusBarView.collapseAllPanels(false); + mStatusBarView.collapseAllPanels(false /* animate */, false /* delayed */, + 1.0f /* speedUpFactor */); } } if (mNavigationBarView != null @@ -2587,9 +2249,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 +2293,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; @@ -2710,13 +2377,17 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } // manually dismiss the volume panel when interacting with the nav bar if (changing && interacting && barWindow == StatusBarManager.WINDOW_NAVIGATION_BAR) { - if (mVolumeComponent != null) { - mVolumeComponent.dismissNow(); - } + dismissVolumeDialog(); } checkBarModes(); } + private void dismissVolumeDialog() { + if (mVolumeComponent != null) { + mVolumeComponent.dismissNow(); + } + } + private void resumeSuspendedAutohide() { if (mAutohideSuspended) { scheduleAutohide(); @@ -2805,90 +2476,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 +2486,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, pw.println("Current Status Bar state:"); pw.println(" mExpandedVisible=" + mExpandedVisible + ", mTrackingPosition=" + mTrackingPosition); - pw.println(" mTickerEnabled=" + mTickerEnabled); - if (mTickerEnabled) { - pw.println(" mTicking=" + mTicking); - pw.println(" mTickerView: " + viewInfo(mTickerView)); - } pw.println(" mTracking=" + mTracking); pw.println(" mDisplayMetrics=" + mDisplayMetrics); pw.println(" mStackScroller: " + viewInfo(mStackScroller)); @@ -2922,8 +2504,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, pw.println(Settings.Global.zenModeToString(mZenMode)); pw.print(" mUseHeadsUp="); pw.println(mUseHeadsUp); - pw.print(" interrupting package: "); - pw.println(hunStateToString(mHeadsUpNotificationView.getEntry())); dumpBarTransitions(pw, "mStatusBarView", mStatusBarView.getBarTransitions()); if (mNavigationBarView != null) { pw.print(" mNavigationBarWindowState="); @@ -2972,12 +2552,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,9 +2595,14 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, if (mSecurityController != null) { mSecurityController.dump(fd, pw, args); } + if (mHeadsUpManager != null) { + mHeadsUpManager.dump(fd, pw, args); + } else { + pw.println(" mHeadsUpManager: null"); + } + pw.println("SharedPreferences:"); - for (Map.Entry<String, ?> entry : mContext.getSharedPreferences(mContext.getPackageName(), - Context.MODE_PRIVATE).getAll().entrySet()) { + for (Map.Entry<String, ?> entry : Prefs.getAll(mContext).entrySet()) { pw.print(" "); pw.print(entry.getKey()); pw.print("="); pw.println(entry.getValue()); } } @@ -3049,25 +2629,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); @@ -3110,8 +2671,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } }); if (dismissShade) { - animateCollapsePanels( - CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */); + animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */, + true /* delayed*/); } return true; } @@ -3135,7 +2696,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, else if (Intent.ACTION_SCREEN_OFF.equals(action)) { mScreenOn = false; notifyNavigationBarScreenOn(false); - notifyHeadsUpScreenOn(false); + notifyHeadsUpScreenOff(); finishBarAnimations(); resetUserExpandedStates(); } @@ -3195,11 +2756,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,21 +2785,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mUserSetupObserver.onChange(false); mContext.getContentResolver().registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE), true, - mUserSetupObserver, - mCurrentUserId); - } - - private void setHeadsUpVisibility(boolean vis) { - if (!ENABLE_HEADS_UP) return; - if (DEBUG) Log.v(TAG, (vis ? "showing" : "hiding") + " heads up window"); - EventLog.writeEvent(EventLogTags.SYSUI_HEADS_UP_STATUS, - vis ? mHeadsUpNotificationView.getKey() : "", - vis ? 1 : 0); - mHeadsUpNotificationView.setVisibility(vis ? View.VISIBLE : View.GONE); - } - - public void onHeadsUpDismissed() { - mHeadsUpNotificationView.dismiss(); + mUserSetupObserver, mCurrentUserId); } /** @@ -3256,61 +2802,21 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } loadDimens(); - mLinearOutSlowIn = AnimationUtils.loadInterpolator( - mContext, android.R.interpolator.linear_out_slow_in); if (mNotificationPanel != null) { mNotificationPanel.updateResources(); } - if (mHeadsUpNotificationView != null) { - mHeadsUpNotificationView.updateResources(); - } if (mBrightnessMirrorController != null) { mBrightnessMirrorController.updateResources(); } } - private void updateClockSize() { - if (mStatusBarView == null) return; - TextView clock = (TextView) mStatusBarView.findViewById(R.id.clock); - if (clock != null) { - FontSizeUtils.updateFontSize(clock, R.dimen.status_bar_clock_size); - } - } protected void loadDimens() { final Resources res = mContext.getResources(); mNaturalBarHeight = res.getDimensionPixelSize( com.android.internal.R.dimen.status_bar_height); - int newIconSize = res.getDimensionPixelSize( - com.android.internal.R.dimen.status_bar_icon_size); - int newIconHPadding = res.getDimensionPixelSize( - R.dimen.status_bar_icon_padding); - - if (newIconHPadding != mIconHPadding || newIconSize != mIconSize) { -// Log.d(TAG, "size=" + newIconSize + " padding=" + newIconHPadding); - mIconHPadding = newIconHPadding; - mIconSize = newIconSize; - //reloadAllNotificationIcons(); // reload the tray - } - - mEdgeBorder = res.getDimensionPixelSize(R.dimen.status_bar_edge_ignore); - - mNotificationPanelGravity = res.getInteger(R.integer.notification_panel_layout_gravity); - if (mNotificationPanelGravity <= 0) { - mNotificationPanelGravity = Gravity.START | Gravity.TOP; - } - - mCarrierLabelHeight = res.getDimensionPixelSize(R.dimen.carrier_label_height); - mStatusBarHeaderHeight = res.getDimensionPixelSize(R.dimen.status_bar_header_height); - - mNotificationPanelMinHeightFrac = res.getFraction(R.dimen.notification_panel_min_height_frac, 1, 1); - if (mNotificationPanelMinHeightFrac < 0f || mNotificationPanelMinHeightFrac > 1f) { - mNotificationPanelMinHeightFrac = 0f; - } - - mHeadsUpNotificationDecay = res.getInteger(R.integer.heads_up_notification_decay); mRowMinHeight = res.getDimensionPixelSize(R.dimen.notification_min_height); mRowMaxHeight = res.getDimensionPixelSize(R.dimen.notification_max_height); @@ -3367,6 +2873,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } catch (RemoteException e) { // Ignore. } + setNotificationsShown(newlyVisibleAr); } // State logging @@ -3443,17 +2950,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, }; @Override - protected void haltTicker() { - if (mTickerEnabled) { - mTicker.halt(); - } - } - - @Override - protected boolean shouldDisableNavbarGestures() { + public boolean shouldDisableNavbarGestures() { return !isDeviceProvisioned() || mExpandedVisible - || (mDisabled & StatusBarManager.DISABLE_SEARCH) != 0; + || (mDisabled1 & StatusBarManager.DISABLE_SEARCH) != 0; } public void postStartSettingsActivity(final Intent intent, int delay) { @@ -3486,7 +2986,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } @Override - public void setColorFilter(ColorFilter cf) { + public void setColorFilter(ColorFilter colorFilter) { } @Override @@ -3519,11 +3019,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 +3052,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 +3123,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 +3173,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mLaunchTransitionFadingAway = false; } }); + mIconController.appTransitionStarting(SystemClock.uptimeMillis(), + StatusBarIconController.DEFAULT_TINT_ANIMATION_DURATION); } }; if (mNotificationPanel.isLaunchTransitionRunning()) { @@ -3742,17 +3242,32 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } /** + * Notifies the status bar that Keyguard is going away very soon. + */ + public void keyguardGoingAway() { + + // Treat Keyguard exit animation as an app transition to achieve nice transition for status + // bar. + mIconController.appTransitionPending(); + } + + /** * Notifies the status bar the Keyguard is fading away with the specified timings. * - * @param delay the animation delay in miliseconds + * @param startTime the start time of the animations in uptime millis + * @param delay the precalculated animation delay in miliseconds * @param fadeoutDuration the duration of the exit animation, in milliseconds */ - public void setKeyguardFadingAway(long delay, long fadeoutDuration) { + public void setKeyguardFadingAway(long startTime, long delay, long fadeoutDuration) { mKeyguardFadingAway = true; mKeyguardFadingAwayDelay = delay; mKeyguardFadingAwayDuration = fadeoutDuration; mWaitingForKeyguardExit = false; - disable(mDisabledUnmodified, true /* animate */); + mIconController.appTransitionStarting( + startTime + fadeoutDuration + - StatusBarIconController.DEFAULT_TINT_ANIMATION_DURATION, + StatusBarIconController.DEFAULT_TINT_ANIMATION_DURATION); + disable(mDisabledUnmodified1, mDisabledUnmodified2, true /* animate */); } public boolean isKeyguardFadingAway() { @@ -3766,9 +3281,14 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mKeyguardFadingAway = false; } + public void stopWaitingForKeyguardExit() { + mWaitingForKeyguardExit = false; + } + private void updatePublicMode() { - setLockscreenPublicMode(mStatusBarKeyguardViewManager.isShowing() - && mStatusBarKeyguardViewManager.isSecure(mCurrentUserId)); + setLockscreenPublicMode( + mStatusBarKeyguardViewManager.isShowing() && mStatusBarKeyguardViewManager + .isSecure(mCurrentUserId)); } private void updateKeyguardState(boolean goingToFullShade, boolean fromShadeLocked) { @@ -3783,8 +3303,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) { mScrimController.setKeyguardShowing(true); + mIconPolicy.setKeyguardShowing(true); } else { mScrimController.setKeyguardShowing(false); + mIconPolicy.setKeyguardShowing(false); } mNotificationPanel.setBarState(mState, mKeyguardFadingAway, goingToFullShade); updateDozingState(); @@ -3792,7 +3314,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, updateStackScrollerState(goingToFullShade); updateNotifications(); checkBarModes(); - updateCarrierLabelVisibility(false); updateMediaMetaData(false); mKeyguardMonitor.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(), mStatusBarKeyguardViewManager.isSecure()); @@ -3913,6 +3434,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } } mState = state; + mGroupManager.setStatusBarState(state); mStatusBarWindowManager.setStatusBarState(state); } @@ -4046,13 +3568,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } } - /** - * @return a ViewGroup that spans the entire panel which contains the quick settings - */ - public ViewGroup getQuickSettingsOverlayParent() { - return mNotificationPanel; - } - public long getKeyguardFadingAwayDelay() { return mKeyguardFadingAwayDelay; } @@ -4061,18 +3576,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, return mKeyguardFadingAwayDuration; } - public LinearLayout getSystemIcons() { - return mSystemIcons; - } - - public LinearLayout getSystemIconArea() { - return mSystemIconArea; - } - @Override public void setBouncerShowing(boolean bouncerShowing) { super.setBouncerShowing(bouncerShowing); - disable(mDisabledUnmodified, true /* animate */); + disable(mDisabledUnmodified1, mDisabledUnmodified2, true /* animate */); } public void onScreenTurnedOff() { @@ -4116,7 +3623,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, if ((time - mLastLockToAppLongPress) < LOCK_TO_APP_GESTURE_TOLERENCE) { activityManager.stopLockTaskModeOnCurrent(); // When exiting refresh disabled flags. - mNavigationBarView.setDisabledFlags(mDisabled, true); + mNavigationBarView.setDisabledFlags(mDisabled1, true); } else if ((v.getId() == R.id.back) && !mNavigationBarView.getRecentsButton().isPressed()) { // If we aren't pressing recents right now then they presses @@ -4133,7 +3640,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, // should stop lock task. activityManager.stopLockTaskModeOnCurrent(); // When exiting refresh disabled flags. - mNavigationBarView.setDisabledFlags(mDisabled, true); + mNavigationBarView.setDisabledFlags(mDisabled1, true); } } if (sendBackLongPress) { @@ -4211,6 +3718,34 @@ 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); + } + if (mIconPolicy != null) { + mIconPolicy.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..0e8e844 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java @@ -16,16 +16,22 @@ package com.android.systemui.statusbar.phone; +import android.app.ActivityManagerNative; import android.app.AlarmManager; +import android.app.IUserSwitchObserver; import android.app.StatusBarManager; import android.bluetooth.BluetoothAdapter; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.UserInfo; import android.media.AudioManager; import android.os.Handler; +import android.os.IRemoteCallback; +import android.os.RemoteException; import android.os.UserHandle; +import android.os.UserManager; import android.provider.Settings.Global; import android.telecom.TelecomManager; import android.util.Log; @@ -33,6 +39,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,17 +53,14 @@ public class PhoneStatusBarPolicy { private static final String TAG = "PhoneStatusBarPolicy"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - private static final boolean SHOW_SYNC_ICON = false; - - private static final String SLOT_SYNC_ACTIVE = "sync_active"; private static final String SLOT_CAST = "cast"; private static final String SLOT_HOTSPOT = "hotspot"; private static final String SLOT_BLUETOOTH = "bluetooth"; private static final String SLOT_TTY = "tty"; private static final String SLOT_ZEN = "zen"; private static final String SLOT_VOLUME = "volume"; - private static final String SLOT_CDMA_ERI = "cdma_eri"; private static final String SLOT_ALARM_CLOCK = "alarm_clock"; + private static final String SLOT_MANAGED_PROFILE = "managed_profile"; private final Context mContext; private final StatusBarManager mService; @@ -75,6 +79,10 @@ public class PhoneStatusBarPolicy { private boolean mBluetoothEnabled = false; + private boolean mManagedProfileFocused = false; + private boolean mManagedProfileIconVisible = true; + + private boolean mKeyguardVisible = true; private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override @@ -83,9 +91,6 @@ public class PhoneStatusBarPolicy { if (action.equals(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)) { updateAlarm(); } - else if (action.equals(Intent.ACTION_SYNC_STATE_CHANGED)) { - updateSyncState(intent); - } else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED) || action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) { updateBluetooth(); @@ -100,9 +105,6 @@ public class PhoneStatusBarPolicy { else if (action.equals(TelecomManager.ACTION_CURRENT_TTY_MODE_CHANGED)) { updateTTY(intent); } - else if (action.equals(Intent.ACTION_USER_SWITCHED)) { - updateAlarm(); - } } }; @@ -115,24 +117,25 @@ 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); filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED); filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED); filter.addAction(TelecomManager.ACTION_CURRENT_TTY_MODE_CHANGED); - filter.addAction(Intent.ACTION_USER_SWITCHED); mContext.registerReceiver(mIntentReceiver, filter, null, mHandler); + // listen for user / profile change. + try { + ActivityManagerNative.getDefault().registerUserSwitchObserver(mUserSwitchListener); + } catch (RemoteException e) { + // Ignore + } + // TTY status mService.setIcon(SLOT_TTY, R.drawable.stat_sys_tty_mode, 0, null); mService.setIconVisibility(SLOT_TTY, false); - // Cdma Roaming Indicator, ERI - mService.setIcon(SLOT_CDMA_ERI, R.drawable.stat_sys_roaming_cdma_0, 0, null); - mService.setIconVisibility(SLOT_CDMA_ERI, false); - // bluetooth status updateBluetooth(); @@ -140,11 +143,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); @@ -163,6 +161,10 @@ public class PhoneStatusBarPolicy { mService.setIcon(SLOT_HOTSPOT, R.drawable.stat_sys_hotspot, 0, null); mService.setIconVisibility(SLOT_HOTSPOT, mHotspot.isHotspotEnabled()); mHotspot.addCallback(mHotspotCallback); + + // managed profile + mService.setIcon(SLOT_MANAGED_PROFILE, R.drawable.stat_sys_managed_profile_status, 0, null); + mService.setIconVisibility(SLOT_MANAGED_PROFILE, false); } public void setZenMode(int zen) { @@ -172,16 +174,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,17 +217,26 @@ 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); + zenDescription = mContext.getString(R.string.interruption_level_none); } else if (mZen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) { zenVisible = true; zenIconId = R.drawable.stat_sys_zen_important; - zenDescription = mContext.getString(R.string.zen_important_interruptions); + zenDescription = mContext.getString(R.string.interruption_level_priority); } - if (mZen != Global.ZEN_MODE_NO_INTERRUPTIONS && + if (DndTile.isVisible(mContext) && !DndTile.isCombinedIcon(mContext) + && audioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_SILENT) { + volumeVisible = true; + volumeIconId = R.drawable.stat_sys_ringer_silent; + volumeDescription = mContext.getString(R.string.accessibility_ringer_silent); + } else if (mZen != Global.ZEN_MODE_NO_INTERRUPTIONS && mZen != Global.ZEN_MODE_ALARMS && audioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_VIBRATE) { volumeVisible = true; volumeIconId = R.drawable.stat_sys_ringer_vibrate; @@ -311,6 +316,53 @@ public class PhoneStatusBarPolicy { mService.setIconVisibility(SLOT_CAST, isCasting); } + private void profileChanged(int userId) { + UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + UserInfo user = null; + if (userId == UserHandle.USER_CURRENT) { + try { + user = ActivityManagerNative.getDefault().getCurrentUser(); + } catch (RemoteException e) { + // Ignore + } + } else { + user = userManager.getUserInfo(userId); + } + + mManagedProfileFocused = user != null && user.isManagedProfile(); + if (DEBUG) Log.v(TAG, "profileChanged: mManagedProfileFocused: " + mManagedProfileFocused); + // Actually update the icon later when transition starts. + } + + private void updateManagedProfile() { + if (DEBUG) Log.v(TAG, "updateManagedProfile: mManagedProfileFocused: " + + mManagedProfileFocused + + " mKeyguardVisible: " + mKeyguardVisible); + boolean showIcon = mManagedProfileFocused && !mKeyguardVisible; + if (mManagedProfileIconVisible != showIcon) { + mService.setIconVisibility(SLOT_MANAGED_PROFILE, showIcon); + mManagedProfileIconVisible = showIcon; + } + } + + private final IUserSwitchObserver.Stub mUserSwitchListener = + new IUserSwitchObserver.Stub() { + @Override + public void onUserSwitching(int newUserId, IRemoteCallback reply) { + } + + @Override + public void onUserSwitchComplete(int newUserId) throws RemoteException { + updateAlarm(); + profileChanged(newUserId); + } + + @Override + public void onForegroundProfileSwitch(int newProfileId) { + profileChanged(newProfileId); + } + }; + private final HotspotController.Callback mHotspotCallback = new HotspotController.Callback() { @Override public void onHotspotChanged(boolean enabled) { @@ -324,4 +376,13 @@ public class PhoneStatusBarPolicy { updateCast(); } }; + + public void appTransitionStarting(long startTime, long duration) { + updateManagedProfile(); + } + + public void setKeyguardShowing(boolean visible) { + mKeyguardVisible = visible; + updateManagedProfile(); + } } 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/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 0e8a794..e6edbea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.phone; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Color; @@ -29,13 +30,18 @@ import android.view.animation.Interpolator; import com.android.systemui.R; import com.android.systemui.statusbar.BackDropView; +import com.android.systemui.statusbar.ExpandableNotificationRow; +import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.ScrimView; +import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.stack.StackStateAnimator; /** * Controls both the scrim behind the notifications and in front of the notifications (when a * security method gets shown). */ -public class ScrimController implements ViewTreeObserver.OnPreDrawListener { +public class ScrimController implements ViewTreeObserver.OnPreDrawListener, + HeadsUpManager.OnHeadsUpChangedListener { public static final long ANIMATION_DURATION = 220; private static final float SCRIM_BEHIND_ALPHA = 0.62f; @@ -43,10 +49,13 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener { private static final float SCRIM_BEHIND_ALPHA_UNLOCKING = 0.2f; private static final float SCRIM_IN_FRONT_ALPHA = 0.75f; private static final int TAG_KEY_ANIM = R.id.scrim; + private static final int TAG_HUN_START_ALPHA = R.id.hun_scrim_alpha_start; + private static final int TAG_HUN_END_ALPHA = R.id.hun_scrim_alpha_end; private final ScrimView mScrimBehind; private final ScrimView mScrimInFront; private final UnlockMethodCache mUnlockMethodCache; + private final View mHeadsUpScrim; private boolean mKeyguardShowing; private float mFraction; @@ -70,15 +79,22 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener { private float mDozeBehindAlpha; private float mCurrentInFrontAlpha; private float mCurrentBehindAlpha; + private float mCurrentHeadsUpAlpha = 1; + private int mPinnedHeadsUpCount; + private float mTopHeadsUpDragAmount; + private View mDraggedHeadsUpView; - public ScrimController(ScrimView scrimBehind, ScrimView scrimInFront, boolean scrimSrcEnabled) { + public ScrimController(ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim, + boolean scrimSrcEnabled) { mScrimBehind = scrimBehind; mScrimInFront = scrimInFront; + mHeadsUpScrim = headsUpScrim; final Context context = scrimBehind.getContext(); mUnlockMethodCache = UnlockMethodCache.getInstance(context); mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in); mScrimSrcEnabled = scrimSrcEnabled; + updateHeadsUpScrim(false); } public void setKeyguardShowing(boolean showing) { @@ -217,7 +233,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener { } } - private void setScrimColor(ScrimView scrim, float alpha) { + private void setScrimColor(View scrim, float alpha) { Object runningAnim = scrim.getTag(TAG_KEY_ANIM); if (runningAnim instanceof ValueAnimator) { ((ValueAnimator) runningAnim).cancel(); @@ -236,25 +252,34 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener { } private float getCurrentScrimAlpha(View scrim) { - return scrim == mScrimBehind ? mCurrentBehindAlpha : mCurrentInFrontAlpha; + return scrim == mScrimBehind ? mCurrentBehindAlpha + : scrim == mScrimInFront ? mCurrentInFrontAlpha + : mCurrentHeadsUpAlpha; } private void setCurrentScrimAlpha(View scrim, float alpha) { if (scrim == mScrimBehind) { mCurrentBehindAlpha = alpha; - } else { + } else if (scrim == mScrimInFront) { mCurrentInFrontAlpha = alpha; + } else { + alpha = Math.max(0.0f, Math.min(1.0f, alpha)); + mCurrentHeadsUpAlpha = alpha; } } - private void updateScrimColor(ScrimView scrim) { + private void updateScrimColor(View scrim) { float alpha1 = getCurrentScrimAlpha(scrim); - float alpha2 = getDozeAlpha(scrim); - float alpha = 1 - (1 - alpha1) * (1 - alpha2); - scrim.setScrimColor(Color.argb((int) (alpha * 255), 0, 0, 0)); + if (scrim instanceof ScrimView) { + float alpha2 = getDozeAlpha(scrim); + float alpha = 1 - (1 - alpha1) * (1 - alpha2); + ((ScrimView) scrim).setScrimColor(Color.argb((int) (alpha * 255), 0, 0, 0)); + } else { + scrim.setAlpha(alpha1); + } } - private void startScrimAnimation(final ScrimView scrim, float target) { + private void startScrimAnimation(final View scrim, float target) { float current = getCurrentScrimAlpha(scrim); ValueAnimator anim = ValueAnimator.ofFloat(current, target); anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @@ -320,4 +345,91 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener { boolean asSrc = mBackDropView.getVisibility() != View.VISIBLE && mScrimSrcEnabled; mScrimBehind.setDrawAsSrc(asSrc); } + + @Override + public void onHeadsUpPinnedModeChanged(boolean inPinnedMode) { + } + + @Override + public void onHeadsUpPinned(ExpandableNotificationRow headsUp) { + mPinnedHeadsUpCount++; + updateHeadsUpScrim(true); + } + + @Override + public void onHeadsUpUnPinned(ExpandableNotificationRow headsUp) { + mPinnedHeadsUpCount--; + if (headsUp == mDraggedHeadsUpView) { + mDraggedHeadsUpView = null; + mTopHeadsUpDragAmount = 0.0f; + } + updateHeadsUpScrim(true); + } + + @Override + public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) { + } + + private void updateHeadsUpScrim(boolean animate) { + float alpha = calculateHeadsUpAlpha(); + ValueAnimator previousAnimator = StackStateAnimator.getChildTag(mHeadsUpScrim, + TAG_KEY_ANIM); + float animEndValue = -1; + if (previousAnimator != null) { + if (animate || alpha == mCurrentHeadsUpAlpha) { + previousAnimator.cancel(); + } + animEndValue = StackStateAnimator.getChildTag(mHeadsUpScrim, TAG_HUN_START_ALPHA); + } + if (alpha != mCurrentHeadsUpAlpha && alpha != animEndValue) { + if (animate) { + startScrimAnimation(mHeadsUpScrim, alpha); + mHeadsUpScrim.setTag(TAG_HUN_START_ALPHA, mCurrentHeadsUpAlpha); + mHeadsUpScrim.setTag(TAG_HUN_END_ALPHA, alpha); + } else { + if (previousAnimator != null) { + float previousStartValue = StackStateAnimator.getChildTag(mHeadsUpScrim, + TAG_HUN_START_ALPHA); + float previousEndValue = StackStateAnimator.getChildTag(mHeadsUpScrim, + TAG_HUN_END_ALPHA); + // we need to increase all animation keyframes of the previous animator by the + // relative change to the end value + PropertyValuesHolder[] values = previousAnimator.getValues(); + float relativeDiff = alpha - previousEndValue; + float newStartValue = previousStartValue + relativeDiff; + values[0].setFloatValues(newStartValue, alpha); + mHeadsUpScrim.setTag(TAG_HUN_START_ALPHA, newStartValue); + mHeadsUpScrim.setTag(TAG_HUN_END_ALPHA, alpha); + previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); + } else { + // update the alpha directly + setCurrentScrimAlpha(mHeadsUpScrim, alpha); + updateScrimColor(mHeadsUpScrim); + } + } + } + } + + /** + * Set the amount the current top heads up view is dragged. The range is from 0 to 1 and 0 means + * the heads up is in its resting space and 1 means it's fully dragged out. + * + * @param draggedHeadsUpView the dragged view + * @param topHeadsUpDragAmount how far is it dragged + */ + public void setTopHeadsUpDragAmount(View draggedHeadsUpView, float topHeadsUpDragAmount) { + mTopHeadsUpDragAmount = topHeadsUpDragAmount; + mDraggedHeadsUpView = draggedHeadsUpView; + updateHeadsUpScrim(false); + } + + private float calculateHeadsUpAlpha() { + if (mPinnedHeadsUpCount >= 2) { + return 1.0f; + } else if (mPinnedHeadsUpCount == 0) { + return 0.0f; + } else { + return 1.0f - mTopHeadsUpDragAmount; + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SecureCameraLaunchManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SecureCameraLaunchManager.java index 4a43c47..45c8938 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SecureCameraLaunchManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SecureCameraLaunchManager.java @@ -27,6 +27,7 @@ import android.provider.MediaStore; import android.util.Log; import com.android.internal.widget.LockPatternUtils; +import com.android.keyguard.KeyguardUpdateMonitor; import java.util.HashMap; import java.util.List; @@ -228,7 +229,7 @@ public class SecureCameraLaunchManager { // Get the list of applications that can handle the intent. final List<ResolveInfo> appList = packageManager.queryIntentActivitiesAsUser( - intent, PackageManager.MATCH_DEFAULT_ONLY, mLockPatternUtils.getCurrentUser()); + intent, PackageManager.MATCH_DEFAULT_ONLY, KeyguardUpdateMonitor.getCurrentUser()); if (appList.size() == 0) { if (DEBUG) Log.d(TAG, "No targets found for secure camera intent"); return false; @@ -237,7 +238,7 @@ public class SecureCameraLaunchManager { // Get the application that the intent resolves to. ResolveInfo resolved = packageManager.resolveActivityAsUser(intent, PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_META_DATA, - mLockPatternUtils.getCurrentUser()); + KeyguardUpdateMonitor.getCurrentUser()); if (resolved == null || resolved.activityInfo == null) { return false; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/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..0caf51a 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; @@ -144,6 +145,7 @@ public class StatusBarKeyguardViewManager { if (mShowing) { if (mOccluded) { mPhoneStatusBar.hideKeyguard(); + mPhoneStatusBar.stopWaitingForKeyguardExit(); mBouncer.hide(false /* destroyView */); } else { showBouncerOrKeyguard(); @@ -187,10 +189,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 +259,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 +437,13 @@ public class StatusBarKeyguardViewManager { public boolean isInputRestricted() { return mViewMediatorCallback.isInputRestricted(); } + + public void keyguardGoingAway() { + mPhoneStatusBar.keyguardGoingAway(); + } + + public void animateCollapsePanels(float speedUpFactor) { + mPhoneStatusBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */, + false /* delayed */, speedUpFactor); + } } 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..84a9f64 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) { @@ -128,7 +130,7 @@ public class StatusBarWindowManager { private void applyHeight(State state) { boolean expanded = state.isKeyguardShowingAndNotOccluded() || state.statusBarExpanded - || state.keyguardFadingAway || state.bouncerShowing; + || state.keyguardFadingAway || state.bouncerShowing || state.headsUpShowing; if (expanded) { mLpChanged.height = ViewGroup.LayoutParams.MATCH_PARENT; } else { @@ -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; } @@ -170,11 +172,20 @@ public class StatusBarWindowManager { applyUserActivityTimeout(state); applyInputFeatures(state); applyFitsSystemWindows(state); + applyModalFlag(state); if (mLp.copyFrom(mLpChanged) != 0) { mWindowManager.updateViewLayout(mStatusBarView, mLp); } } + private void applyModalFlag(State state) { + if (state.headsUpShowing) { + mLpChanged.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; + } else { + mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; + } + } + public void setKeyguardShowing(boolean showing) { mCurrentState.keyguardShowing = showing; apply(mCurrentState); @@ -201,11 +212,6 @@ public class StatusBarWindowManager { apply(mCurrentState); } - public void setKeyguardUserActivityTimeout(long timeout) { - mCurrentState.keyguardUserActivityTimeout = timeout; - apply(mCurrentState); - } - public void setBouncerShowing(boolean showing) { mCurrentState.bouncerShowing = showing; apply(mCurrentState); @@ -221,6 +227,11 @@ public class StatusBarWindowManager { apply(mCurrentState); } + public void setHeadsUpShowing(boolean showing) { + mCurrentState.headsUpShowing = showing; + apply(mCurrentState); + } + /** * @param state The {@link StatusBarState} of the status bar. */ @@ -235,10 +246,10 @@ public class StatusBarWindowManager { boolean keyguardNeedsInput; boolean statusBarExpanded; boolean statusBarFocusable; - long keyguardUserActivityTimeout; boolean bouncerShowing; boolean keyguardFadingAway; boolean qsExpanded; + boolean headsUpShowing; /** * The {@link BaseStatusBar} state from the status bar. 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..66d71f6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java @@ -80,8 +80,8 @@ public class UnlockMethodCache { } private void update(boolean updateAlways) { - int user = mLockPatternUtils.getCurrentUser(); - boolean secure = mLockPatternUtils.isSecure(); + int user = KeyguardUpdateMonitor.getCurrentUser(); + boolean secure = mLockPatternUtils.isSecure(user); boolean currentlyInsecure = !secure || mKeyguardUpdateMonitor.getUserHasTrust(user); boolean trustManaged = mKeyguardUpdateMonitor.getUserTrustIsManaged(user); boolean faceUnlockRunning = mKeyguardUpdateMonitor.isFaceUnlockRunning(user) @@ -125,7 +125,7 @@ public class UnlockMethodCache { } @Override - public void onFingerprintRecognized(int userId) { + public void onFingerprintAuthenticated(int userId) { update(false /* updateAlways */); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/VelocityTrackerFactory.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/VelocityTrackerFactory.java index 4f43b4d..e153b85 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/VelocityTrackerFactory.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/VelocityTrackerFactory.java @@ -20,8 +20,6 @@ import android.content.Context; import com.android.systemui.R; -import static android.util.Pools.SynchronizedPool; - /** * A class to generate {@link VelocityTrackerInterface}, depending on the configuration. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java index ad4c211..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/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java new file mode 100644 index 0000000..0db9221 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java @@ -0,0 +1,562 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy; + +import android.content.Context; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.os.Handler; +import android.os.SystemClock; +import android.provider.Settings; +import android.util.ArrayMap; +import android.util.Log; +import android.util.Pools; +import android.view.ViewTreeObserver; +import android.view.accessibility.AccessibilityEvent; + +import com.android.systemui.R; +import com.android.systemui.statusbar.ExpandableNotificationRow; +import com.android.systemui.statusbar.NotificationData; +import com.android.systemui.statusbar.phone.PhoneStatusBar; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Stack; +import java.util.TreeSet; + +/** + * A manager which handles heads up notifications which is a special mode where + * they simply peek from the top of the screen. + */ +public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsListener { + private static final String TAG = "HeadsUpManager"; + private static final boolean DEBUG = false; + private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms"; + + private final int mHeadsUpNotificationDecay; + private final int mMinimumDisplayTime; + + private final int mTouchAcceptanceDelay; + private final ArrayMap<String, Long> mSnoozedPackages; + private final HashSet<OnHeadsUpChangedListener> mListeners = new HashSet<>(); + private final int mDefaultSnoozeLengthMs; + private final Handler mHandler = new Handler(); + private final Pools.Pool<HeadsUpEntry> mEntryPool = new Pools.Pool<HeadsUpEntry>() { + + private Stack<HeadsUpEntry> mPoolObjects = new Stack<>(); + + @Override + public HeadsUpEntry acquire() { + if (!mPoolObjects.isEmpty()) { + return mPoolObjects.pop(); + } + return new HeadsUpEntry(); + } + + @Override + public boolean release(HeadsUpEntry instance) { + instance.reset(); + mPoolObjects.push(instance); + return true; + } + }; + + private PhoneStatusBar mBar; + private int mSnoozeLengthMs; + private ContentObserver mSettingsObserver; + private HashMap<String, HeadsUpEntry> mHeadsUpEntries = new HashMap<>(); + private TreeSet<HeadsUpEntry> mSortedEntries = new TreeSet<>(); + private HashSet<String> mSwipedOutKeys = new HashSet<>(); + private int mUser; + private Clock mClock; + private boolean mReleaseOnExpandFinish; + private boolean mTrackingHeadsUp; + private HashSet<NotificationData.Entry> mEntriesToRemoveAfterExpand = new HashSet<>(); + private boolean mIsExpanded; + private boolean mHasPinnedNotification; + private int[] mTmpTwoArray = new int[2]; + + public HeadsUpManager(final Context context, ViewTreeObserver observer) { + Resources resources = context.getResources(); + mTouchAcceptanceDelay = resources.getInteger(R.integer.touch_acceptance_delay); + mSnoozedPackages = new ArrayMap<>(); + mDefaultSnoozeLengthMs = resources.getInteger(R.integer.heads_up_default_snooze_length_ms); + mSnoozeLengthMs = mDefaultSnoozeLengthMs; + mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time); + mHeadsUpNotificationDecay = resources.getInteger(R.integer.heads_up_notification_decay); + mClock = new Clock(); + + mSnoozeLengthMs = Settings.Global.getInt(context.getContentResolver(), + SETTING_HEADS_UP_SNOOZE_LENGTH_MS, mDefaultSnoozeLengthMs); + mSettingsObserver = new ContentObserver(mHandler) { + @Override + public void onChange(boolean selfChange) { + final int packageSnoozeLengthMs = Settings.Global.getInt( + context.getContentResolver(), SETTING_HEADS_UP_SNOOZE_LENGTH_MS, -1); + if (packageSnoozeLengthMs > -1 && packageSnoozeLengthMs != mSnoozeLengthMs) { + mSnoozeLengthMs = packageSnoozeLengthMs; + if (DEBUG) Log.v(TAG, "mSnoozeLengthMs = " + mSnoozeLengthMs); + } + } + }; + context.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS), false, + mSettingsObserver); + observer.addOnComputeInternalInsetsListener(this); + } + + public void setBar(PhoneStatusBar bar) { + mBar = bar; + } + + public void addListener(OnHeadsUpChangedListener listener) { + mListeners.add(listener); + } + + public PhoneStatusBar getBar() { + return mBar; + } + + /** + * Called when posting a new notification to the heads up. + */ + public void showNotification(NotificationData.Entry headsUp) { + if (DEBUG) Log.v(TAG, "showNotification"); + addHeadsUpEntry(headsUp); + updateNotification(headsUp, true); + headsUp.setInterruption(); + } + + /** + * Called when updating or posting a notification to the heads up. + */ + public void updateNotification(NotificationData.Entry headsUp, boolean alert) { + if (DEBUG) Log.v(TAG, "updateNotification"); + + headsUp.row.setChildrenExpanded(false /* expanded */, false /* animated */); + headsUp.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); + + if (alert) { + HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(headsUp.key); + headsUpEntry.updateEntry(); + setEntryPinned(headsUpEntry, !mIsExpanded /* isPinned */); + } + } + + private void addHeadsUpEntry(NotificationData.Entry entry) { + HeadsUpEntry headsUpEntry = mEntryPool.acquire(); + + // This will also add the entry to the sortedList + headsUpEntry.setEntry(entry); + mHeadsUpEntries.put(entry.key, headsUpEntry); + entry.row.setHeadsUp(true); + setEntryPinned(headsUpEntry, !mIsExpanded /* isPinned */); + for (OnHeadsUpChangedListener listener : mListeners) { + listener.onHeadsUpStateChanged(entry, true); + } + entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); + } + + private void setEntryPinned(HeadsUpEntry headsUpEntry, boolean isPinned) { + ExpandableNotificationRow row = headsUpEntry.entry.row; + if (row.isPinned() != isPinned) { + row.setPinned(isPinned); + updatePinnedMode(); + for (OnHeadsUpChangedListener listener : mListeners) { + if (isPinned) { + listener.onHeadsUpPinned(row); + } else { + listener.onHeadsUpUnPinned(row); + } + } + } + } + + private void removeHeadsUpEntry(NotificationData.Entry entry) { + HeadsUpEntry remove = mHeadsUpEntries.remove(entry.key); + mSortedEntries.remove(remove); + entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); + entry.row.setHeadsUp(false); + setEntryPinned(remove, false /* isPinned */); + for (OnHeadsUpChangedListener listener : mListeners) { + listener.onHeadsUpStateChanged(entry, false); + } + mEntryPool.release(remove); + } + + private void updatePinnedMode() { + boolean hasPinnedNotification = hasPinnedNotificationInternal(); + if (hasPinnedNotification == mHasPinnedNotification) { + return; + } + mHasPinnedNotification = hasPinnedNotification; + for (OnHeadsUpChangedListener listener : mListeners) { + listener.onHeadsUpPinnedModeChanged(hasPinnedNotification); + } + } + + /** + * React to the removal of the notification in the heads up. + * + * @return true if the notification was removed and false if it still needs to be kept around + * for a bit since it wasn't shown long enough + */ + public boolean removeNotification(String key) { + if (DEBUG) Log.v(TAG, "remove"); + if (wasShownLongEnough(key)) { + releaseImmediately(key); + return true; + } else { + getHeadsUpEntry(key).removeAsSoonAsPossible(); + return false; + } + } + + private boolean wasShownLongEnough(String key) { + HeadsUpEntry headsUpEntry = getHeadsUpEntry(key); + HeadsUpEntry topEntry = getTopEntry(); + if (mSwipedOutKeys.contains(key)) { + // We always instantly dismiss views being manually swiped out. + mSwipedOutKeys.remove(key); + return true; + } + if (headsUpEntry != topEntry) { + return true; + } + return headsUpEntry.wasShownLongEnough(); + } + + public boolean isHeadsUp(String key) { + return mHeadsUpEntries.containsKey(key); + } + + /** + * Push any current Heads Up notification down into the shade. + */ + public void releaseAllImmediately() { + if (DEBUG) Log.v(TAG, "releaseAllImmediately"); + ArrayList<String> keys = new ArrayList<>(mHeadsUpEntries.keySet()); + for (String key : keys) { + releaseImmediately(key); + } + } + + public void releaseImmediately(String key) { + HeadsUpEntry headsUpEntry = getHeadsUpEntry(key); + if (headsUpEntry == null) { + return; + } + NotificationData.Entry shadeEntry = headsUpEntry.entry; + removeHeadsUpEntry(shadeEntry); + } + + public boolean isSnoozed(String packageName) { + final String key = snoozeKey(packageName, mUser); + Long snoozedUntil = mSnoozedPackages.get(key); + if (snoozedUntil != null) { + if (snoozedUntil > SystemClock.elapsedRealtime()) { + if (DEBUG) Log.v(TAG, key + " snoozed"); + return true; + } + mSnoozedPackages.remove(packageName); + } + return false; + } + + public void snooze() { + for (String key : mHeadsUpEntries.keySet()) { + HeadsUpEntry entry = mHeadsUpEntries.get(key); + String packageName = entry.entry.notification.getPackageName(); + mSnoozedPackages.put(snoozeKey(packageName, mUser), + SystemClock.elapsedRealtime() + mSnoozeLengthMs); + } + mReleaseOnExpandFinish = true; + } + + private static String snoozeKey(String packageName, int user) { + return user + "," + packageName; + } + + private HeadsUpEntry getHeadsUpEntry(String key) { + return mHeadsUpEntries.get(key); + } + + public NotificationData.Entry getEntry(String key) { + return mHeadsUpEntries.get(key).entry; + } + + public TreeSet<HeadsUpEntry> getSortedEntries() { + return mSortedEntries; + } + + public HeadsUpEntry getTopEntry() { + return mSortedEntries.isEmpty() ? null : mSortedEntries.first(); + } + + /** + * Decides whether a click is invalid for a notification, i.e it has not been shown long enough + * that a user might have consciously clicked on it. + * + * @param key the key of the touched notification + * @return whether the touch is invalid and should be discarded + */ + public boolean shouldSwallowClick(String key) { + HeadsUpEntry entry = mHeadsUpEntries.get(key); + if (entry != null && mClock.currentTimeMillis() < entry.postTime) { + return true; + } + return false; + } + + public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) { + if (!mIsExpanded && mHasPinnedNotification) { + int minX = Integer.MAX_VALUE; + int maxX = 0; + int minY = Integer.MAX_VALUE; + int maxY = 0; + for (HeadsUpEntry entry : mSortedEntries) { + ExpandableNotificationRow row = entry.entry.row; + if (row.isPinned()) { + row.getLocationOnScreen(mTmpTwoArray); + minX = Math.min(minX, mTmpTwoArray[0]); + minY = Math.min(minY, 0); + maxX = Math.max(maxX, mTmpTwoArray[0] + row.getWidth()); + maxY = Math.max(maxY, row.getHeadsUpHeight()); + } + } + + info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); + info.touchableRegion.set(minX, minY, maxX, maxY); + } + } + + public void setUser(int user) { + mUser = user; + } + + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("HeadsUpManager state:"); + pw.print(" mTouchAcceptanceDelay="); pw.println(mTouchAcceptanceDelay); + pw.print(" mSnoozeLengthMs="); pw.println(mSnoozeLengthMs); + pw.print(" now="); pw.println(SystemClock.elapsedRealtime()); + pw.print(" mUser="); pw.println(mUser); + for (HeadsUpEntry entry: mSortedEntries) { + pw.print(" HeadsUpEntry="); pw.println(entry.entry); + } + int N = mSnoozedPackages.size(); + pw.println(" snoozed packages: " + N); + for (int i = 0; i < N; i++) { + pw.print(" "); pw.print(mSnoozedPackages.valueAt(i)); + pw.print(", "); pw.println(mSnoozedPackages.keyAt(i)); + } + } + + public boolean hasPinnedHeadsUp() { + return mHasPinnedNotification; + } + + private boolean hasPinnedNotificationInternal() { + for (String key : mHeadsUpEntries.keySet()) { + HeadsUpEntry entry = mHeadsUpEntries.get(key); + if (entry.entry.row.isPinned()) { + return true; + } + } + return false; + } + + /** + * Notifies that a notification was swiped out and will be removed. + * + * @param key the notification key + */ + public void addSwipedOutNotification(String key) { + mSwipedOutKeys.add(key); + } + + public void unpinAll() { + for (String key : mHeadsUpEntries.keySet()) { + HeadsUpEntry entry = mHeadsUpEntries.get(key); + setEntryPinned(entry, false /* isPinned */); + } + } + + public void onExpandingFinished() { + if (mReleaseOnExpandFinish) { + releaseAllImmediately(); + mReleaseOnExpandFinish = false; + } else { + for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) { + removeHeadsUpEntry(entry); + } + mEntriesToRemoveAfterExpand.clear(); + } + } + + public void setTrackingHeadsUp(boolean trackingHeadsUp) { + mTrackingHeadsUp = trackingHeadsUp; + } + + public void setIsExpanded(boolean isExpanded) { + if (isExpanded != mIsExpanded) { + mIsExpanded = isExpanded; + if (isExpanded) { + unpinAll(); + } + } + } + + public int getTopHeadsUpHeight() { + HeadsUpEntry topEntry = getTopEntry(); + return topEntry != null ? topEntry.entry.row.getHeadsUpHeight() : 0; + } + + /** + * Compare two entries and decide how they should be ranked. + * + * @return -1 if the first argument should be ranked higher than the second, 1 if the second + * one should be ranked higher and 0 if they are equal. + */ + public int compare(NotificationData.Entry a, NotificationData.Entry b) { + HeadsUpEntry aEntry = getHeadsUpEntry(a.key); + HeadsUpEntry bEntry = getHeadsUpEntry(b.key); + if (aEntry == null || bEntry == null) { + return aEntry == null ? 1 : -1; + } + return aEntry.compareTo(bEntry); + } + + + /** + * This represents a notification and how long it is in a heads up mode. It also manages its + * lifecycle automatically when created. + */ + public class HeadsUpEntry implements Comparable<HeadsUpEntry> { + public NotificationData.Entry entry; + public long postTime; + public long earliestRemovaltime; + private Runnable mRemoveHeadsUpRunnable; + + public void setEntry(final NotificationData.Entry entry) { + this.entry = entry; + + // The actual post time will be just after the heads-up really slided in + postTime = mClock.currentTimeMillis() + mTouchAcceptanceDelay; + mRemoveHeadsUpRunnable = new Runnable() { + @Override + public void run() { + if (!mTrackingHeadsUp) { + removeHeadsUpEntry(entry); + } else { + mEntriesToRemoveAfterExpand.add(entry); + } + } + }; + updateEntry(); + } + + public void updateEntry() { + long currentTime = mClock.currentTimeMillis(); + earliestRemovaltime = currentTime + mMinimumDisplayTime; + postTime = Math.max(postTime, currentTime); + removeAutoRemovalCallbacks(); + if (canEntryDecay()) { + long finishTime = postTime + mHeadsUpNotificationDecay; + long removeDelay = Math.max(finishTime - currentTime, mMinimumDisplayTime); + mHandler.postDelayed(mRemoveHeadsUpRunnable, removeDelay); + } + updateSortOrder(HeadsUpEntry.this); + } + + private boolean canEntryDecay() { + return entry.notification.getNotification().fullScreenIntent == null; + } + + @Override + public int compareTo(HeadsUpEntry o) { + return postTime < o.postTime ? 1 + : postTime == o.postTime ? 0 + : -1; + } + + public void removeAutoRemovalCallbacks() { + mHandler.removeCallbacks(mRemoveHeadsUpRunnable); + } + + public boolean wasShownLongEnough() { + return earliestRemovaltime < mClock.currentTimeMillis(); + } + + public void removeAsSoonAsPossible() { + removeAutoRemovalCallbacks(); + mHandler.postDelayed(mRemoveHeadsUpRunnable, + earliestRemovaltime - mClock.currentTimeMillis()); + } + + public void reset() { + removeAutoRemovalCallbacks(); + entry = null; + mRemoveHeadsUpRunnable = null; + } + } + + /** + * Update the sorted heads up order. + * + * @param headsUpEntry the headsUp that changed + */ + private void updateSortOrder(HeadsUpEntry headsUpEntry) { + mSortedEntries.remove(headsUpEntry); + mSortedEntries.add(headsUpEntry); + } + + public static class Clock { + public long currentTimeMillis() { + return SystemClock.elapsedRealtime(); + } + } + + public interface OnHeadsUpChangedListener { + /** + * The state whether there exist pinned heads-ups or not changed. + * + * @param inPinnedMode whether there are any pinned heads-ups + */ + void onHeadsUpPinnedModeChanged(boolean inPinnedMode); + + /** + * A notification was just pinned to the top. + */ + void onHeadsUpPinned(ExpandableNotificationRow headsUp); + + /** + * A notification was just unpinned from the top. + */ + void onHeadsUpUnPinned(ExpandableNotificationRow headsUp); + + /** + * A notification just became a heads up or turned back to its normal state. + * + * @param entry the entry of the changed notification + * @param isHeadsUp whether the notification is now a headsUp notification + */ + void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java deleted file mode 100644 index 2e3e67a..0000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java +++ /dev/null @@ -1,495 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.policy; - -import android.content.Context; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.database.ContentObserver; -import android.graphics.Outline; -import android.graphics.Rect; -import android.os.SystemClock; -import android.provider.Settings; -import android.util.ArrayMap; -import android.util.AttributeSet; -import android.util.Log; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.ViewGroup; -import android.view.ViewOutlineProvider; -import android.view.ViewTreeObserver; -import android.view.accessibility.AccessibilityEvent; -import android.widget.FrameLayout; - -import com.android.systemui.ExpandHelper; -import com.android.systemui.Gefingerpoken; -import com.android.systemui.R; -import com.android.systemui.SwipeHelper; -import com.android.systemui.statusbar.ExpandableView; -import com.android.systemui.statusbar.NotificationData; -import com.android.systemui.statusbar.phone.PhoneStatusBar; - -import java.util.ArrayList; - -public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper.Callback, ExpandHelper.Callback, - ViewTreeObserver.OnComputeInternalInsetsListener { - private static final String TAG = "HeadsUpNotificationView"; - private static final boolean DEBUG = false; - private static final boolean SPEW = DEBUG; - private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms"; - - Rect mTmpRect = new Rect(); - int[] mTmpTwoArray = new int[2]; - - private final int mTouchSensitivityDelay; - private final float mMaxAlpha = 1f; - private final ArrayMap<String, Long> mSnoozedPackages; - private final int mDefaultSnoozeLengthMs; - - private SwipeHelper mSwipeHelper; - private EdgeSwipeHelper mEdgeSwipeHelper; - - private PhoneStatusBar mBar; - - private long mStartTouchTime; - private ViewGroup mContentHolder; - private int mSnoozeLengthMs; - private ContentObserver mSettingsObserver; - - private NotificationData.Entry mHeadsUp; - private int mUser; - private String mMostRecentPackageName; - - public HeadsUpNotificationView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public HeadsUpNotificationView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - Resources resources = context.getResources(); - mTouchSensitivityDelay = resources.getInteger(R.integer.heads_up_sensitivity_delay); - if (DEBUG) Log.v(TAG, "create() " + mTouchSensitivityDelay); - mSnoozedPackages = new ArrayMap<>(); - mDefaultSnoozeLengthMs = resources.getInteger(R.integer.heads_up_default_snooze_length_ms); - mSnoozeLengthMs = mDefaultSnoozeLengthMs; - } - - public void updateResources() { - if (mContentHolder != null) { - final LayoutParams lp = (LayoutParams) mContentHolder.getLayoutParams(); - lp.width = getResources().getDimensionPixelSize(R.dimen.notification_panel_width); - lp.gravity = getResources().getInteger(R.integer.notification_panel_layout_gravity); - mContentHolder.setLayoutParams(lp); - } - } - - public void setBar(PhoneStatusBar bar) { - mBar = bar; - } - - public ViewGroup getHolder() { - return mContentHolder; - } - - public boolean showNotification(NotificationData.Entry headsUp) { - if (mHeadsUp != null && headsUp != null && !mHeadsUp.key.equals(headsUp.key)) { - // bump any previous heads up back to the shade - release(); - } - - mHeadsUp = headsUp; - if (mContentHolder != null) { - mContentHolder.removeAllViews(); - } - - if (mHeadsUp != null) { - mMostRecentPackageName = mHeadsUp.notification.getPackageName(); - mHeadsUp.row.setSystemExpanded(true); - mHeadsUp.row.setSensitive(false); - mHeadsUp.row.setHeadsUp(true); - mHeadsUp.row.setHideSensitive( - false, false /* animated */, 0 /* delay */, 0 /* duration */); - if (mContentHolder == null) { - // too soon! - return false; - } - mContentHolder.setX(0); - mContentHolder.setVisibility(View.VISIBLE); - mContentHolder.setAlpha(mMaxAlpha); - mContentHolder.addView(mHeadsUp.row); - sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); - - mSwipeHelper.snapChild(mContentHolder, 1f); - mStartTouchTime = SystemClock.elapsedRealtime() + mTouchSensitivityDelay; - - mHeadsUp.setInterruption(); - - // 2. Animate mHeadsUpNotificationView in - mBar.scheduleHeadsUpOpen(); - - // 3. Set alarm to age the notification off - mBar.resetHeadsUpDecayTimer(); - } - return true; - } - - @Override - protected void onVisibilityChanged(View changedView, int visibility) { - super.onVisibilityChanged(changedView, visibility); - if (changedView.getVisibility() == VISIBLE) { - sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); - } - } - - public boolean isShowing(String key) { - return mHeadsUp != null && mHeadsUp.key.equals(key); - } - - /** Discard the Heads Up notification. */ - public void clear() { - mHeadsUp = null; - mBar.scheduleHeadsUpClose(); - } - - /** Respond to dismissal of the Heads Up window. */ - public void dismiss() { - if (mHeadsUp == null) return; - if (mHeadsUp.notification.isClearable()) { - mBar.onNotificationClear(mHeadsUp.notification); - } else { - release(); - } - mHeadsUp = null; - mBar.scheduleHeadsUpClose(); - } - - /** Push any current Heads Up notification down into the shade. */ - public void release() { - if (mHeadsUp != null) { - mBar.displayNotificationFromHeadsUp(mHeadsUp.notification); - } - mHeadsUp = null; - } - - public boolean isSnoozed(String packageName) { - final String key = snoozeKey(packageName, mUser); - Long snoozedUntil = mSnoozedPackages.get(key); - if (snoozedUntil != null) { - if (snoozedUntil > SystemClock.elapsedRealtime()) { - if (DEBUG) Log.v(TAG, key + " snoozed"); - return true; - } - mSnoozedPackages.remove(packageName); - } - return false; - } - - private void snooze() { - if (mMostRecentPackageName != null) { - mSnoozedPackages.put(snoozeKey(mMostRecentPackageName, mUser), - SystemClock.elapsedRealtime() + mSnoozeLengthMs); - } - releaseAndClose(); - } - - private static String snoozeKey(String packageName, int user) { - return user + "," + packageName; - } - - public void releaseAndClose() { - release(); - mBar.scheduleHeadsUpClose(); - } - - public NotificationData.Entry getEntry() { - return mHeadsUp; - } - - public boolean isClearable() { - return mHeadsUp == null || mHeadsUp.notification.isClearable(); - } - - // ViewGroup methods - - private static final ViewOutlineProvider CONTENT_HOLDER_OUTLINE_PROVIDER = - new ViewOutlineProvider() { - @Override - public void getOutline(View view, Outline outline) { - int outlineLeft = view.getPaddingLeft(); - int outlineTop = view.getPaddingTop(); - - // Apply padding to shadow. - outline.setRect(outlineLeft, outlineTop, - view.getWidth() - outlineLeft - view.getPaddingRight(), - view.getHeight() - outlineTop - view.getPaddingBottom()); - } - }; - - @Override - public void onAttachedToWindow() { - final ViewConfiguration viewConfiguration = ViewConfiguration.get(getContext()); - float touchSlop = viewConfiguration.getScaledTouchSlop(); - mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, getContext()); - mSwipeHelper.setMaxSwipeProgress(mMaxAlpha); - mEdgeSwipeHelper = new EdgeSwipeHelper(touchSlop); - - int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height); - int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_max_height); - - mContentHolder = (ViewGroup) findViewById(R.id.content_holder); - mContentHolder.setOutlineProvider(CONTENT_HOLDER_OUTLINE_PROVIDER); - - mSnoozeLengthMs = Settings.Global.getInt(mContext.getContentResolver(), - SETTING_HEADS_UP_SNOOZE_LENGTH_MS, mDefaultSnoozeLengthMs); - mSettingsObserver = new ContentObserver(getHandler()) { - @Override - public void onChange(boolean selfChange) { - final int packageSnoozeLengthMs = Settings.Global.getInt( - mContext.getContentResolver(), SETTING_HEADS_UP_SNOOZE_LENGTH_MS, -1); - if (packageSnoozeLengthMs > -1 && packageSnoozeLengthMs != mSnoozeLengthMs) { - mSnoozeLengthMs = packageSnoozeLengthMs; - if (DEBUG) Log.v(TAG, "mSnoozeLengthMs = " + mSnoozeLengthMs); - } - } - }; - mContext.getContentResolver().registerContentObserver( - Settings.Global.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS), false, - mSettingsObserver); - if (DEBUG) Log.v(TAG, "mSnoozeLengthMs = " + mSnoozeLengthMs); - - if (mHeadsUp != null) { - // whoops, we're on already! - showNotification(mHeadsUp); - } - - getViewTreeObserver().addOnComputeInternalInsetsListener(this); - } - - @Override - protected void onDetachedFromWindow() { - mContext.getContentResolver().unregisterContentObserver(mSettingsObserver); - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - if (DEBUG) Log.v(TAG, "onInterceptTouchEvent()"); - if (SystemClock.elapsedRealtime() < mStartTouchTime) { - return true; - } - return mEdgeSwipeHelper.onInterceptTouchEvent(ev) - || mSwipeHelper.onInterceptTouchEvent(ev) - || super.onInterceptTouchEvent(ev); - } - - // View methods - - @Override - public void onDraw(android.graphics.Canvas c) { - super.onDraw(c); - if (DEBUG) { - //Log.d(TAG, "onDraw: canvas height: " + c.getHeight() + "px; measured height: " - // + getMeasuredHeight() + "px"); - c.save(); - c.clipRect(6, 6, c.getWidth() - 6, getMeasuredHeight() - 6, - android.graphics.Region.Op.DIFFERENCE); - c.drawColor(0xFFcc00cc); - c.restore(); - } - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - if (SystemClock.elapsedRealtime() < mStartTouchTime) { - return false; - } - mBar.resetHeadsUpDecayTimer(); - return mEdgeSwipeHelper.onTouchEvent(ev) - || mSwipeHelper.onTouchEvent(ev) - || super.onTouchEvent(ev); - } - - @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - float densityScale = getResources().getDisplayMetrics().density; - mSwipeHelper.setDensityScale(densityScale); - float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop(); - mSwipeHelper.setPagingTouchSlop(pagingTouchSlop); - } - - // ExpandHelper.Callback methods - - @Override - public ExpandableView getChildAtRawPosition(float x, float y) { - return getChildAtPosition(x, y); - } - - @Override - public ExpandableView getChildAtPosition(float x, float y) { - return mHeadsUp == null ? null : mHeadsUp.row; - } - - @Override - public boolean canChildBeExpanded(View v) { - return mHeadsUp != null && mHeadsUp.row == v && mHeadsUp.row.isExpandable(); - } - - @Override - public void setUserExpandedChild(View v, boolean userExpanded) { - if (mHeadsUp != null && mHeadsUp.row == v) { - mHeadsUp.row.setUserExpanded(userExpanded); - } - } - - @Override - public void setUserLockedChild(View v, boolean userLocked) { - if (mHeadsUp != null && mHeadsUp.row == v) { - mHeadsUp.row.setUserLocked(userLocked); - } - } - - @Override - public void expansionStateChanged(boolean isExpanding) { - - } - - // SwipeHelper.Callback methods - - @Override - public boolean canChildBeDismissed(View v) { - return true; - } - - @Override - public boolean isAntiFalsingNeeded() { - return false; - } - - @Override - public float getFalsingThresholdFactor() { - return 1.0f; - } - - @Override - public void onChildDismissed(View v) { - Log.v(TAG, "User swiped heads up to dismiss"); - mBar.onHeadsUpDismissed(); - } - - @Override - public void onBeginDrag(View v) { - } - - @Override - public void onDragCancelled(View v) { - mContentHolder.setAlpha(mMaxAlpha); // sometimes this isn't quite reset - } - - @Override - public void onChildSnappedBack(View animView) { - } - - @Override - public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) { - getBackground().setAlpha((int) (255 * swipeProgress)); - return false; - } - - @Override - public View getChildAtPosition(MotionEvent ev) { - return mContentHolder; - } - - @Override - public View getChildContentView(View v) { - return mContentHolder; - } - - @Override - public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) { - mContentHolder.getLocationOnScreen(mTmpTwoArray); - - info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); - info.touchableRegion.set(mTmpTwoArray[0], mTmpTwoArray[1], - mTmpTwoArray[0] + mContentHolder.getWidth(), - mTmpTwoArray[1] + mContentHolder.getHeight()); - } - - public void escalate() { - mBar.scheduleHeadsUpEscalation(); - } - - public String getKey() { - return mHeadsUp == null ? null : mHeadsUp.notification.getKey(); - } - - public void setUser(int user) { - mUser = user; - } - - private class EdgeSwipeHelper implements Gefingerpoken { - private static final boolean DEBUG_EDGE_SWIPE = false; - private final float mTouchSlop; - private boolean mConsuming; - private float mFirstY; - private float mFirstX; - - public EdgeSwipeHelper(float touchSlop) { - mTouchSlop = touchSlop; - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - switch (ev.getActionMasked()) { - case MotionEvent.ACTION_DOWN: - if (DEBUG_EDGE_SWIPE) Log.d(TAG, "action down " + ev.getY()); - mFirstX = ev.getX(); - mFirstY = ev.getY(); - mConsuming = false; - break; - - case MotionEvent.ACTION_MOVE: - if (DEBUG_EDGE_SWIPE) Log.d(TAG, "action move " + ev.getY()); - final float dY = ev.getY() - mFirstY; - final float daX = Math.abs(ev.getX() - mFirstX); - final float daY = Math.abs(dY); - if (!mConsuming && daX < daY && daY > mTouchSlop) { - snooze(); - if (dY > 0) { - if (DEBUG_EDGE_SWIPE) Log.d(TAG, "found an open"); - mBar.animateExpandNotificationsPanel(); - } - mConsuming = true; - } - break; - - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - if (DEBUG_EDGE_SWIPE) Log.d(TAG, "action done" ); - mConsuming = false; - break; - } - return mConsuming; - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - return mConsuming; - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java index 0863c86..7ca91a5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java @@ -22,7 +22,6 @@ public interface HotspotController { boolean isHotspotEnabled(); boolean isHotspotSupported(); void setHotspotEnabled(boolean enabled); - boolean isProvisioningNeeded(); public interface Callback { void onHotspotChanged(boolean enabled); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java index 5eff5a6..4bfd528 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java @@ -16,45 +16,38 @@ package com.android.systemui.statusbar.policy; -import android.app.ActivityManager; import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.net.ConnectivityManager; import android.net.wifi.WifiManager; -import android.os.SystemProperties; import android.os.UserHandle; -import android.provider.Settings; import android.util.Log; +import com.android.settingslib.TetherUtil; + import java.util.ArrayList; public class HotspotControllerImpl implements HotspotController { private static final String TAG = "HotspotController"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - // Keep these in sync with Settings TetherService.java - public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType"; - public static final String EXTRA_SET_ALARM = "extraSetAlarm"; - public static final String EXTRA_RUN_PROVISION = "extraRunProvision"; - public static final String EXTRA_ENABLE_WIFI_TETHER = "extraEnableWifiTether"; - // Keep this in sync with Settings TetherSettings.java - public static final int WIFI_TETHERING = 0; + private static final Intent TETHER_SERVICE_INTENT = new Intent() + .putExtra(TetherUtil.EXTRA_ADD_TETHER_TYPE, TetherUtil.TETHERING_WIFI) + .putExtra(TetherUtil.EXTRA_SET_ALARM, true) + .putExtra(TetherUtil.EXTRA_RUN_PROVISION, true) + .putExtra(TetherUtil.EXTRA_ENABLE_WIFI_TETHER, true) + .setComponent(TetherUtil.TETHER_SERVICE); private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>(); private final Receiver mReceiver = new Receiver(); private final Context mContext; private final WifiManager mWifiManager; - private final ConnectivityManager mConnectivityManager; public HotspotControllerImpl(Context context) { mContext = context; mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); - mConnectivityManager = (ConnectivityManager) - mContext.getSystemService(Context.CONNECTIVITY_SERVICE); } public void addCallback(Callback callback) { @@ -78,54 +71,17 @@ public class HotspotControllerImpl implements HotspotController { @Override public boolean isHotspotSupported() { - final boolean isSecondaryUser = ActivityManager.getCurrentUser() != UserHandle.USER_OWNER; - return !isSecondaryUser && mConnectivityManager.isTetheringSupported(); - } - - @Override - public boolean isProvisioningNeeded() { - // Keep in sync with other usage of config_mobile_hotspot_provision_app. - // TetherSettings#isProvisioningNeeded and - // ConnectivityManager#enforceTetherChangePermission - String[] provisionApp = mContext.getResources().getStringArray( - com.android.internal.R.array.config_mobile_hotspot_provision_app); - if (SystemProperties.getBoolean("net.tethering.noprovisioning", false) - || provisionApp == null) { - return false; - } - return (provisionApp.length == 2); + return TetherUtil.isTetheringSupported(mContext); } @Override public void setHotspotEnabled(boolean enabled) { final ContentResolver cr = mContext.getContentResolver(); // Call provisioning app which is called when enabling Tethering from Settings - if (enabled) { - if (isProvisioningNeeded()) { - String tetherEnable = mContext.getResources().getString( - com.android.internal.R.string.config_wifi_tether_enable); - Intent intent = new Intent(); - intent.putExtra(EXTRA_ADD_TETHER_TYPE, WIFI_TETHERING); - intent.putExtra(EXTRA_SET_ALARM, true); - intent.putExtra(EXTRA_RUN_PROVISION, true); - intent.putExtra(EXTRA_ENABLE_WIFI_TETHER, true); - intent.setComponent(ComponentName.unflattenFromString(tetherEnable)); - mContext.startServiceAsUser(intent, UserHandle.CURRENT); - } else { - int wifiState = mWifiManager.getWifiState(); - if ((wifiState == WifiManager.WIFI_STATE_ENABLING) || - (wifiState == WifiManager.WIFI_STATE_ENABLED)) { - mWifiManager.setWifiEnabled(false); - Settings.Global.putInt(cr, Settings.Global.WIFI_SAVED_STATE, 1); - } - mWifiManager.setWifiApEnabled(null, true); - } + if (enabled && TetherUtil.isProvisioningNeeded(mContext)) { + mContext.startServiceAsUser(TETHER_SERVICE_INTENT, UserHandle.CURRENT); } else { - mWifiManager.setWifiApEnabled(null, false); - if (Settings.Global.getInt(cr, Settings.Global.WIFI_SAVED_STATE, 0) == 1) { - mWifiManager.setWifiEnabled(true); - Settings.Global.putInt(cr, Settings.Global.WIFI_SAVED_STATE, 0); - } + TetherUtil.setWifiTethering(enabled, mContext); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java index 6998791..3f63b5f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java @@ -26,7 +26,7 @@ import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.drawable.Drawable; -import android.view.HardwareCanvas; +import android.view.DisplayListCanvas; import android.view.RenderNodeAnimator; import android.view.View; import android.view.animation.Interpolator; @@ -106,7 +106,7 @@ public class KeyButtonRipple extends Drawable { public void draw(Canvas canvas) { mSupportHardware = canvas.isHardwareAccelerated(); if (mSupportHardware) { - drawHardware((HardwareCanvas) canvas); + drawHardware((DisplayListCanvas) canvas); } else { drawSoftware(canvas); } @@ -118,7 +118,7 @@ public class KeyButtonRipple extends Drawable { } @Override - public void setColorFilter(ColorFilter cf) { + public void setColorFilter(ColorFilter colorFilter) { // Not supported. } @@ -131,7 +131,7 @@ public class KeyButtonRipple extends Drawable { return getBounds().width() > getBounds().height(); } - private void drawHardware(HardwareCanvas c) { + private void drawHardware(DisplayListCanvas c) { if (mDrawingHardwareGlow) { c.drawRoundRect(mLeftProp, mTopProp, mRightProp, mBottomProp, mRxProp, mRyProp, mPaintProp); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java index b9cc0f9..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..c3c6b12 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java @@ -0,0 +1,540 @@ +/* + * 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); + } + + public void setCarrierNetworkChangeMode(boolean carrierNetworkChangeMode) { + mCurrentState.carrierNetworkChangeMode = carrierNetworkChangeMode; + notifyListenersIfNecessary(); + } + + /** + * Start listening for phone state changes. + */ + public void registerListener() { + mPhone.listen(mPhoneStateListener, + PhoneStateListener.LISTEN_SERVICE_STATE + | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS + | PhoneStateListener.LISTEN_CALL_STATE + | PhoneStateListener.LISTEN_DATA_CONNECTION_STATE + | PhoneStateListener.LISTEN_DATA_ACTIVITY + | PhoneStateListener.LISTEN_CARRIER_NETWORK_CHANGE); + } + + /** + * Stop listening for phone state changes. + */ + public void unregisterListener() { + mPhone.listen(mPhoneStateListener, 0); + } + + /** + * Produce a mapping of data network types to icon groups for simple and quick use in + * updateTelephony. + */ + private void mapIconSets() { + mNetworkToIconLookup.clear(); + + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_0, TelephonyIcons.THREE_G); + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_A, TelephonyIcons.THREE_G); + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_B, TelephonyIcons.THREE_G); + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EHRPD, TelephonyIcons.THREE_G); + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UMTS, TelephonyIcons.THREE_G); + + if (!mConfig.showAtLeast3G) { + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UNKNOWN, + TelephonyIcons.UNKNOWN); + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EDGE, TelephonyIcons.E); + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_CDMA, TelephonyIcons.ONE_X); + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_1xRTT, TelephonyIcons.ONE_X); + + mDefaultIcons = TelephonyIcons.G; + } else { + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UNKNOWN, + TelephonyIcons.THREE_G); + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EDGE, + TelephonyIcons.THREE_G); + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_CDMA, + TelephonyIcons.THREE_G); + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_1xRTT, + TelephonyIcons.THREE_G); + mDefaultIcons = TelephonyIcons.THREE_G; + } + + MobileIconGroup hGroup = TelephonyIcons.THREE_G; + if (mConfig.hspaDataDistinguishable) { + hGroup = TelephonyIcons.H; + } + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSDPA, hGroup); + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSUPA, hGroup); + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSPA, hGroup); + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSPAP, hGroup); + + if (mConfig.show4gForLte) { + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE, TelephonyIcons.FOUR_G); + } else { + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE, TelephonyIcons.LTE); + } + } + + @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.carrierNetworkChangeMode + && mCurrentState.activityIn, + mCurrentState.dataConnected + && !mCurrentState.carrierNetworkChangeMode + && 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(), + getCurrentDarkIconId(), + typeIcon, + contentDescription, + dataContentDescription, + // Only wide if actually showing something. + icons.mIsWide && typeIcon != 0, + mSubscriptionInfo.getSubscriptionId()); + } + } + + private int getCurrentDarkIconId() { + return getCurrentIconId(false /* light */); + } + + @Override + protected MobileState cleanState() { + return new MobileState(); + } + + private boolean hasService() { + if (mServiceState != null) { + // Consider the device to be in service if either voice or data + // service is available. Some SIM cards are marketed as data-only + // and do not support voice service, and on these SIM cards, we + // want to show signal bars for data service as well as the "no + // service" or "emergency calls only" text that indicates that voice + // is not available. + switch (mServiceState.getVoiceRegState()) { + case ServiceState.STATE_POWER_OFF: + return false; + case ServiceState.STATE_OUT_OF_SERVICE: + case ServiceState.STATE_EMERGENCY_ONLY: + return mServiceState.getDataRegState() == ServiceState.STATE_IN_SERVICE; + default: + return true; + } + } else { + return false; + } + } + + private boolean isCdma() { + return (mSignalStrength != null) && !mSignalStrength.isGsm(); + } + + public boolean isEmergencyOnly() { + return (mServiceState != null && mServiceState.isEmergencyOnly()); + } + + private boolean isRoaming() { + if (isCdma()) { + final int iconMode = mServiceState.getCdmaEriIconMode(); + return mServiceState.getCdmaEriIconIndex() != EriInfo.ROAMING_INDICATOR_OFF + && (iconMode == EriInfo.ROAMING_ICON_MODE_NORMAL + || iconMode == EriInfo.ROAMING_ICON_MODE_FLASH); + } else { + return mServiceState != null && mServiceState.getRoaming(); + } + } + + private boolean isCarrierNetworkChangeActive() { + return !hasService() && mCurrentState.carrierNetworkChangeMode; + } + + public void handleBroadcast(Intent intent) { + String action = intent.getAction(); + if (action.equals(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION)) { + updateNetworkName(intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false), + intent.getStringExtra(TelephonyIntents.EXTRA_SPN), + intent.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 (isCarrierNetworkChangeActive()) { + mCurrentState.iconGroup = TelephonyIcons.CARRIER_NETWORK_CHANGE; + } else if (isRoaming()) { + mCurrentState.iconGroup = TelephonyIcons.ROAMING; + } + if (isEmergencyOnly() != mCurrentState.isEmergency) { + mCurrentState.isEmergency = isEmergencyOnly(); + mNetworkController.recalculateEmergency(); + } + // Fill in the network name if we think we have it. + if (mCurrentState.networkName == mNetworkNameDefault && mServiceState != null + && mServiceState.getOperatorAlphaShort() != null) { + mCurrentState.networkName = mServiceState.getOperatorAlphaShort(); + } + + notifyListenersIfNecessary(); + } + + @VisibleForTesting + void setActivity(int activity) { + mCurrentState.activityIn = activity == TelephonyManager.DATA_ACTIVITY_INOUT + || activity == TelephonyManager.DATA_ACTIVITY_IN; + mCurrentState.activityOut = activity == TelephonyManager.DATA_ACTIVITY_INOUT + || activity == TelephonyManager.DATA_ACTIVITY_OUT; + notifyListenersIfNecessary(); + } + + @Override + public void dump(PrintWriter pw) { + super.dump(pw); + pw.println(" mSubscription=" + mSubscriptionInfo + ","); + pw.println(" mServiceState=" + mServiceState + ","); + pw.println(" mSignalStrength=" + mSignalStrength + ","); + pw.println(" mDataState=" + mDataState + ","); + pw.println(" mDataNetType=" + mDataNetType + ","); + } + + class MobilePhoneStateListener extends PhoneStateListener { + public MobilePhoneStateListener(int subId) { + 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); + } + + @Override + public void onCarrierNetworkChange(boolean active) { + if (DEBUG) { + Log.d(mTag, "onCarrierNetworkChange: active=" + active); + } + mCurrentState.carrierNetworkChangeMode = active; + + updateTelephony(); + } + }; + + static class MobileIconGroup extends SignalController.IconGroup { + final int mDataContentDescription; // mContentDescriptionDataType + final int mDataType; + final boolean mIsWide; + final int[] mQsDataType; + + public MobileIconGroup(String name, int[][] sbIcons, int[][] qsIcons, int[] contentDesc, + int sbNullState, int qsNullState, int sbDiscState, int qsDiscState, + int discContentDesc, int dataContentDesc, int dataType, boolean isWide, + int[] qsDataType) { + this(name, sbIcons, sbIcons, qsIcons, contentDesc, sbNullState, qsNullState, + sbDiscState, sbDiscState, qsDiscState, discContentDesc, dataContentDesc, + dataType, isWide, qsDataType); + } + + public MobileIconGroup(String name, int[][] sbIcons, int[][] sbDarkIcons, int[][] qsIcons, + int[] contentDesc, int sbNullState, int qsNullState, int sbDiscState, + int sbDarkDiscState, int qsDiscState, int discContentDesc, int dataContentDesc, + int dataType, boolean isWide, int[] qsDataType) { + super(name, sbIcons, sbDarkIcons, qsIcons, contentDesc, sbNullState, qsNullState, + sbDiscState, sbDarkDiscState, 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; + boolean carrierNetworkChangeMode; + 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; + carrierNetworkChangeMode = state.carrierNetworkChangeMode; + } + + @Override + protected void toString(StringBuilder builder) { + super.toString(builder); + builder.append(','); + builder.append("dataSim=").append(dataSim).append(','); + builder.append("networkName=").append(networkName).append(','); + builder.append("dataConnected=").append(dataConnected).append(','); + builder.append("inetForNetwork=").append(inetForNetwork).append(','); + builder.append("isEmergency=").append(isEmergency).append(','); + builder.append("airplaneMode=").append(airplaneMode).append(','); + builder.append("carrierNetworkChangeMode=").append(carrierNetworkChangeMode); + } + + @Override + public boolean equals(Object o) { + return super.equals(o) + && Objects.equals(((MobileState) o).networkName, networkName) + && ((MobileState) o).dataSim == dataSim + && ((MobileState) o).dataConnected == dataConnected + && ((MobileState) o).isEmergency == isEmergency + && ((MobileState) o).airplaneMode == airplaneMode + && ((MobileState) o).carrierNetworkChangeMode == carrierNetworkChangeMode + && ((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..9adf028 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() { @@ -206,7 +173,7 @@ public class NetworkControllerImpl extends BroadcastReceiver filter.addAction(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED); filter.addAction(TelephonyIntents.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED); filter.addAction(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION); - filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE); + filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); filter.addAction(ConnectivityManager.INET_CONDITION_ACTION); filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED); @@ -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 @@ -373,17 +331,15 @@ public class NetworkControllerImpl extends BroadcastReceiver Log.d(TAG, "onReceive: intent=" + intent); } final String action = intent.getAction(); - if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE) || + if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION) || action.equals(ConnectivityManager.INET_CONDITION_ACTION)) { updateConnectivity(); - refreshCarrierLabel(); } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) { mConfig = Config.readConfig(mContext); handleConfigurationChanged(); } else if (action.equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) { refreshLocale(); updateAirplaneMode(false); - refreshCarrierLabel(); } else if (action.equals(TelephonyIntents.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED)) { // We are using different subs now, we might be able to make calls. recalculateEmergency(); @@ -419,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,13 @@ public class NetworkControllerImpl extends BroadcastReceiver controller.getState().enabled = show; controller.notifyListeners(); } - refreshCarrierLabel(); + String carrierNetworkChange = args.getString("carriernetworkchange"); + if (carrierNetworkChange != null) { + boolean show = carrierNetworkChange.equals("show"); + for (MobileSignalController controller : mMobileSignalControllers.values()) { + controller.setCarrierNetworkChangeMode(show); + } + } } } @@ -821,971 +722,12 @@ 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); - void setMobileDataIndicators(boolean visible, int strengthIcon, int typeIcon, - String contentDescription, String typeContentDescription, boolean isTypeIconWide, - int subId); + void setMobileDataIndicators(boolean visible, int strengthIcon, int darkStrengthIcon, + int typeIcon, String contentDescription, String typeContentDescription, + boolean isTypeIconWide, int subId); void setSubs(List<SubscriptionInfo> subs); void setNoSims(boolean show); 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..5d89e2f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PreviewInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PreviewInflater.java @@ -16,16 +16,17 @@ package com.android.systemui.statusbar.policy; +import android.content.ComponentName; 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; import com.android.internal.widget.LockPatternUtils; +import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.statusbar.phone.KeyguardPreviewContainer; import java.util.List; @@ -80,13 +81,13 @@ public class PreviewInflater { WidgetInfo info = new WidgetInfo(); PackageManager packageManager = mContext.getPackageManager(); final List<ResolveInfo> appList = packageManager.queryIntentActivitiesAsUser( - intent, PackageManager.MATCH_DEFAULT_ONLY, mLockPatternUtils.getCurrentUser()); + intent, PackageManager.MATCH_DEFAULT_ONLY, KeyguardUpdateMonitor.getCurrentUser()); if (appList.size() == 0) { return null; } ResolveInfo resolved = packageManager.resolveActivityAsUser(intent, PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_META_DATA, - mLockPatternUtils.getCurrentUser()); + KeyguardUpdateMonitor.getCurrentUser()); if (wouldLaunchResolverActivity(resolved, appList)) { return null; } @@ -107,15 +108,28 @@ public class PreviewInflater { public static boolean wouldLaunchResolverActivity(Context ctx, Intent intent, int currentUserId) { + return getTargetPackage(ctx, intent, currentUserId) == null; + } + + /** + * @return the target package of the intent it resolves to a specific package or {@code null} if + * it resolved to the resolver activity + */ + public static String getTargetPackage(Context ctx, Intent intent, + int currentUserId) { PackageManager packageManager = ctx.getPackageManager(); final List<ResolveInfo> appList = packageManager.queryIntentActivitiesAsUser( intent, PackageManager.MATCH_DEFAULT_ONLY, currentUserId); if (appList.size() == 0) { - return false; + return null; } ResolveInfo resolved = packageManager.resolveActivityAsUser(intent, PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_META_DATA, currentUserId); - return wouldLaunchResolverActivity(resolved, appList); + if (resolved == null || wouldLaunchResolverActivity(resolved, appList)) { + return null; + } else { + return resolved.activityInfo.packageName; + } } private static boolean wouldLaunchResolverActivity( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java new file mode 100644 index 0000000..7d721c2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.policy; + +import com.android.systemui.R; + +import android.annotation.NonNull; +import android.app.Notification; +import android.app.PendingIntent; +import android.app.RemoteInput; +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.util.AttributeSet; +import android.util.Log; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.ProgressBar; +import android.widget.TextView; + +/** + * Host for the remote input. + */ +public class RemoteInputView extends FrameLayout implements View.OnClickListener { + + private static final String TAG = "RemoteInput"; + + private RemoteEditText mEditText; + private ProgressBar mProgressBar; + private PendingIntent mPendingIntent; + private RemoteInput mRemoteInput; + private Notification.Action mAction; + + public RemoteInputView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + mProgressBar = (ProgressBar) findViewById(R.id.remote_input_progress); + + mEditText = (RemoteEditText) getChildAt(0); + mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + + // Check if this was the result of hitting the enter key + final boolean isSoftImeEvent = event == null + && (actionId == EditorInfo.IME_ACTION_DONE + || actionId == EditorInfo.IME_ACTION_NEXT + || actionId == EditorInfo.IME_ACTION_SEND); + final boolean isKeyboardEnterKey = event != null + && KeyEvent.isConfirmKey(event.getKeyCode()) + && event.getAction() == KeyEvent.ACTION_DOWN; + + if (isSoftImeEvent || isKeyboardEnterKey) { + sendRemoteInput(); + return true; + } + return false; + } + }); + mEditText.setOnClickListener(this); + mEditText.setInnerFocusable(false); + } + + private void sendRemoteInput() { + Bundle results = new Bundle(); + results.putString(mRemoteInput.getResultKey(), mEditText.getText().toString()); + Intent fillInIntent = new Intent(); + RemoteInput.addResultsToIntent(mAction.getRemoteInputs(), fillInIntent, + results); + + mEditText.setEnabled(false); + mProgressBar.setVisibility(VISIBLE); + + try { + mPendingIntent.send(mContext, 0, fillInIntent); + } catch (PendingIntent.CanceledException e) { + Log.i(TAG, "Unable to send remote input result", e); + } + } + + public static RemoteInputView inflate(Context context, ViewGroup root, + Notification.Action action, RemoteInput remoteInput) { + RemoteInputView v = (RemoteInputView) + LayoutInflater.from(context).inflate(R.layout.remote_input, root, false); + + v.mEditText.setHint(action.title); + v.mPendingIntent = action.actionIntent; + v.mRemoteInput = remoteInput; + v.mAction = action; + + return v; + } + + @Override + public void onClick(View v) { + if (v == mEditText) { + if (!mEditText.isFocusable()) { + mEditText.setInnerFocusable(true); + InputMethodManager imm = InputMethodManager.getInstance(); + if (imm != null) { + imm.viewClicked(mEditText); + imm.showSoftInput(mEditText, 0); + } + } + } + } + + /** + * An EditText that changes appearance based on whether it's focusable and becomes + * un-focusable whenever the user navigates away from it or it becomes invisible. + */ + public static class RemoteEditText extends EditText { + + private final Drawable mBackground; + + public RemoteEditText(Context context, AttributeSet attrs) { + super(context, attrs); + mBackground = getBackground(); + } + + private void defocusIfNeeded() { + if (isFocusable() && isEnabled()) { + setInnerFocusable(false); + } + } + + @Override + protected void onVisibilityChanged(View changedView, int visibility) { + super.onVisibilityChanged(changedView, visibility); + + if (!isShown()) { + defocusIfNeeded(); + } + } + + @Override + protected void onFocusLost() { + super.onFocusLost(); + defocusIfNeeded(); + } + + @Override + public boolean onKeyPreIme(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + defocusIfNeeded(); + } + return super.onKeyPreIme(keyCode, event); + } + + + void setInnerFocusable(boolean focusable) { + setFocusableInTouchMode(focusable); + setFocusable(focusable); + setCursorVisible(focusable); + + if (focusable) { + requestFocus(); + setBackground(mBackground); + } else { + setBackground(null); + } + + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java index 6148feb..e1e022d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java @@ -22,10 +22,6 @@ public interface SecurityController { String getDeviceOwnerName(); String getProfileOwnerName(); boolean isVpnEnabled(); - String getVpnApp(); - boolean isLegacyVpn(); - String getLegacyVpnName(); - void disconnectFromVpn(); void onUserSwitched(int newUserId); void addCallback(SecurityControllerCallback callback); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java index f0dd943..4f47cc6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java @@ -19,6 +19,7 @@ import android.app.ActivityManager; import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.UserInfo; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.IConnectivityManager; @@ -27,10 +28,14 @@ import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.UserHandle; +import android.os.UserManager; import android.text.TextUtils; import android.util.Log; +import android.util.SparseArray; import com.android.internal.net.VpnConfig; +import com.android.internal.net.VpnInfo; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -50,15 +55,13 @@ public class SecurityControllerImpl implements SecurityController { private final Context mContext; private final ConnectivityManager mConnectivityManager; - private final IConnectivityManager mConnectivityService = IConnectivityManager.Stub.asInterface( - ServiceManager.getService(Context.CONNECTIVITY_SERVICE)); + private final IConnectivityManager mConnectivityManagerService; private final DevicePolicyManager mDevicePolicyManager; + private final UserManager mUserManager; private final ArrayList<SecurityControllerCallback> mCallbacks = new ArrayList<SecurityControllerCallback>(); - private VpnConfig mVpnConfig; - private String mVpnName; - private int mCurrentVpnNetworkId = NO_NETWORK; + private SparseArray<Boolean> mCurrentVpnUsers = new SparseArray<>(); private int mCurrentUserId; public SecurityControllerImpl(Context context) { @@ -67,6 +70,10 @@ public class SecurityControllerImpl implements SecurityController { context.getSystemService(Context.DEVICE_POLICY_SERVICE); mConnectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + mConnectivityManagerService = IConnectivityManager.Stub.asInterface( + ServiceManager.getService(Context.CONNECTIVITY_SERVICE)); + mUserManager = (UserManager) + context.getSystemService(Context.USER_SERVICE); // TODO: re-register network callback on user change. mConnectivityManager.registerNetworkCallback(REQUEST, mNetworkCallback); @@ -75,9 +82,7 @@ public class SecurityControllerImpl implements SecurityController { public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("SecurityController state:"); - pw.print(" mCurrentVpnNetworkId="); pw.println(mCurrentVpnNetworkId); - pw.print(" mVpnConfig="); pw.println(mVpnConfig); - pw.print(" mVpnName="); pw.println(mVpnName); + pw.print(" mCurrentVpnUsers=" + mCurrentVpnUsers); } @Override @@ -86,56 +91,33 @@ public class SecurityControllerImpl implements SecurityController { } @Override - public boolean hasProfileOwner() { - return !TextUtils.isEmpty(mDevicePolicyManager.getProfileOwnerNameAsUser(mCurrentUserId)); - } - - @Override public String getDeviceOwnerName() { return mDevicePolicyManager.getDeviceOwnerName(); } @Override - public String getProfileOwnerName() { - return mDevicePolicyManager.getProfileOwnerNameAsUser(mCurrentUserId); - } - - - @Override - public boolean isVpnEnabled() { - return mCurrentVpnNetworkId != NO_NETWORK; - } - - @Override - public boolean isLegacyVpn() { - return mVpnConfig.legacy; - } - - @Override - public String getVpnApp() { - return mVpnName; + public boolean hasProfileOwner() { + boolean result = false; + for (UserInfo profile : mUserManager.getProfiles(mCurrentUserId)) { + result |= (mDevicePolicyManager.getProfileOwnerAsUser(profile.id) != null); + } + return result; } @Override - public String getLegacyVpnName() { - return mVpnConfig.session; + public String getProfileOwnerName() { + for (UserInfo profile : mUserManager.getProfiles(mCurrentUserId)) { + String name = mDevicePolicyManager.getProfileOwnerNameAsUser(profile.id); + if (name != null) { + return name; + } + } + return null; } @Override - public void disconnectFromVpn() { - try { - if (isLegacyVpn()) { - mConnectivityService.prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN); - } else { - // Prevent this app from initiating VPN connections in the future without user - // intervention. - mConnectivityService.setVpnPackageAuthorization(false); - - mConnectivityService.prepareVpn(mVpnConfig.user, VpnConfig.LEGACY_VPN); - } - } catch (Exception e) { - Log.e(TAG, "Unable to disconnect from VPN", e); - } + public boolean isVpnEnabled() { + return mCurrentVpnUsers.get(mCurrentUserId) != null; } @Override @@ -158,14 +140,6 @@ public class SecurityControllerImpl implements SecurityController { fireCallbacks(); } - private void setCurrentNetid(int netId) { - if (netId != mCurrentVpnNetworkId) { - mCurrentVpnNetworkId = netId; - updateState(); - fireCallbacks(); - } - } - private void fireCallbacks() { for (SecurityControllerCallback callback : mCallbacks) { callback.onStateChanged(); @@ -173,27 +147,30 @@ public class SecurityControllerImpl implements SecurityController { } private void updateState() { + // Find all users with an active VPN + SparseArray<Boolean> vpnUsers = new SparseArray<>(); try { - mVpnConfig = mConnectivityService.getVpnConfig(); + for (VpnInfo vpn : mConnectivityManagerService.getAllVpnInfo()) { + UserInfo user = mUserManager.getUserInfo(UserHandle.getUserId(vpn.ownerUid)); + int groupId = (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID ? + user.profileGroupId : user.id); - if (mVpnConfig != null && !mVpnConfig.legacy) { - mVpnName = VpnConfig.getVpnLabel(mContext, mVpnConfig.user).toString(); + vpnUsers.put(groupId, Boolean.TRUE); } - } catch (RemoteException | NameNotFoundException e) { - Log.w(TAG, "Unable to get current VPN", e); + } catch (RemoteException rme) { + // Roll back to previous state + Log.e(TAG, "Unable to list active VPNs", rme); + return; } + mCurrentVpnUsers = vpnUsers; } private final NetworkCallback mNetworkCallback = new NetworkCallback() { @Override public void onAvailable(Network network) { - NetworkCapabilities networkCapabilities = - mConnectivityManager.getNetworkCapabilities(network); - if (DEBUG) Log.d(TAG, "onAvailable " + network.netId + " : " + networkCapabilities); - if (networkCapabilities != null && - networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) { - setCurrentNetid(network.netId); - } + if (DEBUG) Log.d(TAG, "onAvailable " + network.netId); + updateState(); + fireCallbacks(); }; // TODO Find another way to receive VPN lost. This may be delayed depending on @@ -201,9 +178,8 @@ public class SecurityControllerImpl implements SecurityController { @Override public void onLost(Network network) { if (DEBUG) Log.d(TAG, "onLost " + network.netId); - if (mCurrentVpnNetworkId == network.netId) { - setCurrentNetid(NO_NETWORK); - } + updateState(); + fireCallbacks(); }; }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java new file mode 100644 index 0000000..c204814 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java @@ -0,0 +1,342 @@ +/* + * 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() { + return getCurrentIconId(true /* light */); + } + + protected int getCurrentIconId(boolean light) { + if (mCurrentState.connected) { + if (light) { + return getIcons().mSbIcons[mCurrentState.inetCondition][mCurrentState.level]; + } else { + return getIcons().mSbDarkIcons[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[][] mSbDarkIcons; + final int[][] mQsIcons; + final int[] mContentDesc; + final int mSbNullState; + final int mQsNullState; + final int mSbDiscState; + final int mSbDarkDiscState; + 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) { + this(name, sbIcons, sbIcons, qsIcons, contentDesc, sbNullState, qsNullState, + sbDiscState, sbDiscState, qsDiscState, discContentDesc); + } + + public IconGroup(String name, int[][] sbIcons, int[][] sbDarkIcons, int[][] qsIcons, + int[] contentDesc, int sbNullState, int qsNullState, int sbDiscState, + int sbDarkDiscState, int qsDiscState, int discContentDesc) { + mName = name; + mSbIcons = sbIcons; + mSbDarkIcons = sbDarkIcons; + mQsIcons = qsIcons; + mContentDesc = contentDesc; + mSbNullState = sbNullState; + mQsNullState = qsNullState; + mSbDiscState = sbDiscState; + mSbDarkDiscState = sbDarkDiscState; + 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..053feb12 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.policy; import com.android.systemui.R; -import com.android.systemui.statusbar.policy.NetworkControllerImpl.MobileSignalController.MobileIconGroup; +import com.android.systemui.statusbar.policy.MobileSignalController.MobileIconGroup; class TelephonyIcons { //***** Signal strength icons @@ -68,6 +68,42 @@ class TelephonyIcons { R.drawable.stat_sys_signal_4_fully } }; + //CarrierNetworkChange + static final int[][] TELEPHONY_CARRIER_NETWORK_CHANGE = { + { R.drawable.stat_sys_signal_carrier_network_change_animation, + R.drawable.stat_sys_signal_carrier_network_change_animation, + R.drawable.stat_sys_signal_carrier_network_change_animation, + R.drawable.stat_sys_signal_carrier_network_change_animation }, + { R.drawable.stat_sys_signal_carrier_network_change_animation, + R.drawable.stat_sys_signal_carrier_network_change_animation, + R.drawable.stat_sys_signal_carrier_network_change_animation, + R.drawable.stat_sys_signal_carrier_network_change_animation } + }; + + static final int[][] TELEPHONY_CARRIER_NETWORK_CHANGE_DARK = { + { R.drawable.stat_sys_signal_dark_carrier_network_change_animation, + R.drawable.stat_sys_signal_dark_carrier_network_change_animation, + R.drawable.stat_sys_signal_dark_carrier_network_change_animation, + R.drawable.stat_sys_signal_dark_carrier_network_change_animation }, + { R.drawable.stat_sys_signal_dark_carrier_network_change_animation, + R.drawable.stat_sys_signal_dark_carrier_network_change_animation, + R.drawable.stat_sys_signal_dark_carrier_network_change_animation, + R.drawable.stat_sys_signal_dark_carrier_network_change_animation } + }; + + static final int[][] QS_TELEPHONY_CARRIER_NETWORK_CHANGE = { + { R.drawable.ic_qs_signal_carrier_network_change_animation, + R.drawable.ic_qs_signal_carrier_network_change_animation, + R.drawable.ic_qs_signal_carrier_network_change_animation, + R.drawable.ic_qs_signal_carrier_network_change_animation, + R.drawable.ic_qs_signal_carrier_network_change_animation }, + { R.drawable.ic_qs_signal_carrier_network_change_animation, + R.drawable.ic_qs_signal_carrier_network_change_animation, + R.drawable.ic_qs_signal_carrier_network_change_animation, + R.drawable.ic_qs_signal_carrier_network_change_animation, + R.drawable.ic_qs_signal_carrier_network_change_animation } + }; + static final int[] QS_DATA_R = { R.drawable.ic_qs_signal_r, R.drawable.ic_qs_signal_r @@ -202,11 +238,34 @@ class TelephonyIcons { static final int ICON_3G = R.drawable.stat_sys_data_fully_connected_3g; static final int ICON_4G = R.drawable.stat_sys_data_fully_connected_4g; static final int ICON_1X = R.drawable.stat_sys_data_fully_connected_1x; + static final int ICON_CARRIER_NETWORK_CHANGE = + R.drawable.stat_sys_signal_carrier_network_change_animation; + static final int ICON_CARRIER_NETWORK_CHANGE_DARK = + R.drawable.stat_sys_signal_dark_carrier_network_change_animation; static final int QS_ICON_LTE = R.drawable.ic_qs_signal_lte; static final int QS_ICON_3G = R.drawable.ic_qs_signal_3g; static final int QS_ICON_4G = R.drawable.ic_qs_signal_4g; static final int QS_ICON_1X = R.drawable.ic_qs_signal_1x; + static final int QS_ICON_CARRIER_NETWORK_CHANGE = + R.drawable.ic_qs_signal_carrier_network_change_animation; + + static final MobileIconGroup CARRIER_NETWORK_CHANGE = new MobileIconGroup( + "CARRIER_NETWORK_CHANGE", + TelephonyIcons.TELEPHONY_CARRIER_NETWORK_CHANGE, + TelephonyIcons.TELEPHONY_CARRIER_NETWORK_CHANGE_DARK, + TelephonyIcons.QS_TELEPHONY_CARRIER_NETWORK_CHANGE, + AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH, + 0, 0, + TelephonyIcons.ICON_CARRIER_NETWORK_CHANGE, + TelephonyIcons.ICON_CARRIER_NETWORK_CHANGE_DARK, + TelephonyIcons.QS_ICON_CARRIER_NETWORK_CHANGE, + AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0], + R.string.accessibility_carrier_network_change_mode, + 0, + false, + null + ); static final MobileIconGroup THREE_G = new MobileIconGroup( "3G", diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index 4ac41a1..194bcfa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -19,6 +19,9 @@ package com.android.systemui.statusbar.policy; import android.app.ActivityManager; import android.app.ActivityManagerNative; import android.app.Dialog; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; @@ -63,6 +66,7 @@ public class UserSwitcherController { private static final boolean DEBUG = false; private static final String SIMPLE_USER_SWITCHER_GLOBAL_SETTING = "lockscreenSimpleUserSwitcher"; + private static final String ACTION_REMOVE_GUEST = "com.android.systemui.REMOVE_GUEST"; private final Context mContext; private final UserManager mUserManager; @@ -89,6 +93,7 @@ public class UserSwitcherController { filter.addAction(Intent.ACTION_USER_INFO_CHANGED); filter.addAction(Intent.ACTION_USER_SWITCHED); filter.addAction(Intent.ACTION_USER_STOPPING); + filter.addAction(ACTION_REMOVE_GUEST); mContext.registerReceiverAsUser(mReceiver, UserHandle.OWNER, filter, null /* permission */, null /* scheduler */); @@ -296,6 +301,22 @@ public class UserSwitcherController { Log.v(TAG, "Broadcast: a=" + intent.getAction() + " user=" + intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)); } + if (ACTION_REMOVE_GUEST.equals(intent.getAction())) { + int currentUser = ActivityManager.getCurrentUser(); + UserInfo userInfo = mUserManager.getUserInfo(currentUser); + if (userInfo != null && userInfo.isGuest()) { + showExitGuestDialog(currentUser); + } + return; + } + if (Intent.ACTION_USER_ADDED.equals(intent.getAction())) { + final int currentId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); + UserInfo userInfo = mUserManager.getUserInfo(currentId); + if (userInfo != null && userInfo.isGuest()) { + showGuestNotification(currentId); + } + } + if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) { if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) { mExitGuestDialog.cancel(); @@ -329,6 +350,24 @@ public class UserSwitcherController { } refreshUsers(forcePictureLoadForId); } + + private void showGuestNotification(int guestUserId) { + PendingIntent removeGuestPI = PendingIntent.getBroadcastAsUser(mContext, + 0, new Intent(ACTION_REMOVE_GUEST), 0, UserHandle.OWNER); + Notification notification = new Notification.Builder(mContext) + .setVisibility(Notification.VISIBILITY_SECRET) + .setPriority(Notification.PRIORITY_MIN) + .setSmallIcon(R.drawable.ic_person) + .setContentTitle(mContext.getString(R.string.guest_notification_title)) + .setContentText(mContext.getString(R.string.guest_notification_text)) + .setShowWhen(false) + .addAction(R.drawable.ic_delete, + mContext.getString(R.string.guest_notification_remove_action), + removeGuestPI) + .build(); + NotificationManager.from(mContext).notifyAsUser(null, 0, notification, + new UserHandle(guestUserId)); + } }; private final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) { 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..9d84a85 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java @@ -17,27 +17,34 @@ package com.android.systemui.statusbar.policy; import android.content.ComponentName; +import android.net.Uri; import android.service.notification.Condition; +import android.service.notification.ZenModeConfig; +import android.service.notification.ZenModeConfig.ZenRule; public interface ZenModeController { void addCallback(Callback callback); void removeCallback(Callback callback); - void setZen(int zen); + void setZen(int zen, Uri conditionId, String reason); int getZen(); void requestConditions(boolean request); - void setExitCondition(Condition exitCondition); - Condition getExitCondition(); + ZenRule getManualRule(); + ZenModeConfig getConfig(); long getNextAlarm(); void setUserId(int userId); boolean isZenAvailable(); ComponentName getEffectsSuppressor(); + boolean isCountdownConditionSupported(); + int getCurrentUser(); public static class Callback { public void onZenChanged(int zen) {} - public void onExitConditionChanged(Condition exitCondition) {} public void onConditionsChanged(Condition[] conditions) {} public void onNextAlarmChanged() {} public void onZenAvailableChanged(boolean available) {} public void onEffectsSupressorChanged() {} + public void onManualRuleChanged(ZenRule rule) {} + public void onConfigChanged(ZenModeConfig config) {} } + }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java index 37ed7d8..5b80ac2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java @@ -16,8 +16,8 @@ package com.android.systemui.statusbar.policy; +import android.app.ActivityManager; import android.app.AlarmManager; -import android.app.INotificationManager; import android.app.NotificationManager; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -28,14 +28,13 @@ 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; import android.service.notification.Condition; import android.service.notification.IConditionListener; import android.service.notification.ZenModeConfig; +import android.service.notification.ZenModeConfig.ZenRule; import android.util.Log; import android.util.Slog; @@ -43,6 +42,7 @@ import com.android.systemui.qs.GlobalSetting; import java.util.ArrayList; import java.util.LinkedHashMap; +import java.util.Objects; /** Platform implementation of the zen mode controller. **/ public class ZenModeControllerImpl implements ZenModeController { @@ -53,7 +53,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; @@ -61,6 +61,7 @@ public class ZenModeControllerImpl implements ZenModeController { private int mUserId; private boolean mRequesting; private boolean mRegistered; + private ZenModeConfig mConfig; public ZenModeControllerImpl(Context context, Handler handler) { mContext = context; @@ -73,13 +74,13 @@ public class ZenModeControllerImpl implements ZenModeController { mConfigSetting = new GlobalSetting(mContext, handler, Global.ZEN_MODE_CONFIG_ETAG) { @Override protected void handleValueChanged(int value) { - fireExitConditionChanged(); + updateZenModeConfig(); } }; + mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + mConfig = mNoMan.getZenModeConfig(); mModeSetting.setListening(true); mConfigSetting.setListening(true); - mNoMan = INotificationManager.Stub.asInterface( - ServiceManager.getService(Context.NOTIFICATION_SERVICE)); mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); mSetupObserver = new SetupObserver(handler); mSetupObserver.register(); @@ -101,8 +102,8 @@ public class ZenModeControllerImpl implements ZenModeController { } @Override - public void setZen(int zen) { - mModeSetting.setValue(zen); + public void setZen(int zen, Uri conditionId, String reason) { + mNoMan.setZenMode(zen, conditionId, reason); } @Override @@ -113,36 +114,20 @@ public class ZenModeControllerImpl implements ZenModeController { @Override public void requestConditions(boolean request) { mRequesting = request; - try { - mNoMan.requestZenModeConditions(mListener, request ? Condition.FLAG_RELEVANT_NOW : 0); - } catch (RemoteException e) { - // noop - } + mNoMan.requestZenModeConditions(mListener, request ? Condition.FLAG_RELEVANT_NOW : 0); if (!mRequesting) { mConditions.clear(); } } @Override - public void setExitCondition(Condition exitCondition) { - try { - mNoMan.setZenModeCondition(exitCondition); - } catch (RemoteException e) { - // noop - } + public ZenRule getManualRule() { + return mConfig == null ? null : mConfig.manualRule; } @Override - public Condition getExitCondition() { - try { - final ZenModeConfig config = mNoMan.getZenModeConfig(); - if (config != null) { - return config.exitCondition; - } - } catch (RemoteException e) { - // noop - } - return null; + public ZenModeConfig getConfig() { + return mConfig; } @Override @@ -169,6 +154,17 @@ public class ZenModeControllerImpl implements ZenModeController { return NotificationManager.from(mContext).getEffectsSuppressor(); } + @Override + public boolean isCountdownConditionSupported() { + return NotificationManager.from(mContext) + .isSystemConditionProviderEnabled(ZenModeConfig.COUNTDOWN_PATH); + } + + @Override + public int getCurrentUser() { + return ActivityManager.getCurrentUser(); + } + private void fireNextAlarmChanged() { for (Callback cb : mCallbacks) { cb.onNextAlarmChanged(); @@ -199,11 +195,15 @@ public class ZenModeControllerImpl implements ZenModeController { } } - private void fireExitConditionChanged() { - final Condition exitCondition = getExitCondition(); - if (DEBUG) Slog.d(TAG, "exitCondition changed: " + exitCondition); + private void fireManualRuleChanged(ZenRule rule) { + for (Callback cb : mCallbacks) { + cb.onManualRuleChanged(rule); + } + } + + private void fireConfigChanged(ZenModeConfig config) { for (Callback cb : mCallbacks) { - cb.onExitConditionChanged(exitCondition); + cb.onConfigChanged(config); } } @@ -217,6 +217,17 @@ public class ZenModeControllerImpl implements ZenModeController { mConditions.values().toArray(new Condition[mConditions.values().size()])); } + private void updateZenModeConfig() { + final ZenModeConfig config = mNoMan.getZenModeConfig(); + if (Objects.equals(config, mConfig)) return; + final ZenRule oldRule = mConfig != null ? mConfig.manualRule : null; + mConfig = config; + fireConfigChanged(config); + final ZenRule newRule = config != null ? config.manualRule : null; + if (Objects.equals(oldRule, newRule)) return; + fireManualRuleChanged(newRule); + } + private final IConditionListener mListener = new IConditionListener.Stub() { @Override public void onConditionsReceived(Condition[] conditions) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java index 8e677f1..4a7ea96 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java @@ -17,7 +17,10 @@ package com.android.systemui.statusbar.stack; import android.view.View; + import com.android.systemui.statusbar.ActivatableNotificationView; +import com.android.systemui.statusbar.ExpandableNotificationRow; +import com.android.systemui.statusbar.policy.HeadsUpManager; import java.util.ArrayList; @@ -34,6 +37,12 @@ public class AmbientState { private int mSpeedBumpIndex = -1; private boolean mDark; private boolean mHideSensitive; + private HeadsUpManager mHeadsUpManager; + private float mStackTranslation; + private int mLayoutHeight; + private int mTopPadding; + private boolean mShadeExpanded; + private float mMaxHeadsUpTranslation; public int getScrollY() { return mScrollY; @@ -115,4 +124,63 @@ public class AmbientState { public void setSpeedBumpIndex(int speedBumpIndex) { mSpeedBumpIndex = speedBumpIndex; } + + public void setHeadsUpManager(HeadsUpManager headsUpManager) { + mHeadsUpManager = headsUpManager; + } + + public float getStackTranslation() { + return mStackTranslation; + } + + public void setStackTranslation(float stackTranslation) { + mStackTranslation = stackTranslation; + } + + public int getLayoutHeight() { + return mLayoutHeight; + } + + public void setLayoutHeight(int layoutHeight) { + mLayoutHeight = layoutHeight; + } + + public float getTopPadding() { + return mTopPadding; + } + + public void setTopPadding(int topPadding) { + mTopPadding = topPadding; + } + + public int getInnerHeight() { + return mLayoutHeight - mTopPadding - getTopHeadsUpPushIn(); + } + + private int getTopHeadsUpPushIn() { + ExpandableNotificationRow topHeadsUpEntry = getTopHeadsUpEntry(); + return topHeadsUpEntry != null ? topHeadsUpEntry.getHeadsUpHeight() + - topHeadsUpEntry.getMinHeight(): 0; + } + + public boolean isShadeExpanded() { + return mShadeExpanded; + } + + public void setShadeExpanded(boolean shadeExpanded) { + mShadeExpanded = shadeExpanded; + } + + public void setMaxHeadsUpTranslation(float maxHeadsUpTranslation) { + mMaxHeadsUpTranslation = maxHeadsUpTranslation; + } + + public float getMaxHeadsUpTranslation() { + return mMaxHeadsUpTranslation; + } + + public ExpandableNotificationRow getTopHeadsUpEntry() { + HeadsUpManager.HeadsUpEntry topEntry = mHeadsUpManager.getTopEntry(); + return topEntry == null ? null : topEntry.entry.row; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/HeadsUpAppearInterpolator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/HeadsUpAppearInterpolator.java new file mode 100644 index 0000000..05c0099 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/HeadsUpAppearInterpolator.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.stack; + +import android.graphics.Path; +import android.view.animation.PathInterpolator; + +/** + * An interpolator specifically designed for the appear animation of heads up notifications. + */ +public class HeadsUpAppearInterpolator extends PathInterpolator { + public HeadsUpAppearInterpolator() { + super(getAppearPath()); + } + + private static Path getAppearPath() { + Path path = new Path(); + path.moveTo(0, 0); + float x1 = 250f; + float x2 = 150f; + float x3 = 100f; + float y1 = 90f; + float y2 = 78f; + float y3 = 80f; + float xTot = (x1 + x2 + x3); + path.cubicTo(x1 * 0.9f / xTot, 0f, + x1 * 0.8f / xTot, y1 / y3, + x1 / xTot , y1 / y3); + path.cubicTo((x1 + x2 * 0.4f) / xTot, y1 / y3, + (x1 + x2 * 0.2f) / xTot, y2 / y3, + (x1 + x2) / xTot, y2 / y3); + path.cubicTo((x1 + x2 + x3 * 0.4f) / xTot, y2 / y3, + (x1 + x2 + x3 * 0.2f) / xTot, 1f, + 1f, 1f); + return path; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java new file mode 100644 index 0000000..3c9e8cf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java @@ -0,0 +1,406 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.stack; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; + +import com.android.systemui.R; +import com.android.systemui.statusbar.ExpandableNotificationRow; +import com.android.systemui.statusbar.ExpandableView; + +import java.util.ArrayList; +import java.util.List; + +/** + * A container containing child notifications + */ +public class NotificationChildrenContainer extends ViewGroup { + + private final int mChildPadding; + private final int mDividerHeight; + private final int mMaxNotificationHeight; + private final List<View> mDividers = new ArrayList<>(); + private final List<ExpandableNotificationRow> mChildren = new ArrayList<>(); + private final View mCollapseButton; + private final View mCollapseDivider; + private final int mCollapseButtonHeight; + private final int mNotificationAppearDistance; + + public NotificationChildrenContainer(Context context) { + this(context, null); + } + + public NotificationChildrenContainer(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + mChildPadding = getResources().getDimensionPixelSize( + R.dimen.notification_children_padding); + mDividerHeight = getResources().getDimensionPixelSize( + R.dimen.notification_children_divider_height); + mMaxNotificationHeight = getResources().getDimensionPixelSize( + R.dimen.notification_max_height); + mNotificationAppearDistance = getResources().getDimensionPixelSize( + R.dimen.notification_appear_distance); + LayoutInflater inflater = mContext.getSystemService(LayoutInflater.class); + mCollapseButton = inflater.inflate(R.layout.notification_collapse_button, this, + false); + mCollapseButtonHeight = getResources().getDimensionPixelSize( + R.dimen.notification_bottom_decor_height); + addView(mCollapseButton); + mCollapseDivider = inflateDivider(); + addView(mCollapseDivider); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + int childCount = mChildren.size(); + boolean firstChild = true; + for (int i = 0; i < childCount; i++) { + View child = mChildren.get(i); + boolean viewGone = child.getVisibility() == View.GONE; + if (i != 0) { + View divider = mDividers.get(i - 1); + int dividerVisibility = divider.getVisibility(); + int newVisibility = viewGone ? INVISIBLE : VISIBLE; + if (dividerVisibility != newVisibility) { + divider.setVisibility(newVisibility); + } + } + if (viewGone) { + continue; + } + child.layout(0, 0, getWidth(), child.getMeasuredHeight()); + if (!firstChild) { + mDividers.get(i - 1).layout(0, 0, getWidth(), mDividerHeight); + } else { + firstChild = false; + } + } + mCollapseButton.layout(0, 0, getWidth(), mCollapseButtonHeight); + mCollapseDivider.layout(0, mCollapseButtonHeight - mDividerHeight, getWidth(), + mCollapseButtonHeight); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int ownMaxHeight = mMaxNotificationHeight; + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY; + boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST; + if (hasFixedHeight || isHeightLimited) { + int size = MeasureSpec.getSize(heightMeasureSpec); + ownMaxHeight = Math.min(ownMaxHeight, size); + } + int newHeightSpec = MeasureSpec.makeMeasureSpec(ownMaxHeight, MeasureSpec.AT_MOST); + int dividerHeightSpec = MeasureSpec.makeMeasureSpec(mDividerHeight, MeasureSpec.EXACTLY); + int collapseButtonHeightSpec = MeasureSpec.makeMeasureSpec(mCollapseButtonHeight, + MeasureSpec.EXACTLY); + mCollapseButton.measure(widthMeasureSpec, collapseButtonHeightSpec); + mCollapseDivider.measure(widthMeasureSpec, dividerHeightSpec); + int height = mCollapseButtonHeight; + int childCount = mChildren.size(); + boolean firstChild = true; + for (int i = 0; i < childCount; i++) { + View child = mChildren.get(i); + if (child.getVisibility() == View.GONE) { + continue; + } + child.measure(widthMeasureSpec, newHeightSpec); + height += child.getMeasuredHeight(); + if (!firstChild) { + // layout the divider + View divider = mDividers.get(i - 1); + divider.measure(widthMeasureSpec, dividerHeightSpec); + height += mChildPadding; + } else { + firstChild = false; + } + } + int width = MeasureSpec.getSize(widthMeasureSpec); + height = hasFixedHeight ? ownMaxHeight + : isHeightLimited ? Math.min(ownMaxHeight, height) + : height; + setMeasuredDimension(width, height); + } + + /** + * Add a child notification to this view. + * + * @param row the row to add + * @param childIndex the index to add it at, if -1 it will be added at the end + */ + public void addNotification(ExpandableNotificationRow row, int childIndex) { + int newIndex = childIndex < 0 ? mChildren.size() : childIndex; + mChildren.add(newIndex, row); + addView(row); + if (mChildren.size() != 1) { + View divider = inflateDivider(); + addView(divider); + mDividers.add(Math.max(newIndex - 1, 0), divider); + } + // TODO: adapt background corners + // TODO: fix overdraw + } + + public void removeNotification(ExpandableNotificationRow row) { + int childIndex = mChildren.indexOf(row); + mChildren.remove(row); + removeView(row); + if (!mDividers.isEmpty()) { + View divider = mDividers.remove(Math.max(childIndex - 1, 0)); + removeView(divider); + } + row.setSystemChildExpanded(false); + // TODO: adapt background corners + } + + private View inflateDivider() { + return LayoutInflater.from(mContext).inflate( + R.layout.notification_children_divider, this, false); + } + + public List<ExpandableNotificationRow> getNotificationChildren() { + return mChildren; + } + + /** + * Apply the order given in the list to the children. + * + * @param childOrder the new list order + * @return whether the list order has changed + */ + public boolean applyChildOrder(List<ExpandableNotificationRow> childOrder) { + if (childOrder == null) { + return false; + } + boolean result = false; + for (int i = 0; i < mChildren.size() && i < childOrder.size(); i++) { + ExpandableNotificationRow child = mChildren.get(i); + ExpandableNotificationRow desiredChild = childOrder.get(i); + if (child != desiredChild) { + mChildren.remove(desiredChild); + mChildren.add(i, desiredChild); + result = true; + } + } + + // Let's make the first child expanded! + boolean first = true; + for (int i = 0; i < childOrder.size(); i++) { + ExpandableNotificationRow child = childOrder.get(i); + child.setSystemChildExpanded(first); + first = false; + } + return result; + } + + public int getIntrinsicHeight() { + int childCount = mChildren.size(); + int intrinsicHeight = 0; + int visibleChildren = 0; + for (int i = 0; i < childCount; i++) { + ExpandableNotificationRow child = mChildren.get(i); + if (child.getVisibility() == View.GONE) { + continue; + } + intrinsicHeight += child.getIntrinsicHeight(); + visibleChildren++; + } + if (visibleChildren > 0) { + intrinsicHeight += (visibleChildren - 1) * mDividerHeight; + } + return intrinsicHeight; + } + + /** + * Update the state of all its children based on a linear layout algorithm. + * + * @param resultState the state to update + * @param parentState the state of the parent + */ + public void getState(StackScrollState resultState, StackViewState parentState) { + int childCount = mChildren.size(); + int yPosition = mCollapseButtonHeight; + boolean firstChild = true; + for (int i = 0; i < childCount; i++) { + ExpandableNotificationRow child = mChildren.get(i); + if (child.getVisibility() == View.GONE) { + continue; + } + if (!firstChild) { + // There's a divider + yPosition += mChildPadding; + } else { + firstChild = false; + } + StackViewState childState = resultState.getViewStateForView(child); + int intrinsicHeight = child.getIntrinsicHeight(); + childState.yTranslation = yPosition; + childState.zTranslation = 0; + childState.height = intrinsicHeight; + childState.dimmed = parentState.dimmed; + childState.dark = parentState.dark; + childState.hideSensitive = parentState.hideSensitive; + childState.belowSpeedBump = parentState.belowSpeedBump; + childState.scale = parentState.scale; + childState.clipTopAmount = 0; + childState.topOverLap = 0; + childState.location = parentState.location; + yPosition += intrinsicHeight; + } + } + + public void applyState(StackScrollState state) { + int childCount = mChildren.size(); + boolean firstChild = true; + ViewState dividerState = new ViewState(); + for (int i = 0; i < childCount; i++) { + ExpandableNotificationRow child = mChildren.get(i); + StackViewState viewState = state.getViewStateForView(child); + if (child.getVisibility() == View.GONE) { + continue; + } + if (!firstChild) { + // layout the divider + View divider = mDividers.get(i - 1); + dividerState.initFrom(divider); + dividerState.yTranslation = (int) (viewState.yTranslation + - (mChildPadding + mDividerHeight) / 2.0f); + dividerState.alpha = 1; + state.applyViewState(divider, dividerState); + } else { + firstChild = false; + } + state.applyState(child, viewState); + } + } + + public void setCollapseClickListener(OnClickListener collapseClickListener) { + mCollapseButton.setOnClickListener(collapseClickListener); + } + + /** + * This is called when the children expansion has changed and positions the children properly + * for an appear animation. + * + * @param state the new state we animate to + */ + public void prepareExpansionChanged(StackScrollState state) { + int childCount = mChildren.size(); + boolean firstChild = true; + StackViewState sourceState = new StackViewState(); + ViewState dividerState = new ViewState(); + for (int i = 0; i < childCount; i++) { + ExpandableNotificationRow child = mChildren.get(i); + StackViewState viewState = state.getViewStateForView(child); + if (child.getVisibility() == View.GONE) { + continue; + } + if (!firstChild) { + // layout the divider + View divider = mDividers.get(i - 1); + dividerState.initFrom(divider); + dividerState.yTranslation = viewState.yTranslation + - (mChildPadding + mDividerHeight) / 2.0f + mNotificationAppearDistance; + dividerState.alpha = 0; + state.applyViewState(divider, dividerState); + } else { + firstChild = false; + } + sourceState.copyFrom(viewState); + sourceState.alpha = 0; + sourceState.yTranslation += mNotificationAppearDistance; + state.applyState(child, sourceState); + } + mCollapseButton.setAlpha(0); + mCollapseDivider.setAlpha(0); + mCollapseDivider.setTranslationY(mNotificationAppearDistance / 4); + } + + public void startAnimationToState(StackScrollState state, StackStateAnimator stateAnimator, + boolean withDelays, long baseDelay, long duration) { + int childCount = mChildren.size(); + boolean firstChild = true; + ViewState dividerState = new ViewState(); + int notGoneIndex = 0; + for (int i = 0; i < childCount; i++) { + ExpandableNotificationRow child = mChildren.get(i); + StackViewState viewState = state.getViewStateForView(child); + if (child.getVisibility() == View.GONE) { + continue; + } + int difference = Math.min(StackStateAnimator.DELAY_EFFECT_MAX_INDEX_DIFFERENCE_CHILDREN, + notGoneIndex + 1); + long delay = withDelays + ? difference * StackStateAnimator.ANIMATION_DELAY_PER_ELEMENT_EXPAND_CHILDREN + : 0; + delay += baseDelay; + if (!firstChild) { + // layout the divider + View divider = mDividers.get(i - 1); + dividerState.initFrom(divider); + dividerState.yTranslation = viewState.yTranslation + - (mChildPadding + mDividerHeight) / 2.0f; + dividerState.alpha = 1; + stateAnimator.startViewAnimations(divider, dividerState, delay, duration); + } else { + firstChild = false; + } + stateAnimator.startStackAnimations(child, viewState, state, -1, delay); + notGoneIndex++; + } + dividerState.initFrom(mCollapseButton); + dividerState.alpha = 1.0f; + stateAnimator.startViewAnimations(mCollapseButton, dividerState, baseDelay, duration); + dividerState.initFrom(mCollapseDivider); + dividerState.alpha = 1.0f; + dividerState.yTranslation = 0.0f; + stateAnimator.startViewAnimations(mCollapseDivider, dividerState, baseDelay, duration); + } + + public ExpandableNotificationRow getViewAtPosition(float y) { + // find the view under the pointer, accounting for GONE views + final int count = mChildren.size(); + for (int childIdx = 0; childIdx < count; childIdx++) { + ExpandableNotificationRow slidingChild = mChildren.get(childIdx); + float childTop = slidingChild.getTranslationY(); + float top = childTop + slidingChild.getClipTopAmount(); + float bottom = childTop + slidingChild.getActualHeight(); + if (y >= top && y <= bottom) { + return slidingChild; + } + } + return null; + } + + public void setTintColor(int color) { + ExpandableNotificationRow.applyTint(mCollapseDivider, color); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index 6dcbed6..a1b0cae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -24,6 +24,7 @@ import android.graphics.Paint; import android.graphics.PointF; import android.util.AttributeSet; import android.util.Log; +import android.util.Pair; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; @@ -41,12 +42,15 @@ 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.phone.ScrimController; +import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.ScrollAdapter; -import com.android.systemui.statusbar.stack.StackScrollState.ViewState; import java.util.ArrayList; import java.util.HashSet; @@ -56,7 +60,7 @@ import java.util.HashSet; */ public class NotificationStackScrollLayout extends ViewGroup implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter, - ExpandableView.OnHeightChangedListener { + ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener { private static final String TAG = "NotificationStackScrollLayout"; private static final boolean DEBUG = false; @@ -119,15 +123,16 @@ public class NotificationStackScrollLayout extends ViewGroup */ private StackScrollState mCurrentStackScrollState = new StackScrollState(this); private AmbientState mAmbientState = new AmbientState(); - private ArrayList<View> mChildrenToAddAnimated = new ArrayList<View>(); - private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<View>(); - private ArrayList<View> mSnappedBackChildren = new ArrayList<View>(); - private ArrayList<View> mDragAnimPendingChildren = new ArrayList<View>(); - private ArrayList<View> mChildrenChangingPositions = new ArrayList<View>(); + private NotificationGroupManager mGroupManager; + private ArrayList<View> mChildrenToAddAnimated = new ArrayList<>(); + private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>(); + private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<>(); + private ArrayList<View> mSnappedBackChildren = new ArrayList<>(); + private ArrayList<View> mDragAnimPendingChildren = new ArrayList<>(); + private ArrayList<View> mChildrenChangingPositions = new ArrayList<>(); private HashSet<View> mFromMoreCardAdditions = new HashSet<>(); - private ArrayList<AnimationEvent> mAnimationEvents - = new ArrayList<AnimationEvent>(); - private ArrayList<View> mSwipedOutViews = new ArrayList<View>(); + private ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>(); + private ArrayList<View> mSwipedOutViews = new ArrayList<>(); private final StackStateAnimator mStateAnimator = new StackStateAnimator(this); private boolean mAnimationsEnabled; private boolean mChangePositionInProgress; @@ -141,7 +146,6 @@ public class NotificationStackScrollLayout extends ViewGroup * The raw amount of the overScroll on the bottom, which is not rubber-banded. */ private float mOverScrolledBottomPixels; - private OnChildLocationsChangedListener mListener; private OnOverscrollTopChangedListener mOverscrollTopChangedListener; private ExpandableView.OnHeightChangedListener mOnHeightChangedListener; @@ -169,7 +173,6 @@ public class NotificationStackScrollLayout extends ViewGroup * Was the scroller scrolled to the top when the down motion was observed? */ private boolean mScrolledToTopOnFirstDown; - /** * The minimal amount of over scroll which is needed in order to switch to the quick settings * when over scrolling on a expanded card. @@ -177,10 +180,12 @@ public class NotificationStackScrollLayout extends ViewGroup private float mMinTopOverScrollToEscape; private int mIntrinsicPadding; private int mNotificationTopPadding; + private float mStackTranslation; private float mTopPaddingOverflow; private boolean mDontReportNextOverScroll; private boolean mRequestViewResizeAnimationOnLayout; private boolean mNeedViewResizeAnimation; + private View mExpandedGroupView; private boolean mEverythingNeedsAnimation; /** @@ -201,7 +206,6 @@ public class NotificationStackScrollLayout extends ViewGroup private boolean mDelegateToScrollView; private boolean mDisallowScrollingInThisMotion; private long mGoToFullShadeDelay; - private ViewTreeObserver.OnPreDrawListener mChildrenUpdater = new ViewTreeObserver.OnPreDrawListener() { @Override @@ -214,6 +218,13 @@ public class NotificationStackScrollLayout extends ViewGroup }; private PhoneStatusBar mPhoneStatusBar; private int[] mTempInt2 = new int[2]; + private boolean mGenerateChildOrderChangedEvent; + private HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>(); + private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations + = new HashSet<>(); + private HeadsUpManager mHeadsUpManager; + private boolean mTrackingHeadsUp; + private ScrimController mScrimController; public NotificationStackScrollLayout(Context context) { this(context, null); @@ -309,7 +320,7 @@ public class NotificationStackScrollLayout extends ViewGroup private void notifyHeightChangeListener(ExpandableView view) { if (mOnHeightChangedListener != null) { - mOnHeightChangedListener.onHeightChanged(view); + mOnHeightChangedListener.onHeightChanged(view, false /* needsAnimation */); } } @@ -329,6 +340,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 +353,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 +391,15 @@ public class NotificationStackScrollLayout extends ViewGroup * Returns the location the given child is currently rendered at. * * @param child the child to get the location for - * @return one of {@link ViewState}'s <code>LOCATION_*</code> constants + * @return one of {@link StackViewState}'s <code>LOCATION_*</code> constants */ public int getChildLocation(View child) { - ViewState childViewState = mCurrentStackScrollState.getViewStateForView(child); + StackViewState childViewState = mCurrentStackScrollState.getViewStateForView(child); if (childViewState == null) { - return ViewState.LOCATION_UNKNOWN; + return StackViewState.LOCATION_UNKNOWN; } if (childViewState.gone) { - return ViewState.LOCATION_GONE; + return StackViewState.LOCATION_GONE; } return childViewState.location; } @@ -394,8 +410,8 @@ public class NotificationStackScrollLayout extends ViewGroup } private void updateAlgorithmHeightAndPadding() { - mStackScrollAlgorithm.setLayoutHeight(getLayoutHeight()); - mStackScrollAlgorithm.setTopPadding(mTopPadding); + mAmbientState.setLayoutHeight(getLayoutHeight()); + mAmbientState.setTopPadding(mTopPadding); } /** @@ -468,9 +484,13 @@ public class NotificationStackScrollLayout extends ViewGroup int newStackHeight = (int) height; int minStackHeight = getMinStackHeight(); int stackHeight; - if (newStackHeight - mTopPadding - mTopPaddingOverflow >= minStackHeight + float paddingOffset; + boolean trackingHeadsUp = mTrackingHeadsUp; + int normalUnfoldPositionStart = trackingHeadsUp ? mHeadsUpManager.getTopHeadsUpHeight() + : minStackHeight; + if (newStackHeight - mTopPadding - mTopPaddingOverflow >= normalUnfoldPositionStart || getNotGoneChildCount() == 0) { - setTranslationY(mTopPaddingOverflow); + paddingOffset = mTopPaddingOverflow; stackHeight = newStackHeight; } else { @@ -482,9 +502,13 @@ public class NotificationStackScrollLayout extends ViewGroup float partiallyThere = (newStackHeight - mTopPadding - mTopPaddingOverflow) / minStackHeight; partiallyThere = Math.max(0, partiallyThere); - translationY += (1 - partiallyThere) * (mBottomStackPeekSize + - mCollapseSecondCardPadding); - setTranslationY(translationY - mTopPadding); + if (!trackingHeadsUp) { + translationY += (1 - partiallyThere) * (mBottomStackPeekSize + + mCollapseSecondCardPadding); + } else { + translationY = (int) (height - mHeadsUpManager.getTopHeadsUpHeight()); + } + paddingOffset = translationY - mTopPadding; stackHeight = (int) (height - (translationY - mTopPadding)); } if (stackHeight != mCurrentStackHeight) { @@ -492,6 +516,19 @@ public class NotificationStackScrollLayout extends ViewGroup updateAlgorithmHeightAndPadding(); requestChildrenUpdate(); } + setStackTranslation(paddingOffset); + } + + public float getStackTranslation() { + return mStackTranslation; + } + + private void setStackTranslation(float stackTranslation) { + if (stackTranslation != mStackTranslation) { + mStackTranslation = stackTranslation; + mAmbientState.setStackTranslation(stackTranslation); + requestChildrenUpdate(); + } } /** @@ -533,11 +570,6 @@ public class NotificationStackScrollLayout extends ViewGroup if (mDismissAllInProgress) { return; } - if (DEBUG) Log.v(TAG, "onChildDismissed: " + v); - final View veto = v.findViewById(R.id.veto); - if (veto != null && veto.getVisibility() != View.GONE) { - veto.performClick(); - } setSwipingInProgress(false); if (mDragAnimPendingChildren.contains(v)) { // We start the swipe and finish it in the same frame, we don't want any animation @@ -546,6 +578,17 @@ public class NotificationStackScrollLayout extends ViewGroup } mSwipedOutViews.add(v); mAmbientState.onDragFinished(v); + if (v instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) v; + if (row.isHeadsUp()) { + mHeadsUpManager.addSwipedOutNotification(row.getStatusBarNotification().getKey()); + } + } + final View veto = v.findViewById(R.id.veto); + if (veto != null && veto.getVisibility() != View.GONE) { + veto.performClick(); + } + if (DEBUG) Log.v(TAG, "onChildDismissed: " + v); } @Override @@ -565,28 +608,48 @@ public class NotificationStackScrollLayout extends ViewGroup @Override public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) { + if (isPinnedHeadsUp(animView) && canChildBeDismissed(animView)) { + mScrimController.setTopHeadsUpDragAmount(animView, + Math.min(Math.abs(swipeProgress - 1.0f), 1.0f)); + } return false; } - @Override - public float getFalsingThresholdFactor() { - return mPhoneStatusBar.isScreenOnComingFromTouch() ? 1.5f : 1.0f; - } - public void onBeginDrag(View v) { setSwipingInProgress(true); mAmbientState.onBeginDrag(v); - if (mAnimationsEnabled) { + if (mAnimationsEnabled && !isPinnedHeadsUp(v)) { mDragAnimPendingChildren.add(v); mNeedsAnimation = true; } requestChildrenUpdate(); } + public static boolean isPinnedHeadsUp(View v) { + if (v instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) v; + return row.isHeadsUp() && row.isPinned(); + } + return false; + } + + private boolean isHeadsUp(View v) { + if (v instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) v; + return row.isHeadsUp(); + } + return false; + } + public void onDragCancelled(View v) { setSwipingInProgress(false); } + @Override + public float getFalsingThresholdFactor() { + return mPhoneStatusBar.isScreenOnComingFromTouch() ? 1.5f : 1.0f; + } + public View getChildAtPosition(MotionEvent ev) { return getChildAtPosition(ev.getX(), ev.getY()); } @@ -645,6 +708,14 @@ public class NotificationStackScrollLayout extends ViewGroup int right = getWidth(); if (touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) { + if (slidingChild instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild; + if (row.isHeadsUp() && row.isPinned() + && mHeadsUpManager.getTopEntry().entry.row != row) { + continue; + } + return row.getViewAtPosition(touchY - childTop); + } return slidingChild; } } @@ -653,7 +724,8 @@ public class NotificationStackScrollLayout extends ViewGroup public boolean canChildBeExpanded(View v) { return v instanceof ExpandableNotificationRow - && ((ExpandableNotificationRow) v).isExpandable(); + && ((ExpandableNotificationRow) v).isExpandable() + && !((ExpandableNotificationRow) v).isHeadsUp(); } public void setUserExpandedChild(View v, boolean userExpanded) { @@ -723,7 +795,6 @@ public class NotificationStackScrollLayout extends ViewGroup } public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) { - child.setClipBounds(null); mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration); } @@ -740,7 +811,8 @@ public class NotificationStackScrollLayout extends ViewGroup } handleEmptySpaceClick(ev); boolean expandWantsIt = false; - if (!mSwipingInProgress && !mOnlyScrollingInThisMotion && isScrollingEnabled()) { + if (mIsExpanded && !mSwipingInProgress && !mOnlyScrollingInThisMotion + && isScrollingEnabled()) { if (isCancelOrUp) { mExpandHelper.onlyObserveMovements(false); } @@ -752,7 +824,8 @@ public class NotificationStackScrollLayout extends ViewGroup } } boolean scrollerWantsIt = false; - if (!mSwipingInProgress && !mExpandingNotification && !mDisallowScrollingInThisMotion) { + if (mIsExpanded && !mSwipingInProgress && !mExpandingNotification + && !mDisallowScrollingInThisMotion) { scrollerWantsIt = onScrollTouch(ev); } boolean horizontalSwipeWantsIt = false; @@ -1330,12 +1403,9 @@ public class NotificationStackScrollLayout extends ViewGroup // add the padding before this element height += mPaddingBetweenElements; } - if (child instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) child; - height += row.getIntrinsicHeight(); - } else if (child instanceof ExpandableView) { + if (child instanceof ExpandableView) { ExpandableView expandableView = (ExpandableView) child; - height += expandableView.getActualHeight(); + height += expandableView.getIntrinsicHeight(); } } } @@ -1544,6 +1614,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 @@ -1561,7 +1639,13 @@ public class NotificationStackScrollLayout extends ViewGroup updateAnimationState(false, child); // Make sure the clipRect we might have set is removed - child.setClipBounds(null); + ((ExpandableView) child).setClipTopOptimization(0); + } + + private boolean isChildInGroup(View child) { + return child instanceof ExpandableNotificationRow + && mGroupManager.isChildInGroupWithSummary( + ((ExpandableNotificationRow) child).getStatusBarNotification()); } /** @@ -1571,7 +1655,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 +1671,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 +1735,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,22 +1751,31 @@ public class NotificationStackScrollLayout extends ViewGroup } } + public void notifyGroupChildRemoved(View row) { + onViewRemovedInternal(row); + } + + public void notifyGroupChildAdded(View row) { + onViewAddedInternal(row); + } + public void setAnimationsEnabled(boolean animationsEnabled) { mAnimationsEnabled = animationsEnabled; updateNotificationAnimationStates(); } private void updateNotificationAnimationStates() { - boolean running = mIsExpanded && mAnimationsEnabled; + boolean running = mAnimationsEnabled; int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View child = getChildAt(i); + running &= mIsExpanded || isPinnedHeadsUp(child); updateAnimationState(running, child); } } private void updateAnimationState(View child) { - updateAnimationState(mAnimationsEnabled && mIsExpanded, child); + updateAnimationState((mAnimationsEnabled || isPinnedHeadsUp(child)) && mIsExpanded, child); } @@ -1691,6 +1805,10 @@ public class NotificationStackScrollLayout extends ViewGroup } mNeedsAnimation = true; } + if (isHeadsUp(child)) { + mAddedHeadsUpChildren.add(child); + mChildrenToAddAnimated.remove(child); + } } /** @@ -1729,6 +1847,7 @@ public class NotificationStackScrollLayout extends ViewGroup } private void generateChildHierarchyEvents() { + generateHeadsUpAnimationEvents(); generateChildRemovalEvents(); generateChildAdditionEvents(); generatePositionChangeEvents(); @@ -1741,10 +1860,54 @@ public class NotificationStackScrollLayout extends ViewGroup generateDarkEvent(); generateGoToFullShadeEvent(); generateViewResizeEvent(); + generateGroupExpansionEvent(); generateAnimateEverythingEvent(); mNeedsAnimation = false; } + private void generateHeadsUpAnimationEvents() { + for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) { + ExpandableNotificationRow row = eventPair.first; + boolean isHeadsUp = eventPair.second; + int type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER; + boolean onBottom = false; + if (!mIsExpanded && !isHeadsUp) { + type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR; + } else if (mAddedHeadsUpChildren.contains(row) || (row.isPinned() && !mIsExpanded)) { + if (row.isPinned() || shouldHunAppearFromBottom(row)) { + // Our custom add animation + type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR; + } else { + // Normal add animation + type = AnimationEvent.ANIMATION_TYPE_ADD; + } + onBottom = !row.isPinned(); + } + AnimationEvent event = new AnimationEvent(row, type); + event.headsUpFromBottom = onBottom; + mAnimationEvents.add(event); + } + mHeadsUpChangeAnimations.clear(); + mAddedHeadsUpChildren.clear(); + } + + private boolean shouldHunAppearFromBottom(ExpandableNotificationRow row) { + StackViewState viewState = mCurrentStackScrollState.getViewStateForView(row); + if (viewState.yTranslation + viewState.height < mAmbientState.getMaxHeadsUpTranslation()) { + return false; + } + return true; + } + + private void generateGroupExpansionEvent() { + // Generate a group expansion/collapsing event if there is such a group at all + if (mExpandedGroupView != null) { + mAnimationEvents.add(new AnimationEvent(mExpandedGroupView, + AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED)); + mExpandedGroupView = null; + } + } + private void generateViewResizeEvent() { if (mNeedViewResizeAnimation) { mAnimationEvents.add( @@ -1791,6 +1954,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 +2227,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(); } @@ -2103,6 +2274,10 @@ public class NotificationStackScrollLayout extends ViewGroup public void onChildAnimationFinished() { requestChildrenUpdate(); + for (Runnable runnable : mAnimationFinishedRunnables) { + runnable.run(); + } + mAnimationFinishedRunnables.clear(); } /** @@ -2204,7 +2379,7 @@ public class NotificationStackScrollLayout extends ViewGroup * @return the y position of the first notification */ public float getNotificationsTopY() { - return mTopPadding + getTranslationY(); + return mTopPadding + getStackTranslation(); } @Override @@ -2338,6 +2513,20 @@ public class NotificationStackScrollLayout extends ViewGroup public void setDismissAllInProgress(boolean dismissAllInProgress) { mDismissAllInProgress = dismissAllInProgress; mDismissView.setDismissAllInProgress(dismissAllInProgress); + if (dismissAllInProgress) { + disableClipOptimization(); + } + } + + private void disableClipOptimization() { + final int count = getChildCount(); + for (int i = 0; i < count; i++) { + ExpandableView child = (ExpandableView) getChildAt(i); + if (child.getVisibility() == GONE) { + continue; + } + child.setClipTopOptimization(0); + } } public boolean isDismissViewNotGone() { @@ -2377,7 +2566,7 @@ public class NotificationStackScrollLayout extends ViewGroup max = bottom; } } - return max + getTranslationY(); + return max + getStackTranslation(); } /** @@ -2392,31 +2581,140 @@ 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; + } + + 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(); + } + } + + public void runAfterAnimationFinished(Runnable runnable) { + mAnimationFinishedRunnables.add(runnable); + } + + public void setHeadsUpManager(HeadsUpManager headsUpManager) { + mHeadsUpManager = headsUpManager; + mAmbientState.setHeadsUpManager(headsUpManager); + mStackScrollAlgorithm.setHeadsUpManager(headsUpManager); + } + + public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) { + if (mAnimationsEnabled) { + mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp)); + mNeedsAnimation = true; + requestChildrenUpdate(); + } + } + + public void setShadeExpanded(boolean shadeExpanded) { + mAmbientState.setShadeExpanded(shadeExpanded); + mStateAnimator.setShadeExpanded(shadeExpanded); + } + + /** + * Set the boundary for the bottom heads up position. The heads up will always be above this + * position. + * + * @param height the height of the screen + * @param bottomBarHeight the height of the bar on the bottom + */ + public void setHeadsUpBoundaries(int height, int bottomBarHeight) { + mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight); + mStateAnimator.setHeadsUpAppearHeightBottom(height); + requestChildrenUpdate(); + } + + public void setTrackingHeadsUp(boolean trackingHeadsUp) { + mTrackingHeadsUp = trackingHeadsUp; + } + + public void setScrimController(ScrimController scrimController) { + mScrimController = scrimController; + } + /** * A listener that is notified when some child locations might have changed. */ @@ -2553,6 +2851,38 @@ public class NotificationStackScrollLayout extends ViewGroup .animateY() .animateZ(), + // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED + new AnimationFilter() + .animateAlpha() + .animateHeight() + .animateTopInset() + .animateY() + .animateZ(), + + // ANIMATION_TYPE_HEADS_UP_APPEAR + new AnimationFilter() + .animateAlpha() + .animateHeight() + .animateTopInset() + .animateY() + .animateZ(), + + // ANIMATION_TYPE_HEADS_UP_DISAPPEAR + new AnimationFilter() + .animateAlpha() + .animateHeight() + .animateTopInset() + .animateY() + .animateZ(), + + // ANIMATION_TYPE_HEADS_UP_OTHER + new AnimationFilter() + .animateAlpha() + .animateHeight() + .animateTopInset() + .animateY() + .animateZ(), + // ANIMATION_TYPE_EVERYTHING new AnimationFilter() .animateAlpha() @@ -2607,6 +2937,18 @@ public class NotificationStackScrollLayout extends ViewGroup // ANIMATION_TYPE_VIEW_RESIZE StackStateAnimator.ANIMATION_DURATION_STANDARD, + // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED + StackStateAnimator.ANIMATION_DURATION_EXPAND_CLICKED, + + // ANIMATION_TYPE_HEADS_UP_APPEAR + StackStateAnimator.ANIMATION_DURATION_HEADS_UP_APPEAR, + + // ANIMATION_TYPE_HEADS_UP_DISAPPEAR + StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR, + + // ANIMATION_TYPE_HEADS_UP_OTHER + StackStateAnimator.ANIMATION_DURATION_STANDARD, + // ANIMATION_TYPE_EVERYTHING StackStateAnimator.ANIMATION_DURATION_STANDARD, }; @@ -2624,7 +2966,11 @@ public class NotificationStackScrollLayout extends ViewGroup static final int ANIMATION_TYPE_GO_TO_FULL_SHADE = 10; static final int ANIMATION_TYPE_HIDE_SENSITIVE = 11; static final int ANIMATION_TYPE_VIEW_RESIZE = 12; - static final int ANIMATION_TYPE_EVERYTHING = 13; + static final int ANIMATION_TYPE_GROUP_EXPANSION_CHANGED = 13; + static final int ANIMATION_TYPE_HEADS_UP_APPEAR = 14; + static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR = 15; + static final int ANIMATION_TYPE_HEADS_UP_OTHER = 16; + static final int ANIMATION_TYPE_EVERYTHING = 17; static final int DARK_ANIMATION_ORIGIN_INDEX_ABOVE = -1; static final int DARK_ANIMATION_ORIGIN_INDEX_BELOW = -2; @@ -2636,6 +2982,7 @@ public class NotificationStackScrollLayout extends ViewGroup final long length; View viewAfterChangingView; int darkAnimationOriginIndex; + boolean headsUpFromBottom; AnimationEvent(View view, int type) { this(view, type, LENGTHS[type]); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java index ddc4251..202063a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java @@ -25,8 +25,10 @@ import android.view.ViewGroup; import com.android.systemui.R; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.ExpandableView; +import com.android.systemui.statusbar.policy.HeadsUpManager; import java.util.ArrayList; +import java.util.List; /** * The Algorithm of the {@link com.android.systemui.statusbar.stack @@ -53,11 +55,6 @@ public class StackScrollAlgorithm { private StackIndentationFunctor mTopStackIndentationFunctor; private StackIndentationFunctor mBottomStackIndentationFunctor; - private int mLayoutHeight; - - /** mLayoutHeight - mTopPadding */ - private int mInnerHeight; - private int mTopPadding; private StackScrollAlgorithmState mTempAlgorithmState = new StackScrollAlgorithmState(); private boolean mIsExpansionChanging; private int mFirstChildMaxHeight; @@ -73,6 +70,7 @@ public class StackScrollAlgorithm { private boolean mIsSmallScreen; private int mMaxNotificationHeight; private boolean mScaleDimmed; + private HeadsUpManager mHeadsUpManager; public StackScrollAlgorithm(Context context) { initConstants(context); @@ -159,18 +157,31 @@ public class StackScrollAlgorithm { updateVisibleChildren(resultState, algorithmState); // Phase 1: - findNumberOfItemsInTopStackAndUpdateState(resultState, algorithmState); + findNumberOfItemsInTopStackAndUpdateState(resultState, algorithmState, ambientState); // Phase 2: - updatePositionsForState(resultState, algorithmState); + updatePositionsForState(resultState, algorithmState, ambientState); // Phase 3: updateZValuesForState(resultState, algorithmState); handleDraggedViews(ambientState, resultState, algorithmState); updateDimmedActivatedHideSensitive(ambientState, resultState, algorithmState); - updateClipping(resultState, algorithmState); + updateClipping(resultState, algorithmState, ambientState); updateSpeedBumpState(resultState, algorithmState, ambientState.getSpeedBumpIndex()); + getNotificationChildrenStates(resultState, algorithmState); + } + + private void getNotificationChildrenStates(StackScrollState resultState, + StackScrollAlgorithmState algorithmState) { + int childCount = algorithmState.visibleChildren.size(); + for (int i = 0; i < childCount; i++) { + ExpandableView v = algorithmState.visibleChildren.get(i); + if (v instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) v; + row.getChildrenStates(resultState); + } + } } private void updateSpeedBumpState(StackScrollState resultState, @@ -178,7 +189,7 @@ public class StackScrollAlgorithm { int childCount = algorithmState.visibleChildren.size(); for (int i = 0; i < childCount; i++) { View child = algorithmState.visibleChildren.get(i); - StackScrollState.ViewState childViewState = resultState.getViewStateForView(child); + StackViewState childViewState = resultState.getViewStateForView(child); // The speed bump can also be gone, so equality needs to be taken when comparing // indices. @@ -187,14 +198,14 @@ public class StackScrollAlgorithm { } private void updateClipping(StackScrollState resultState, - StackScrollAlgorithmState algorithmState) { + StackScrollAlgorithmState algorithmState, AmbientState ambientState) { float previousNotificationEnd = 0; float previousNotificationStart = 0; boolean previousNotificationIsSwiped = false; int childCount = algorithmState.visibleChildren.size(); for (int i = 0; i < childCount; i++) { ExpandableView child = algorithmState.visibleChildren.get(i); - StackScrollState.ViewState state = resultState.getViewStateForView(child); + StackViewState state = resultState.getViewStateForView(child); float newYTranslation = state.yTranslation + state.height * (1f - state.scale) / 2f; float newHeight = state.height * state.scale; // apply clipping and shadow @@ -228,7 +239,7 @@ public class StackScrollAlgorithm { // otherwise we would clip to a transparent view. previousNotificationStart = newYTranslation + state.clipTopAmount * state.scale; previousNotificationEnd = newNotificationEnd; - previousNotificationIsSwiped = child.getTranslationX() != 0; + previousNotificationIsSwiped = ambientState.getDraggedViews().contains(child); } } } @@ -242,8 +253,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 +281,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 +308,17 @@ public class StackScrollAlgorithm { if (!draggedViews.contains(nextChild)) { // only if the view is not dragged itself we modify its state to be fully // visible - StackScrollState.ViewState viewState = resultState.getViewStateForView( + StackViewState viewState = resultState.getViewStateForView( nextChild); // The child below the dragged one must be fully visible - viewState.alpha = 1; + if (!NotificationStackScrollLayout.isPinnedHeadsUp(draggedView) + || NotificationStackScrollLayout.isPinnedHeadsUp(nextChild)) { + viewState.alpha = 1; + } } // Lets set the alpha to the one it currently has, as its currently being dragged - StackScrollState.ViewState viewState = resultState.getViewStateForView(draggedView); + StackViewState viewState = resultState.getViewStateForView(draggedView); // The dragged child should keep the set alpha viewState.alpha = draggedView.getAlpha(); } @@ -320,27 +334,54 @@ public class StackScrollAlgorithm { int childCount = hostView.getChildCount(); state.visibleChildren.clear(); state.visibleChildren.ensureCapacity(childCount); + int notGoneIndex = 0; for (int i = 0; i < childCount; i++) { ExpandableView v = (ExpandableView) hostView.getChildAt(i); if (v.getVisibility() != View.GONE) { - StackScrollState.ViewState viewState = resultState.getViewStateForView(v); - viewState.notGoneIndex = state.visibleChildren.size(); - state.visibleChildren.add(v); + notGoneIndex = updateNotGoneIndex(resultState, state, notGoneIndex, v); + if (v instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) v; + + // handle the notgoneIndex for the children as well + List<ExpandableNotificationRow> children = + row.getNotificationChildren(); + if (row.areChildrenExpanded() && children != null) { + for (ExpandableNotificationRow childRow : children) { + if (childRow.getVisibility() != View.GONE) { + StackViewState childState + = resultState.getViewStateForView(childRow); + childState.notGoneIndex = notGoneIndex; + notGoneIndex++; + } + } + } + } } } } + private int updateNotGoneIndex(StackScrollState resultState, + StackScrollAlgorithmState state, int notGoneIndex, + ExpandableView v) { + StackViewState viewState = resultState.getViewStateForView(v); + viewState.notGoneIndex = notGoneIndex; + state.visibleChildren.add(v); + notGoneIndex++; + return notGoneIndex; + } + /** * Determine the positions for the views. This is the main part of the algorithm. * * @param resultState The result state to update if a change to the properties of a child occurs * @param algorithmState The state in which the current pass of the algorithm is currently in + * @param ambientState The current ambient state */ private void updatePositionsForState(StackScrollState resultState, - StackScrollAlgorithmState algorithmState) { + StackScrollAlgorithmState algorithmState, AmbientState ambientState) { // The starting position of the bottom stack peek - float bottomPeekStart = mInnerHeight - mBottomStackPeekSize; + float bottomPeekStart = ambientState.getInnerHeight() - mBottomStackPeekSize; // The position where the bottom stack starts. float bottomStackStart = bottomPeekStart - mBottomStackSlowDownLength; @@ -351,13 +392,17 @@ public class StackScrollAlgorithm { // How far in is the element currently transitioning into the bottom stack. float yPositionInScrollView = 0.0f; + // If we have a heads-up higher than the collapsed height we need to add the difference to + // the padding of all other elements, i.e push in the top stack slightly. + ExpandableNotificationRow topHeadsUpEntry = ambientState.getTopHeadsUpEntry(); + int childCount = algorithmState.visibleChildren.size(); int numberOfElementsCompletelyIn = (int) algorithmState.itemsInTopStack; for (int i = 0; i < childCount; i++) { ExpandableView child = algorithmState.visibleChildren.get(i); - StackScrollState.ViewState childViewState = resultState.getViewStateForView(child); - childViewState.location = StackScrollState.ViewState.LOCATION_UNKNOWN; - int childHeight = getMaxAllowedChildHeight(child); + StackViewState childViewState = resultState.getViewStateForView(child); + childViewState.location = StackViewState.LOCATION_UNKNOWN; + int childHeight = getMaxAllowedChildHeight(child, ambientState); float yPositionInScrollViewAfterElement = yPositionInScrollView + childHeight + mPaddingBetweenElements; @@ -394,7 +439,8 @@ public class StackScrollAlgorithm { bottomPeekStart, childViewState.yTranslation, childViewState, childHeight); } - clampPositionToBottomStackStart(childViewState, childViewState.height); + clampPositionToBottomStackStart(childViewState, childViewState.height, + ambientState); } else if (nextYPosition >= bottomStackStart) { // Case 2: // We are in the bottom stack. @@ -402,7 +448,7 @@ public class StackScrollAlgorithm { // According to the regular scroll view we are fully translated out of the // bottom of the screen so we are fully in the bottom stack updateStateForChildFullyInBottomStack(algorithmState, - bottomStackStart, childViewState, childHeight); + bottomStackStart, childViewState, childHeight, ambientState); } else { // According to the regular scroll view we are currently translating out of / // into the bottom of the screen @@ -413,8 +459,8 @@ public class StackScrollAlgorithm { } else { // Case 3: // We are in the regular scroll area. - childViewState.location = StackScrollState.ViewState.LOCATION_MAIN_AREA; - clampYTranslation(childViewState, childHeight); + childViewState.location = StackViewState.LOCATION_MAIN_AREA; + clampYTranslation(childViewState, childHeight, ambientState); } // The first card is always rendered. @@ -427,15 +473,62 @@ public class StackScrollAlgorithm { bottomPeekStart - mCollapseSecondCardPadding - childViewState.yTranslation, mCollapsedSize); } - childViewState.location = StackScrollState.ViewState.LOCATION_FIRST_CARD; + childViewState.location = StackViewState.LOCATION_FIRST_CARD; } - if (childViewState.location == StackScrollState.ViewState.LOCATION_UNKNOWN) { + if (childViewState.location == StackViewState.LOCATION_UNKNOWN) { Log.wtf(LOG_TAG, "Failed to assign location for child " + i); } currentYPosition = childViewState.yTranslation + childHeight + mPaddingBetweenElements; yPositionInScrollView = yPositionInScrollViewAfterElement; - childViewState.yTranslation += mTopPadding; + if (ambientState.isShadeExpanded() && topHeadsUpEntry != null + && child != topHeadsUpEntry) { + childViewState.yTranslation += topHeadsUpEntry.getHeadsUpHeight() - mCollapsedSize; + } + childViewState.yTranslation += ambientState.getTopPadding() + + ambientState.getStackTranslation(); + } + updateHeadsUpStates(resultState, algorithmState, ambientState); + } + + private void updateHeadsUpStates(StackScrollState resultState, + StackScrollAlgorithmState algorithmState, AmbientState ambientState) { + int childCount = algorithmState.visibleChildren.size(); + ExpandableNotificationRow topHeadsUpEntry = null; + for (int i = 0; i < childCount; i++) { + View child = algorithmState.visibleChildren.get(i); + if (!(child instanceof ExpandableNotificationRow)) { + break; + } + ExpandableNotificationRow row = (ExpandableNotificationRow) child; + if (!row.isHeadsUp()) { + break; + } else if (topHeadsUpEntry == null) { + topHeadsUpEntry = row; + } + StackViewState childState = resultState.getViewStateForView(row); + boolean isTopEntry = topHeadsUpEntry == row; + if (row.isPinned()) { + childState.yTranslation = 0; + childState.height = row.getHeadsUpHeight(); + if (!isTopEntry) { + // Ensure that a headsUp doesn't vertically extend further than the heads-up at + // the top most z-position + StackViewState topState = resultState.getViewStateForView(topHeadsUpEntry); + childState.height = row.getHeadsUpHeight(); + childState.yTranslation = topState.yTranslation + topState.height + - childState.height; + } + } else if (mIsExpanded) { + if (isTopEntry) { + childState.height += row.getHeadsUpHeight() - mCollapsedSize; + } + childState.height = Math.max(childState.height, row.getHeadsUpHeight()); + // Ensure that the heads up is always visible even when scrolled of from the bottom + float bottomPosition = ambientState.getMaxHeadsUpTranslation() - childState.height; + childState.yTranslation = Math.min(childState.yTranslation, + bottomPosition); + } } } @@ -445,8 +538,9 @@ public class StackScrollAlgorithm { * @param childViewState the view state of the child * @param childHeight the height of this child */ - private void clampYTranslation(StackScrollState.ViewState childViewState, int childHeight) { - clampPositionToBottomStackStart(childViewState, childHeight); + private void clampYTranslation(StackViewState childViewState, int childHeight, + AmbientState ambientState) { + clampPositionToBottomStackStart(childViewState, childHeight, ambientState); clampPositionToTopStackEnd(childViewState, childHeight); } @@ -457,28 +551,34 @@ public class StackScrollAlgorithm { * @param childViewState the view state of the child * @param childHeight the height of this child */ - private void clampPositionToBottomStackStart(StackScrollState.ViewState childViewState, - int childHeight) { + private void clampPositionToBottomStackStart(StackViewState childViewState, + int childHeight, AmbientState ambientState) { childViewState.yTranslation = Math.min(childViewState.yTranslation, - mInnerHeight - mBottomStackPeekSize - mCollapseSecondCardPadding - childHeight); + ambientState.getInnerHeight() - mBottomStackPeekSize - mCollapseSecondCardPadding + - childHeight); } /** * Clamp the yTranslation of the child up such that its end is at lest on the end of the top - * stack.get + * stack. * * @param childViewState the view state of the child * @param childHeight the height of this child */ - private void clampPositionToTopStackEnd(StackScrollState.ViewState childViewState, + private void clampPositionToTopStackEnd(StackViewState childViewState, int childHeight) { childViewState.yTranslation = Math.max(childViewState.yTranslation, mCollapsedSize - childHeight); } - private int getMaxAllowedChildHeight(View child) { + private int getMaxAllowedChildHeight(View child, AmbientState ambientState) { if (child instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) child; + if (ambientState == null && row.isHeadsUp() + || ambientState != null && ambientState.getTopHeadsUpEntry() == child) { + int extraSize = row.getIntrinsicHeight() - row.getHeadsUpHeight(); + return mCollapsedSize + extraSize; + } return row.getIntrinsicHeight(); } else if (child instanceof ExpandableView) { ExpandableView expandableView = (ExpandableView) child; @@ -489,7 +589,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,13 +610,12 @@ public class StackScrollAlgorithm { // We want at least to be at the end of the top stack when collapsing clampPositionToTopStackEnd(childViewState, newHeight); - childViewState.location = StackScrollState.ViewState.LOCATION_MAIN_AREA; + childViewState.location = StackViewState.LOCATION_MAIN_AREA; } private void updateStateForChildFullyInBottomStack(StackScrollAlgorithmState algorithmState, - float transitioningPositionStart, StackScrollState.ViewState childViewState, - int childHeight) { - + float transitioningPositionStart, StackViewState childViewState, + int childHeight, AmbientState ambientState) { float currentYPosition; algorithmState.itemsInBottomStack += 1.0f; if (algorithmState.itemsInBottomStack < MAX_ITEMS_IN_BOTTOM_STACK) { @@ -524,7 +623,7 @@ public class StackScrollAlgorithm { currentYPosition = transitioningPositionStart + mBottomStackIndentationFunctor.getValue(algorithmState.itemsInBottomStack) - mPaddingBetweenElements; - childViewState.location = StackScrollState.ViewState.LOCATION_BOTTOM_STACK_PEEKING; + childViewState.location = StackViewState.LOCATION_BOTTOM_STACK_PEEKING; } else { // we are fully inside the stack if (algorithmState.itemsInBottomStack > MAX_ITEMS_IN_BOTTOM_STACK + 2) { @@ -533,8 +632,8 @@ public class StackScrollAlgorithm { > MAX_ITEMS_IN_BOTTOM_STACK + 1) { childViewState.alpha = 1.0f - algorithmState.partialInBottom; } - childViewState.location = StackScrollState.ViewState.LOCATION_BOTTOM_STACK_HIDDEN; - currentYPosition = mInnerHeight; + childViewState.location = StackViewState.LOCATION_BOTTOM_STACK_HIDDEN; + currentYPosition = ambientState.getInnerHeight(); } childViewState.yTranslation = currentYPosition - childHeight; clampPositionToTopStackEnd(childViewState, childHeight); @@ -542,7 +641,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 +673,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 +682,7 @@ public class StackScrollAlgorithm { childViewState.alpha = 0.0f; } childViewState.yTranslation = mCollapsedSize - childHeight; - childViewState.location = StackScrollState.ViewState.LOCATION_TOP_STACK_HIDDEN; + childViewState.location = StackViewState.LOCATION_TOP_STACK_HIDDEN; } @@ -596,7 +695,7 @@ public class StackScrollAlgorithm { * @param algorithmState The state in which the current pass of the algorithm is currently in */ private void findNumberOfItemsInTopStackAndUpdateState(StackScrollState resultState, - StackScrollAlgorithmState algorithmState) { + StackScrollAlgorithmState algorithmState, AmbientState ambientState) { // The y Position if the element would be in a regular scrollView float yPositionInScrollView = 0.0f; @@ -605,8 +704,8 @@ public class StackScrollAlgorithm { // find the number of elements in the top stack. for (int i = 0; i < childCount; i++) { ExpandableView child = algorithmState.visibleChildren.get(i); - StackScrollState.ViewState childViewState = resultState.getViewStateForView(child); - int childHeight = getMaxAllowedChildHeight(child); + StackViewState childViewState = resultState.getViewStateForView(child); + int childHeight = getMaxAllowedChildHeight(child, ambientState); float yPositionInScrollViewAfterElement = yPositionInScrollView + childHeight + mPaddingBetweenElements; @@ -614,7 +713,7 @@ public class StackScrollAlgorithm { if (i == 0 && algorithmState.scrollY <= mCollapsedSize) { // The starting position of the bottom stack peek - int bottomPeekStart = mInnerHeight - mBottomStackPeekSize - + int bottomPeekStart = ambientState.getInnerHeight() - mBottomStackPeekSize - mCollapseSecondCardPadding; // Collapse and expand the first child while the shade is being expanded float maxHeight = mIsExpansionChanging && child == mFirstChildWhileExpanding @@ -676,7 +775,7 @@ public class StackScrollAlgorithm { int childCount = algorithmState.visibleChildren.size(); for (int i = 0; i < childCount; i++) { View child = algorithmState.visibleChildren.get(i); - StackScrollState.ViewState childViewState = resultState.getViewStateForView(child); + StackViewState childViewState = resultState.getViewStateForView(child); if (i < algorithmState.itemsInTopStack) { float stackIndex = algorithmState.itemsInTopStack - i; @@ -711,21 +810,6 @@ public class StackScrollAlgorithm { } } - public void setLayoutHeight(int layoutHeight) { - this.mLayoutHeight = layoutHeight; - updateInnerHeight(); - } - - public void setTopPadding(int topPadding) { - mTopPadding = topPadding; - updateInnerHeight(); - } - - private void updateInnerHeight() { - mInnerHeight = mLayoutHeight - mTopPadding; - } - - /** * Update whether the device is very small, i.e. Notifications can be in both the top and the * bottom stack at the same time @@ -755,6 +839,13 @@ public class StackScrollAlgorithm { // current height or the end value of the animation. mFirstChildMaxHeight = StackStateAnimator.getFinalActualHeight( mFirstChildWhileExpanding); + if (mFirstChildWhileExpanding instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = + (ExpandableNotificationRow) mFirstChildWhileExpanding; + if (row.isHeadsUp()) { + mFirstChildMaxHeight += mCollapsedSize - row.getHeadsUpHeight(); + } + } } else { updateFirstChildMaxSizeToMaxHeight(); } @@ -776,7 +867,7 @@ public class StackScrollAlgorithm { int oldBottom) { if (mFirstChildWhileExpanding != null) { mFirstChildMaxHeight = getMaxAllowedChildHeight( - mFirstChildWhileExpanding); + mFirstChildWhileExpanding, null); } else { mFirstChildMaxHeight = 0; } @@ -784,7 +875,7 @@ public class StackScrollAlgorithm { } }); } else { - mFirstChildMaxHeight = getMaxAllowedChildHeight(mFirstChildWhileExpanding); + mFirstChildMaxHeight = getMaxAllowedChildHeight(mFirstChildWhileExpanding, null); } } @@ -837,6 +928,10 @@ public class StackScrollAlgorithm { } } + public void setHeadsUpManager(HeadsUpManager headsUpManager) { + mHeadsUpManager = headsUpManager; + } + class StackScrollAlgorithmState { /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java index 0b1ce8f..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..b9466d4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java @@ -26,12 +26,12 @@ import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import com.android.systemui.R; +import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.ExpandableView; import com.android.systemui.statusbar.SpeedBumpView; import java.util.ArrayList; import java.util.HashSet; -import java.util.Set; import java.util.Stack; /** @@ -42,12 +42,17 @@ public class StackStateAnimator { public static final int ANIMATION_DURATION_STANDARD = 360; public static final int ANIMATION_DURATION_GO_TO_FULL_SHADE = 448; public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464; + public static final int ANIMATION_DURATION_EXPAND_CLICKED = 360; public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220; + public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 650; + public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 230; public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80; + public static final int ANIMATION_DELAY_PER_ELEMENT_EXPAND_CHILDREN = 54; public static final int ANIMATION_DELAY_PER_ELEMENT_MANUAL = 32; public static final int ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE = 48; public static final int ANIMATION_DELAY_PER_ELEMENT_DARK = 24; - private static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE = 2; + public static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE = 2; + public static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE_CHILDREN = 3; private static final int TAG_ANIMATOR_TRANSLATION_Y = R.id.translation_y_animator_tag; private static final int TAG_ANIMATOR_TRANSLATION_Z = R.id.translation_z_animator_tag; @@ -69,12 +74,16 @@ public class StackStateAnimator { private static final int TAG_START_TOP_INSET = R.id.top_inset_animator_start_value_tag; private final Interpolator mFastOutSlowInInterpolator; + private final Interpolator mHeadsUpAppearInterpolator; private final int mGoToFullShadeAppearingTranslation; + private final StackViewState mTmpState = new StackViewState(); public NotificationStackScrollLayout mHostLayout; private ArrayList<NotificationStackScrollLayout.AnimationEvent> mNewEvents = new ArrayList<>(); private ArrayList<View> mNewAddChildren = new ArrayList<>(); - private Set<Animator> mAnimatorSet = new HashSet<>(); + private HashSet<View> mHeadsUpAppearChildren = new HashSet<>(); + private HashSet<View> mHeadsUpDisappearChildren = new HashSet<>(); + private HashSet<Animator> mAnimatorSet = new HashSet<>(); private Stack<AnimatorListenerAdapter> mAnimationListenerPool = new Stack<>(); private AnimationFilter mAnimationFilter = new AnimationFilter(); private long mCurrentLength; @@ -82,9 +91,11 @@ public class StackStateAnimator { /** The current index for the last child which was not added in this event set. */ private int mCurrentLastNotAddedIndex; - private ValueAnimator mTopOverScrollAnimator; private ValueAnimator mBottomOverScrollAnimator; + private ExpandableNotificationRow mChildExpandingView; + private int mHeadsUpAppearHeightBottom; + private boolean mShadeExpanded; public StackStateAnimator(NotificationStackScrollLayout hostLayout) { mHostLayout = hostLayout; @@ -93,6 +104,7 @@ public class StackStateAnimator { mGoToFullShadeAppearingTranslation = hostLayout.getContext().getResources().getDimensionPixelSize( R.dimen.go_to_full_shade_appearing_translation); + mHeadsUpAppearInterpolator = new HeadsUpAppearInterpolator(); } public boolean isRunning() { @@ -113,20 +125,50 @@ public class StackStateAnimator { for (int i = 0; i < childCount; i++) { final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i); - StackScrollState.ViewState viewState = finalState.getViewStateForView(child); - if (viewState == null || child.getVisibility() == View.GONE) { + StackViewState viewState = finalState.getViewStateForView(child); + if (viewState == null || child.getVisibility() == View.GONE + || applyWithoutAnimation(child, viewState, finalState)) { continue; } - child.setClipBounds(null); - startAnimations(child, viewState, finalState, i); + child.setClipTopOptimization(0); + startStackAnimations(child, viewState, finalState, i, -1 /* fixedDelay */); } if (!isRunning()) { // no child has preformed any animation, lets finish onAnimationFinished(); } + mHeadsUpAppearChildren.clear(); + mHeadsUpDisappearChildren.clear(); mNewEvents.clear(); mNewAddChildren.clear(); + mChildExpandingView = null; + } + + /** + * Determines if a view should not perform an animation and applies it directly. + * + * @return true if no animation should be performed + */ + private boolean applyWithoutAnimation(ExpandableView child, StackViewState viewState, + StackScrollState finalState) { + if (mShadeExpanded) { + return false; + } + if (getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y) != null) { + // A Y translation animation is running + return false; + } + if (mHeadsUpDisappearChildren.contains(child) || mHeadsUpAppearChildren.contains(child)) { + // This is a heads up animation + return false; + } + if (mHostLayout.isPinnedHeadsUp(child)) { + // This is another headsUp which might move. Let's animate! + return false; + } + finalState.applyState(child, viewState); + return true; } private int findLastNotAddedIndex(StackScrollState finalState) { @@ -134,7 +176,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 +187,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 +217,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 +258,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); + } + } + + /** + * 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(StackScrollState.ViewState viewState, + private long calculateChildAnimationDelay(StackViewState viewState, StackScrollState finalState) { if (mAnimationFilter.hasDarkEvent) { return calculateDelayDark(viewState); @@ -314,7 +358,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 +372,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 +438,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 +495,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 +569,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 +621,8 @@ public class StackStateAnimator { child.setTag(TAG_END_TRANSLATION_Z, newEndValue); } - private void startYTranslationAnimation(final ExpandableView child, - StackScrollState.ViewState viewState, long duration, long delay) { + private void startYTranslationAnimation(final View child, + ViewState viewState, long duration, long delay) { Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Y); Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Y); float newEndValue = viewState.yTranslation; @@ -608,7 +652,9 @@ public class StackStateAnimator { ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y, child.getTranslationY(), newEndValue); - animator.setInterpolator(mFastOutSlowInInterpolator); + Interpolator interpolator = mHeadsUpAppearChildren.contains(child) ? + mHeadsUpAppearInterpolator :mFastOutSlowInInterpolator; + animator.setInterpolator(interpolator); long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator); animator.setDuration(newDuration); if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) { @@ -630,8 +676,8 @@ public class StackStateAnimator { child.setTag(TAG_END_TRANSLATION_Y, newEndValue); } - private void startScaleAnimation(final ExpandableView child, - StackScrollState.ViewState viewState, long duration) { + private void startScaleAnimation(final View child, + ViewState viewState, long duration) { Float previousStartValue = getChildTag(child, TAG_START_SCALE); Float previousEndValue = getChildTag(child, TAG_END_SCALE); float newEndValue = viewState.scale; @@ -723,7 +769,7 @@ public class StackStateAnimator { }; } - private static <T> T getChildTag(View child, int tag) { + public static <T> T getChildTag(View child, int tag) { return (T) child.getTag(tag); } @@ -765,7 +811,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 +822,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 +834,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 +856,32 @@ public class StackStateAnimator { mHostLayout.getOverlay().remove(changingView); } }); - } else if (event.animationType == + } else if (event.animationType == NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) { // A race condition can trigger the view to be added to the overlay even though // it is swiped out. So let's remove it mHostLayout.getOverlay().remove(changingView); + } else if (event.animationType == NotificationStackScrollLayout + .AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED) { + ExpandableNotificationRow row = (ExpandableNotificationRow) event.changingView; + row.prepareExpansionChanged(finalState); + mChildExpandingView = row; + } else if (event.animationType == NotificationStackScrollLayout + .AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR) { + // This item is added, initialize it's properties. + StackViewState viewState = finalState.getViewStateForView(changingView); + mTmpState.copyFrom(viewState); + if (event.headsUpFromBottom) { + mTmpState.yTranslation = mHeadsUpAppearHeightBottom; + } else { + mTmpState.yTranslation = -mTmpState.height; + } + mHeadsUpAppearChildren.add(changingView); + finalState.applyState(changingView, mTmpState); + } else if (event.animationType == NotificationStackScrollLayout + .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR) { + // This item is added, initialize it's properties. + mHeadsUpDisappearChildren.add(changingView); } mNewEvents.add(event); } @@ -883,4 +947,12 @@ public class StackStateAnimator { return getChildTag(view, TAG_END_HEIGHT); } } + + public void setHeadsUpAppearHeightBottom(int headsUpAppearHeightBottom) { + mHeadsUpAppearHeightBottom = headsUpAppearHeightBottom; + } + + public void setShadeExpanded(boolean shadeExpanded) { + mShadeExpanded = shadeExpanded; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackViewState.java new file mode 100644 index 0000000..55ef440 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackViewState.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.stack; + +import android.view.View; + +import com.android.systemui.statusbar.ExpandableView; + +/** +* A state of an expandable view +*/ +public class StackViewState extends ViewState { + + // These are flags such that we can create masks for filtering. + + public static final int LOCATION_UNKNOWN = 0x00; + public static final int LOCATION_FIRST_CARD = 0x01; + public static final int LOCATION_TOP_STACK_HIDDEN = 0x02; + public static final int LOCATION_TOP_STACK_PEEKING = 0x04; + public static final int LOCATION_MAIN_AREA = 0x08; + public static final int LOCATION_BOTTOM_STACK_PEEKING = 0x10; + public static final int LOCATION_BOTTOM_STACK_HIDDEN = 0x20; + /** The view isn't layouted at all. */ + public static final int LOCATION_GONE = 0x40; + + public int height; + public boolean dimmed; + public boolean dark; + public boolean hideSensitive; + public boolean belowSpeedBump; + + /** + * The amount which the view should be clipped from the top. This is calculated to + * perceive consistent shadows. + */ + public int clipTopAmount; + + /** + * How much does the child overlap with the previous view on the top? Can be used for + * a clipping optimization + */ + public int topOverLap; + + /** + * The index of the view, only accounting for views not equal to GONE + */ + public int notGoneIndex; + + /** + * The location this view is currently rendered at. + * + * <p>See <code>LOCATION_</code> flags.</p> + */ + public int location; + + @Override + public void copyFrom(ViewState viewState) { + super.copyFrom(viewState); + if (viewState instanceof StackViewState) { + StackViewState svs = (StackViewState) viewState; + height = svs.height; + dimmed = svs.dimmed; + dark = svs.dark; + hideSensitive = svs.hideSensitive; + belowSpeedBump = svs.belowSpeedBump; + clipTopAmount = svs.clipTopAmount; + topOverLap = svs.topOverLap; + notGoneIndex = svs.notGoneIndex; + location = svs.location; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java new file mode 100644 index 0000000..3e538df --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.stack; + +import android.view.View; + +/** + * A state of a view. This can be used to apply a set of view properties to a view with + * {@link com.android.systemui.statusbar.stack.StackScrollState} or start animations with + * {@link com.android.systemui.statusbar.stack.StackStateAnimator}. +*/ +public class ViewState { + + public float alpha; + public float yTranslation; + public float zTranslation; + public boolean gone; + public float scale; + + public void copyFrom(ViewState viewState) { + alpha = viewState.alpha; + yTranslation = viewState.yTranslation; + zTranslation = viewState.zTranslation; + gone = viewState.gone; + scale = viewState.scale; + } + + public void initFrom(View view) { + alpha = view.getVisibility() == View.INVISIBLE ? 0.0f : view.getAlpha(); + yTranslation = view.getTranslationY(); + zTranslation = view.getTranslationZ(); + gone = view.getVisibility() == View.GONE; + scale = view.getScaleX(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java index 08732e5..a5684a4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java @@ -20,12 +20,11 @@ import android.os.IBinder; import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.StatusBarNotification; import android.view.View; -import android.view.ViewGroup.LayoutParams; -import android.view.WindowManager; import com.android.internal.statusbar.StatusBarIcon; import com.android.systemui.statusbar.ActivatableNotificationView; import com.android.systemui.statusbar.BaseStatusBar; +import com.android.systemui.statusbar.NotificationData; /* * Status bar implementation for "large screen" products that mostly present no on-screen nav @@ -47,7 +46,8 @@ public class TvStatusBar extends BaseStatusBar { } @Override - public void addNotification(StatusBarNotification notification, RankingMap ranking) { + public void addNotification(StatusBarNotification notification, RankingMap ranking, + NotificationData.Entry entry) { } @Override @@ -59,7 +59,7 @@ public class TvStatusBar extends BaseStatusBar { } @Override - public void disable(int state, boolean animate) { + public void disable(int state1, int state2, boolean animate) { } @Override @@ -104,82 +104,77 @@ public class TvStatusBar extends BaseStatusBar { } @Override - protected WindowManager.LayoutParams getSearchLayoutParams( - LayoutParams layoutParams) { - return null; + protected void setAreThereNotifications() { } @Override - protected void haltTicker() { + protected void updateNotifications() { } @Override - protected void setAreThereNotifications() { + public boolean shouldDisableNavbarGestures() { + return true; } - @Override - protected void updateNotifications() { + public View getStatusBarView() { + return null; } @Override - protected void tick(StatusBarNotification n, boolean firstTime) { + public void maybeEscalateHeadsUp() { } @Override - protected void updateExpandedViewPos(int expandedPosition) { + protected int getMaxKeyguardNotifications() { + return 0; } @Override - protected boolean shouldDisableNavbarGestures() { - return true; - } - - public View getStatusBarView() { - return null; + public void animateExpandSettingsPanel() { } @Override - public void resetHeadsUpDecayTimer() { + protected void createAndAddWindows() { } @Override - public void scheduleHeadsUpOpen() { + protected void refreshLayout(int layoutDirection) { } @Override - public void scheduleHeadsUpEscalation() { + public void onActivated(ActivatableNotificationView view) { } @Override - public void scheduleHeadsUpClose() { + public void onActivationReset(ActivatableNotificationView view) { } @Override - protected int getMaxKeyguardNotifications() { - return 0; + public void showScreenPinningRequest() { } @Override - public void animateExpandSettingsPanel() { + public void appTransitionPending() { } @Override - protected void createAndAddWindows() { + public void appTransitionCancelled() { } @Override - protected void refreshLayout(int layoutDirection) { + public void appTransitionStarting(long startTime, long duration) { } @Override - public void onActivated(ActivatableNotificationView view) { + protected void updateHeadsUp(String key, NotificationData.Entry entry, boolean shouldInterrupt, + boolean alertAgain) { } @Override - public void onActivationReset(ActivatableNotificationView view) { + protected void setHeadsUpUser(int newUserId) { } - @Override - public void showScreenPinningRequest() { + protected boolean isSnoozedPackage(StatusBarNotification sbn) { + return false; } } diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java index f804736..e6c95b5 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java +++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 Google Inc. + * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,424 +17,623 @@ package com.android.systemui.usb; import android.app.Notification; +import android.app.Notification.Action; import android.app.NotificationManager; import android.app.PendingIntent; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.content.res.Resources; -import android.os.Environment; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.MoveCallback; +import android.os.Bundle; import android.os.Handler; -import android.os.HandlerThread; import android.os.UserHandle; +import android.os.storage.DiskInfo; import android.os.storage.StorageEventListener; import android.os.storage.StorageManager; -import android.os.storage.StorageVolume; -import android.provider.Settings; +import android.os.storage.VolumeInfo; +import android.os.storage.VolumeRecord; +import android.text.TextUtils; +import android.text.format.DateUtils; import android.util.Log; +import android.util.SparseArray; +import com.android.internal.R; import com.android.systemui.SystemUI; +import java.util.List; + public class StorageNotification extends SystemUI { private static final String TAG = "StorageNotification"; - private static final boolean DEBUG = false; - - private static final boolean POP_UMS_ACTIVITY_ON_CONNECT = true; - - /** - * The notification that is shown when a USB mass storage host - * is connected. - * <p> - * This is lazily created, so use {@link #setUsbStorageNotification()}. - */ - private Notification mUsbStorageNotification; - - /** - * The notification that is shown when the following media events occur: - * - Media is being checked - * - Media is blank (or unknown filesystem) - * - Media is corrupt - * - Media is safe to unmount - * - Media is missing - * <p> - * This is lazily created, so use {@link #setMediaStorageNotification()}. - */ - private Notification mMediaStorageNotification; - private boolean mUmsAvailable; + + private static final int PUBLIC_ID = 0x53505542; // SPUB + private static final int PRIVATE_ID = 0x53505256; // SPRV + private static final int DISK_ID = 0x5344534b; // SDSK + private static final int MOVE_ID = 0x534d4f56; // SMOV + + private static final String ACTION_SNOOZE_VOLUME = "com.android.systemui.action.SNOOZE_VOLUME"; + + // TODO: delay some notifications to avoid bumpy fast operations + + private NotificationManager mNotificationManager; private StorageManager mStorageManager; - private Handler mAsyncEventHandler; + private static class MoveInfo { + public int moveId; + public Bundle extras; + public String packageName; + public String label; + public String volumeUuid; + } + + private final SparseArray<MoveInfo> mMoves = new SparseArray<>(); - private class StorageNotificationEventListener extends StorageEventListener { - public void onUsbMassStorageConnectionChanged(final boolean connected) { - mAsyncEventHandler.post(new Runnable() { - @Override - public void run() { - onUsbMassStorageConnectionChangedAsync(connected); - } - }); + private final StorageEventListener mListener = new StorageEventListener() { + @Override + public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) { + onVolumeStateChangedInternal(vol); } - public void onStorageStateChanged(final String path, - final String oldState, final String newState) { - mAsyncEventHandler.post(new Runnable() { - @Override - public void run() { - onStorageStateChangedAsync(path, oldState, newState); - } - }); + + @Override + public void onVolumeRecordChanged(VolumeRecord rec) { + // Avoid kicking notifications when getting early metadata before + // mounted. If already mounted, we're being kicked because of a + // nickname or init'ed change. + final VolumeInfo vol = mStorageManager.findVolumeByUuid(rec.getFsUuid()); + if (vol != null && vol.isMountedReadable()) { + onVolumeStateChangedInternal(vol); + } } - } + + @Override + public void onVolumeForgotten(String fsUuid) { + // Stop annoying the user + mNotificationManager.cancelAsUser(fsUuid, PRIVATE_ID, UserHandle.ALL); + } + + @Override + public void onDiskScanned(DiskInfo disk, int volumeCount) { + onDiskScannedInternal(disk, volumeCount); + } + }; + + private final BroadcastReceiver mSnoozeReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + // TODO: kick this onto background thread + final String fsUuid = intent.getStringExtra(VolumeRecord.EXTRA_FS_UUID); + mStorageManager.setVolumeSnoozed(fsUuid, true); + } + }; + + private final MoveCallback mMoveCallback = new MoveCallback() { + @Override + public void onCreated(int moveId, Bundle extras) { + final MoveInfo move = new MoveInfo(); + move.moveId = moveId; + move.extras = extras; + if (extras != null) { + move.packageName = extras.getString(Intent.EXTRA_PACKAGE_NAME); + move.label = extras.getString(Intent.EXTRA_TITLE); + move.volumeUuid = extras.getString(VolumeRecord.EXTRA_FS_UUID); + } + mMoves.put(moveId, move); + } + + @Override + public void onStatusChanged(int moveId, int status, long estMillis) { + final MoveInfo move = mMoves.get(moveId); + if (move == null) { + Log.w(TAG, "Ignoring unknown move " + moveId); + return; + } + + if (PackageManager.isMoveStatusFinished(status)) { + onMoveFinished(move, status); + } else { + onMoveProgress(move, status, estMillis); + } + } + }; @Override public void start() { - mStorageManager = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE); - final boolean connected = mStorageManager.isUsbMassStorageConnected(); - if (DEBUG) Log.d(TAG, String.format( "Startup with UMS connection %s (media state %s)", - mUmsAvailable, Environment.getExternalStorageState())); - - HandlerThread thr = new HandlerThread("SystemUI StorageNotification"); - thr.start(); - mAsyncEventHandler = new Handler(thr.getLooper()); - - StorageNotificationEventListener listener = new StorageNotificationEventListener(); - listener.onUsbMassStorageConnectionChanged(connected); - mStorageManager.registerListener(listener); - } + mNotificationManager = mContext.getSystemService(NotificationManager.class); + + mStorageManager = mContext.getSystemService(StorageManager.class); + mStorageManager.registerListener(mListener); - private void onUsbMassStorageConnectionChangedAsync(boolean connected) { - mUmsAvailable = connected; - /* - * Even though we may have a UMS host connected, we the SD card - * may not be in a state for export. - */ - String st = Environment.getExternalStorageState(); - - if (DEBUG) Log.i(TAG, String.format("UMS connection changed to %s (media state %s)", - connected, st)); - - if (connected && (st.equals( - Environment.MEDIA_REMOVED) || st.equals(Environment.MEDIA_CHECKING))) { - /* - * No card or card being checked = don't display - */ - connected = false; + mContext.registerReceiver(mSnoozeReceiver, new IntentFilter(ACTION_SNOOZE_VOLUME), + android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null); + + // Kick current state into place + final List<VolumeInfo> vols = mStorageManager.getVolumes(); + for (VolumeInfo vol : vols) { + onVolumeStateChangedInternal(vol); } - updateUsbMassStorageNotification(connected); + + mContext.getPackageManager().registerMoveCallback(mMoveCallback, new Handler()); + + updateMissingPrivateVolumes(); } - private void onStorageStateChangedAsync(String path, String oldState, String newState) { - if (DEBUG) Log.i(TAG, String.format( - "Media {%s} state changed from {%s} -> {%s}", path, oldState, newState)); - if (newState.equals(Environment.MEDIA_SHARED)) { - /* - * Storage is now shared. Modify the UMS notification - * for stopping UMS. - */ - Intent intent = new Intent(); - intent.setClass(mContext, com.android.systemui.usb.UsbStorageActivity.class); - PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); - setUsbStorageNotification( - com.android.internal.R.string.usb_storage_stop_notification_title, - com.android.internal.R.string.usb_storage_stop_notification_message, - com.android.internal.R.drawable.stat_sys_warning, false, true, pi); - } else if (newState.equals(Environment.MEDIA_CHECKING)) { - /* - * Storage is now checking. Update media notification and disable - * UMS notification. - */ - setMediaStorageNotification( - com.android.internal.R.string.ext_media_checking_notification_title, - com.android.internal.R.string.ext_media_checking_notification_message, - com.android.internal.R.drawable.stat_notify_sdcard_prepare, true, false, null); - updateUsbMassStorageNotification(false); - } else if (newState.equals(Environment.MEDIA_MOUNTED)) { - /* - * Storage is now mounted. Dismiss any media notifications, - * and enable UMS notification if connected. - */ - setMediaStorageNotification(0, 0, 0, false, false, null); - updateUsbMassStorageNotification(mUmsAvailable); - } else if (newState.equals(Environment.MEDIA_UNMOUNTED)) { - /* - * Storage is now unmounted. We may have been unmounted - * because the user is enabling/disabling UMS, in which case we don't - * want to display the 'safe to unmount' notification. - */ - if (!mStorageManager.isUsbMassStorageEnabled()) { - if (oldState.equals(Environment.MEDIA_SHARED)) { - /* - * The unmount was due to UMS being enabled. Dismiss any - * media notifications, and enable UMS notification if connected - */ - setMediaStorageNotification(0, 0, 0, false, false, null); - updateUsbMassStorageNotification(mUmsAvailable); - } else { - /* - * Show safe to unmount media notification, and enable UMS - * notification if connected. - */ - if (Environment.isExternalStorageRemovable()) { - setMediaStorageNotification( - com.android.internal.R.string.ext_media_safe_unmount_notification_title, - com.android.internal.R.string.ext_media_safe_unmount_notification_message, - com.android.internal.R.drawable.stat_notify_sdcard, true, true, null); - } else { - // This device does not have removable storage, so - // don't tell the user they can remove it. - setMediaStorageNotification(0, 0, 0, false, false, null); - } - updateUsbMassStorageNotification(mUmsAvailable); - } + private void updateMissingPrivateVolumes() { + final List<VolumeRecord> recs = mStorageManager.getVolumeRecords(); + for (VolumeRecord rec : recs) { + if (rec.getType() != VolumeInfo.TYPE_PRIVATE) continue; + + final String fsUuid = rec.getFsUuid(); + final VolumeInfo info = mStorageManager.findVolumeByUuid(fsUuid); + if (info != null && info.isMountedWritable()) { + // Yay, private volume is here! + mNotificationManager.cancelAsUser(fsUuid, PRIVATE_ID, UserHandle.ALL); + } else { - /* - * The unmount was due to UMS being enabled. Dismiss any - * media notifications, and disable the UMS notification - */ - setMediaStorageNotification(0, 0, 0, false, false, null); - updateUsbMassStorageNotification(false); + // Boo, annoy the user to reinsert the private volume + final CharSequence title = mContext.getString(R.string.ext_media_missing_title, + rec.getNickname()); + final CharSequence text = mContext.getString(R.string.ext_media_missing_message); + + final Notification notif = new Notification.Builder(mContext) + .setSmallIcon(R.drawable.stat_notify_sdcard) + .setColor(mContext.getColor(R.color.system_notification_accent_color)) + .setContentTitle(title) + .setContentText(text) + .setContentIntent(buildForgetPendingIntent(rec)) + .setStyle(new Notification.BigTextStyle().bigText(text)) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .setLocalOnly(true) + .setCategory(Notification.CATEGORY_SYSTEM) + .setOngoing(true) + .build(); + + mNotificationManager.notifyAsUser(fsUuid, PRIVATE_ID, notif, UserHandle.ALL); } - } else if (newState.equals(Environment.MEDIA_NOFS)) { - /* - * Storage has no filesystem. Show blank media notification, - * and enable UMS notification if connected. - */ - Intent intent = new Intent(); - intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class); - intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, - getVolumeByPath(mStorageManager.getVolumeList(), path)); - PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); - - setMediaStorageNotification( - com.android.internal.R.string.ext_media_nofs_notification_title, - com.android.internal.R.string.ext_media_nofs_notification_message, - com.android.internal.R.drawable.stat_notify_sdcard_usb, true, false, pi); - updateUsbMassStorageNotification(mUmsAvailable); - } else if (newState.equals(Environment.MEDIA_UNMOUNTABLE)) { - /* - * Storage is corrupt. Show corrupt media notification, - * and enable UMS notification if connected. - */ - Intent intent = new Intent(); - intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class); - intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, - getVolumeByPath(mStorageManager.getVolumeList(), path)); - PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); - - setMediaStorageNotification( - com.android.internal.R.string.ext_media_unmountable_notification_title, - com.android.internal.R.string.ext_media_unmountable_notification_message, - com.android.internal.R.drawable.stat_notify_sdcard_usb, true, false, pi); - updateUsbMassStorageNotification(mUmsAvailable); - } else if (newState.equals(Environment.MEDIA_REMOVED)) { - /* - * Storage has been removed. Show nomedia media notification, - * and disable UMS notification regardless of connection state. - */ - setMediaStorageNotification( - com.android.internal.R.string.ext_media_nomedia_notification_title, - com.android.internal.R.string.ext_media_nomedia_notification_message, - com.android.internal.R.drawable.stat_notify_sdcard_usb, - true, false, null); - updateUsbMassStorageNotification(false); - } else if (newState.equals(Environment.MEDIA_BAD_REMOVAL)) { - /* - * Storage has been removed unsafely. Show bad removal media notification, - * and disable UMS notification regardless of connection state. - */ - setMediaStorageNotification( - com.android.internal.R.string.ext_media_badremoval_notification_title, - com.android.internal.R.string.ext_media_badremoval_notification_message, - com.android.internal.R.drawable.stat_sys_warning, - true, true, null); - updateUsbMassStorageNotification(false); + } + } + + private void onDiskScannedInternal(DiskInfo disk, int volumeCount) { + if (volumeCount == 0) { + // No supported volumes found, give user option to format + final CharSequence title = mContext.getString( + R.string.ext_media_unmountable_notification_title, disk.getDescription()); + final CharSequence text = mContext.getString( + R.string.ext_media_unmountable_notification_message, disk.getDescription()); + + final Notification notif = new Notification.Builder(mContext) + .setSmallIcon(getSmallIcon(disk, VolumeInfo.STATE_UNMOUNTABLE)) + .setColor(mContext.getColor(R.color.system_notification_accent_color)) + .setContentTitle(title) + .setContentText(text) + .setContentIntent(buildInitPendingIntent(disk)) + .setStyle(new Notification.BigTextStyle().bigText(text)) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .setLocalOnly(true) + .setCategory(Notification.CATEGORY_ERROR) + .build(); + + mNotificationManager.notifyAsUser(disk.getId(), DISK_ID, notif, UserHandle.ALL); + } else { - Log.w(TAG, String.format("Ignoring unknown state {%s}", newState)); + // Yay, we have volumes! + mNotificationManager.cancelAsUser(disk.getId(), DISK_ID, UserHandle.ALL); } } - /** - * Get the corresponding StorageVolume object for a specific path. - */ - private final StorageVolume getVolumeByPath(StorageVolume[] volumes, String path) { - for (StorageVolume volume : volumes) { - if (volume.getPath().equals(path)) { - return volume; - } + private void onVolumeStateChangedInternal(VolumeInfo vol) { + switch (vol.getType()) { + case VolumeInfo.TYPE_PRIVATE: + onPrivateVolumeStateChangedInternal(vol); + break; + case VolumeInfo.TYPE_PUBLIC: + onPublicVolumeStateChangedInternal(vol); + break; } - Log.w(TAG, "No storage found"); - return null; } - /** - * Update the state of the USB mass storage notification - */ - void updateUsbMassStorageNotification(boolean available) { - - if (available) { - Intent intent = new Intent(); - intent.setClass(mContext, com.android.systemui.usb.UsbStorageActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); - setUsbStorageNotification( - com.android.internal.R.string.usb_storage_notification_title, - com.android.internal.R.string.usb_storage_notification_message, - com.android.internal.R.drawable.stat_sys_data_usb, - false, true, pi); + private void onPrivateVolumeStateChangedInternal(VolumeInfo vol) { + Log.d(TAG, "Notifying about private volume: " + vol.toString()); + + updateMissingPrivateVolumes(); + } + + private void onPublicVolumeStateChangedInternal(VolumeInfo vol) { + Log.d(TAG, "Notifying about public volume: " + vol.toString()); + + final Notification notif; + switch (vol.getState()) { + case VolumeInfo.STATE_UNMOUNTED: + notif = onVolumeUnmounted(vol); + break; + case VolumeInfo.STATE_CHECKING: + notif = onVolumeChecking(vol); + break; + case VolumeInfo.STATE_MOUNTED: + case VolumeInfo.STATE_MOUNTED_READ_ONLY: + notif = onVolumeMounted(vol); + break; + case VolumeInfo.STATE_FORMATTING: + notif = onVolumeFormatting(vol); + break; + case VolumeInfo.STATE_EJECTING: + notif = onVolumeEjecting(vol); + break; + case VolumeInfo.STATE_UNMOUNTABLE: + notif = onVolumeUnmountable(vol); + break; + case VolumeInfo.STATE_REMOVED: + notif = onVolumeRemoved(vol); + break; + case VolumeInfo.STATE_BAD_REMOVAL: + notif = onVolumeBadRemoval(vol); + break; + default: + notif = null; + break; + } + + if (notif != null) { + mNotificationManager.notifyAsUser(vol.getId(), PUBLIC_ID, notif, UserHandle.ALL); } else { - setUsbStorageNotification(0, 0, 0, false, false, null); + mNotificationManager.cancelAsUser(vol.getId(), PUBLIC_ID, UserHandle.ALL); } } - /** - * Sets the USB storage notification. - */ - private synchronized void setUsbStorageNotification(int titleId, int messageId, int icon, - boolean sound, boolean visible, PendingIntent pi) { + private Notification onVolumeUnmounted(VolumeInfo vol) { + // Ignored + return null; + } - if (!visible && mUsbStorageNotification == null) { - return; + private Notification onVolumeChecking(VolumeInfo vol) { + final DiskInfo disk = vol.getDisk(); + final CharSequence title = mContext.getString( + R.string.ext_media_checking_notification_title, disk.getDescription()); + final CharSequence text = mContext.getString( + R.string.ext_media_checking_notification_message, disk.getDescription()); + + return buildNotificationBuilder(vol, title, text) + .setCategory(Notification.CATEGORY_PROGRESS) + .setPriority(Notification.PRIORITY_LOW) + .setOngoing(true) + .build(); + } + + private Notification onVolumeMounted(VolumeInfo vol) { + final VolumeRecord rec = mStorageManager.findRecordByUuid(vol.getFsUuid()); + + // Don't annoy when user dismissed in past + if (rec.isSnoozed()) return null; + + final DiskInfo disk = vol.getDisk(); + if (disk.isAdoptable() && !rec.isInited()) { + final CharSequence title = disk.getDescription(); + final CharSequence text = mContext.getString( + R.string.ext_media_new_notification_message, disk.getDescription()); + + final PendingIntent initIntent = buildInitPendingIntent(vol); + return buildNotificationBuilder(vol, title, text) + .addAction(new Action(0, mContext.getString(R.string.ext_media_init_action), + initIntent)) + .addAction(new Action(0, mContext.getString(R.string.ext_media_unmount_action), + buildUnmountPendingIntent(vol))) + .setContentIntent(initIntent) + .setDeleteIntent(buildSnoozeIntent(vol)) + .setCategory(Notification.CATEGORY_SYSTEM) + .build(); + + } else { + final CharSequence title = disk.getDescription(); + final CharSequence text = mContext.getString( + R.string.ext_media_ready_notification_message, disk.getDescription()); + + final PendingIntent browseIntent = buildBrowsePendingIntent(vol); + return buildNotificationBuilder(vol, title, text) + .addAction(new Action(0, mContext.getString(R.string.ext_media_browse_action), + browseIntent)) + .addAction(new Action(0, mContext.getString(R.string.ext_media_unmount_action), + buildUnmountPendingIntent(vol))) + .setContentIntent(browseIntent) + .setDeleteIntent(buildSnoozeIntent(vol)) + .setCategory(Notification.CATEGORY_SYSTEM) + .setPriority(Notification.PRIORITY_LOW) + .build(); } + } - NotificationManager notificationManager = (NotificationManager) mContext - .getSystemService(Context.NOTIFICATION_SERVICE); + private Notification onVolumeFormatting(VolumeInfo vol) { + // Ignored + return null; + } - if (notificationManager == null) { - return; + private Notification onVolumeEjecting(VolumeInfo vol) { + final DiskInfo disk = vol.getDisk(); + final CharSequence title = mContext.getString( + R.string.ext_media_unmounting_notification_title, disk.getDescription()); + final CharSequence text = mContext.getString( + R.string.ext_media_unmounting_notification_message, disk.getDescription()); + + return buildNotificationBuilder(vol, title, text) + .setCategory(Notification.CATEGORY_PROGRESS) + .setPriority(Notification.PRIORITY_LOW) + .setOngoing(true) + .build(); + } + + private Notification onVolumeUnmountable(VolumeInfo vol) { + final DiskInfo disk = vol.getDisk(); + final CharSequence title = mContext.getString( + R.string.ext_media_unmountable_notification_title, disk.getDescription()); + final CharSequence text = mContext.getString( + R.string.ext_media_unmountable_notification_message, disk.getDescription()); + + return buildNotificationBuilder(vol, title, text) + .setContentIntent(buildVolumeSettingsPendingIntent(vol)) + .setCategory(Notification.CATEGORY_ERROR) + .build(); + } + + private Notification onVolumeRemoved(VolumeInfo vol) { + if (!vol.isPrimary()) { + // Ignore non-primary media + return null; } - if (visible) { - Resources r = Resources.getSystem(); - CharSequence title = r.getText(titleId); - CharSequence message = r.getText(messageId); + final DiskInfo disk = vol.getDisk(); + final CharSequence title = mContext.getString( + R.string.ext_media_nomedia_notification_title, disk.getDescription()); + final CharSequence text = mContext.getString( + R.string.ext_media_nomedia_notification_message, disk.getDescription()); - if (mUsbStorageNotification == null) { - mUsbStorageNotification = new Notification(); - mUsbStorageNotification.icon = icon; - mUsbStorageNotification.when = 0; - } + return buildNotificationBuilder(vol, title, text) + .setCategory(Notification.CATEGORY_ERROR) + .build(); + } - if (sound) { - mUsbStorageNotification.defaults |= Notification.DEFAULT_SOUND; - } else { - mUsbStorageNotification.defaults &= ~Notification.DEFAULT_SOUND; - } + private Notification onVolumeBadRemoval(VolumeInfo vol) { + if (!vol.isPrimary()) { + // Ignore non-primary media + return null; + } - mUsbStorageNotification.flags = Notification.FLAG_ONGOING_EVENT; + final DiskInfo disk = vol.getDisk(); + final CharSequence title = mContext.getString( + R.string.ext_media_badremoval_notification_title, disk.getDescription()); + final CharSequence text = mContext.getString( + R.string.ext_media_badremoval_notification_message, disk.getDescription()); - mUsbStorageNotification.tickerText = title; - if (pi == null) { - Intent intent = new Intent(); - pi = PendingIntent.getBroadcastAsUser(mContext, 0, intent, 0, - UserHandle.CURRENT); - } - mUsbStorageNotification.color = mContext.getResources().getColor( - com.android.internal.R.color.system_notification_accent_color); - mUsbStorageNotification.setLatestEventInfo(mContext, title, message, pi); - mUsbStorageNotification.visibility = Notification.VISIBILITY_PUBLIC; - mUsbStorageNotification.category = Notification.CATEGORY_SYSTEM; - - final boolean adbOn = 1 == Settings.Global.getInt( - mContext.getContentResolver(), - Settings.Global.ADB_ENABLED, - 0); - - if (POP_UMS_ACTIVITY_ON_CONNECT && !adbOn) { - // Pop up a full-screen alert to coach the user through enabling UMS. The average - // user has attached the device to USB either to charge the phone (in which case - // this is harmless) or transfer files, and in the latter case this alert saves - // several steps (as well as subtly indicates that you shouldn't mix UMS with other - // activities on the device). - // - // If ADB is enabled, however, we suppress this dialog (under the assumption that a - // developer (a) knows how to enable UMS, and (b) is probably using USB to install - // builds or use adb commands. - mUsbStorageNotification.fullScreenIntent = pi; - } + return buildNotificationBuilder(vol, title, text) + .setCategory(Notification.CATEGORY_ERROR) + .build(); + } + + private void onMoveProgress(MoveInfo move, int status, long estMillis) { + final CharSequence title; + if (!TextUtils.isEmpty(move.label)) { + title = mContext.getString(R.string.ext_media_move_specific_title, move.label); + } else { + title = mContext.getString(R.string.ext_media_move_title); } - final int notificationId = mUsbStorageNotification.icon; - if (visible) { - notificationManager.notifyAsUser(null, notificationId, mUsbStorageNotification, - UserHandle.ALL); + final CharSequence text; + if (estMillis < 0) { + text = null; } else { - notificationManager.cancelAsUser(null, notificationId, UserHandle.ALL); + text = DateUtils.formatDuration(estMillis); } - } - private synchronized boolean getMediaStorageNotificationDismissable() { - if ((mMediaStorageNotification != null) && - ((mMediaStorageNotification.flags & Notification.FLAG_AUTO_CANCEL) == - Notification.FLAG_AUTO_CANCEL)) - return true; + final PendingIntent intent; + if (move.packageName != null) { + intent = buildWizardMovePendingIntent(move); + } else { + intent = buildWizardMigratePendingIntent(move); + } - return false; + final Notification notif = new Notification.Builder(mContext) + .setSmallIcon(R.drawable.stat_notify_sdcard) + .setColor(mContext.getColor(R.color.system_notification_accent_color)) + .setContentTitle(title) + .setContentText(text) + .setContentIntent(intent) + .setStyle(new Notification.BigTextStyle().bigText(text)) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .setLocalOnly(true) + .setCategory(Notification.CATEGORY_PROGRESS) + .setPriority(Notification.PRIORITY_LOW) + .setProgress(100, status, false) + .setOngoing(true) + .build(); + + mNotificationManager.notifyAsUser(move.packageName, MOVE_ID, notif, UserHandle.ALL); } - /** - * Sets the media storage notification. - */ - private synchronized void setMediaStorageNotification(int titleId, int messageId, int icon, boolean visible, - boolean dismissable, PendingIntent pi) { - - if (!visible && mMediaStorageNotification == null) { + private void onMoveFinished(MoveInfo move, int status) { + if (move.packageName != null) { + // We currently ignore finished app moves; just clear the last + // published progress + mNotificationManager.cancelAsUser(move.packageName, MOVE_ID, UserHandle.ALL); return; } - NotificationManager notificationManager = (NotificationManager) mContext - .getSystemService(Context.NOTIFICATION_SERVICE); + final VolumeInfo privateVol = mContext.getPackageManager().getPrimaryStorageCurrentVolume(); + final String descrip = mStorageManager.getBestVolumeDescription(privateVol); - if (notificationManager == null) { - return; + final CharSequence title; + final CharSequence text; + if (status == PackageManager.MOVE_SUCCEEDED) { + title = mContext.getString(R.string.ext_media_move_success_title); + text = mContext.getString(R.string.ext_media_move_success_message, descrip); + } else { + title = mContext.getString(R.string.ext_media_move_failure_title); + text = mContext.getString(R.string.ext_media_move_failure_message); } - if (mMediaStorageNotification != null && visible) { - /* - * Dismiss the previous notification - we're about to - * re-use it. - */ - final int notificationId = mMediaStorageNotification.icon; - notificationManager.cancel(notificationId); + // Jump back into the wizard flow if we moved to a real disk + final PendingIntent intent; + if (privateVol != null && privateVol.getDisk() != null) { + intent = buildWizardReadyPendingIntent(privateVol.getDisk()); + } else { + intent = buildVolumeSettingsPendingIntent(privateVol); } - if (visible) { - Resources r = Resources.getSystem(); - CharSequence title = r.getText(titleId); - CharSequence message = r.getText(messageId); + final Notification notif = new Notification.Builder(mContext) + .setSmallIcon(R.drawable.stat_notify_sdcard) + .setColor(mContext.getColor(R.color.system_notification_accent_color)) + .setContentTitle(title) + .setContentText(text) + .setContentIntent(intent) + .setStyle(new Notification.BigTextStyle().bigText(text)) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .setLocalOnly(true) + .setCategory(Notification.CATEGORY_SYSTEM) + .setPriority(Notification.PRIORITY_LOW) + .setAutoCancel(true) + .build(); + + mNotificationManager.notifyAsUser(move.packageName, MOVE_ID, notif, UserHandle.ALL); + } - if (mMediaStorageNotification == null) { - mMediaStorageNotification = new Notification(); - mMediaStorageNotification.when = 0; + private int getSmallIcon(DiskInfo disk, int state) { + if (disk.isSd()) { + switch (state) { + case VolumeInfo.STATE_CHECKING: + case VolumeInfo.STATE_EJECTING: + return R.drawable.stat_notify_sdcard_prepare; + default: + return R.drawable.stat_notify_sdcard; } + } else if (disk.isUsb()) { + return R.drawable.stat_sys_data_usb; + } else { + return R.drawable.stat_notify_sdcard; + } + } - mMediaStorageNotification.defaults &= ~Notification.DEFAULT_SOUND; + private Notification.Builder buildNotificationBuilder(VolumeInfo vol, CharSequence title, + CharSequence text) { + return new Notification.Builder(mContext) + .setSmallIcon(getSmallIcon(vol.getDisk(), vol.getState())) + .setColor(mContext.getColor(R.color.system_notification_accent_color)) + .setContentTitle(title) + .setContentText(text) + .setStyle(new Notification.BigTextStyle().bigText(text)) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .setLocalOnly(true); + } - if (dismissable) { - mMediaStorageNotification.flags = Notification.FLAG_AUTO_CANCEL; - } else { - mMediaStorageNotification.flags = Notification.FLAG_ONGOING_EVENT; - } + private PendingIntent buildInitPendingIntent(DiskInfo disk) { + final Intent intent = new Intent(); + intent.setClassName("com.android.settings", + "com.android.settings.deviceinfo.StorageWizardInit"); + intent.putExtra(DiskInfo.EXTRA_DISK_ID, disk.getId()); - mMediaStorageNotification.tickerText = title; - if (pi == null) { - Intent intent = new Intent(); - pi = PendingIntent.getBroadcastAsUser(mContext, 0, intent, 0, - UserHandle.CURRENT); - } + final int requestKey = disk.getId().hashCode(); + return PendingIntent.getActivityAsUser(mContext, requestKey, intent, + PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT); + } - mMediaStorageNotification.icon = icon; - mMediaStorageNotification.color = mContext.getResources().getColor( - com.android.internal.R.color.system_notification_accent_color); - mMediaStorageNotification.setLatestEventInfo(mContext, title, message, pi); - mMediaStorageNotification.visibility = Notification.VISIBILITY_PUBLIC; - mMediaStorageNotification.category = Notification.CATEGORY_SYSTEM; - } + private PendingIntent buildInitPendingIntent(VolumeInfo vol) { + final Intent intent = new Intent(); + intent.setClassName("com.android.settings", + "com.android.settings.deviceinfo.StorageWizardInit"); + intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.getId()); - final int notificationId = mMediaStorageNotification.icon; - if (visible) { - notificationManager.notifyAsUser(null, notificationId, - mMediaStorageNotification, UserHandle.ALL); - } else { - notificationManager.cancelAsUser(null, notificationId, UserHandle.ALL); + final int requestKey = vol.getId().hashCode(); + return PendingIntent.getActivityAsUser(mContext, requestKey, intent, + PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT); + } + + private PendingIntent buildUnmountPendingIntent(VolumeInfo vol) { + final Intent intent = new Intent(); + intent.setClassName("com.android.settings", + "com.android.settings.deviceinfo.StorageUnmountReceiver"); + intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.getId()); + + final int requestKey = vol.getId().hashCode(); + return PendingIntent.getBroadcastAsUser(mContext, requestKey, intent, + PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.CURRENT); + } + + private PendingIntent buildBrowsePendingIntent(VolumeInfo vol) { + final Intent intent = vol.buildBrowseIntent(); + + final int requestKey = vol.getId().hashCode(); + return PendingIntent.getActivityAsUser(mContext, requestKey, intent, + PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT); + } + + private PendingIntent buildVolumeSettingsPendingIntent(VolumeInfo vol) { + final Intent intent = new Intent(); + switch (vol.getType()) { + case VolumeInfo.TYPE_PRIVATE: + intent.setClassName("com.android.settings", + "com.android.settings.Settings$PrivateVolumeSettingsActivity"); + break; + case VolumeInfo.TYPE_PUBLIC: + intent.setClassName("com.android.settings", + "com.android.settings.Settings$PublicVolumeSettingsActivity"); + break; + default: + return null; } + intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.getId()); + + final int requestKey = vol.getId().hashCode(); + return PendingIntent.getActivityAsUser(mContext, requestKey, intent, + PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT); + } + + private PendingIntent buildSnoozeIntent(VolumeInfo vol) { + final Intent intent = new Intent(ACTION_SNOOZE_VOLUME); + intent.putExtra(VolumeRecord.EXTRA_FS_UUID, vol.getFsUuid()); + + final int requestKey = vol.getId().hashCode(); + return PendingIntent.getBroadcastAsUser(mContext, requestKey, intent, + PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.CURRENT); + } + + private PendingIntent buildForgetPendingIntent(VolumeRecord rec) { + final Intent intent = new Intent(); + intent.setClassName("com.android.settings", + "com.android.settings.Settings$PrivateVolumeForgetActivity"); + intent.putExtra(VolumeRecord.EXTRA_FS_UUID, rec.getFsUuid()); + + final int requestKey = rec.getFsUuid().hashCode(); + return PendingIntent.getActivityAsUser(mContext, requestKey, intent, + PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT); + } + + private PendingIntent buildWizardMigratePendingIntent(MoveInfo move) { + final Intent intent = new Intent(); + intent.setClassName("com.android.settings", + "com.android.settings.deviceinfo.StorageWizardMigrateProgress"); + intent.putExtra(PackageManager.EXTRA_MOVE_ID, move.moveId); + + final VolumeInfo vol = mStorageManager.findVolumeByQualifiedUuid(move.volumeUuid); + intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.getId()); + + return PendingIntent.getActivityAsUser(mContext, move.moveId, intent, + PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT); + } + + private PendingIntent buildWizardMovePendingIntent(MoveInfo move) { + final Intent intent = new Intent(); + intent.setClassName("com.android.settings", + "com.android.settings.deviceinfo.StorageWizardMoveProgress"); + intent.putExtra(PackageManager.EXTRA_MOVE_ID, move.moveId); + + return PendingIntent.getActivityAsUser(mContext, move.moveId, intent, + PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT); + } + + private PendingIntent buildWizardReadyPendingIntent(DiskInfo disk) { + final Intent intent = new Intent(); + intent.setClassName("com.android.settings", + "com.android.settings.deviceinfo.StorageWizardReady"); + intent.putExtra(DiskInfo.EXTRA_DISK_ID, disk.getId()); + + final int requestKey = disk.getId().hashCode(); + return PendingIntent.getActivityAsUser(mContext, requestKey, intent, + PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT); } } diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java index 9928f7f..23a65e8 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java @@ -93,7 +93,8 @@ public class UsbResolverActivity extends ResolverActivity { } @Override - protected void onIntentSelected(ResolveInfo ri, Intent intent, boolean alwaysCheck) { + protected void onTargetSelected(TargetInfo target, boolean alwaysCheck) { + final ResolveInfo ri = target.getResolveInfo(); try { IBinder b = ServiceManager.getService(USB_SERVICE); IUsbManager service = IUsbManager.Stub.asInterface(b); @@ -121,7 +122,7 @@ public class UsbResolverActivity extends ResolverActivity { } try { - startActivityAsUser(intent, new UserHandle(userId)); + target.startAsUser(this, null, new UserHandle(userId)); } catch (ActivityNotFoundException e) { Log.e(TAG, "startActivity failed", e); } 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/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..f7cb9fe 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/SegmentedButtons.java +++ b/packages/SystemUI/src/com/android/systemui/volume/SegmentedButtons.java @@ -17,6 +17,7 @@ package com.android.systemui.volume; import android.content.Context; +import android.graphics.Typeface; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; @@ -30,6 +31,8 @@ import java.util.Objects; public class SegmentedButtons extends LinearLayout { private static final int LABEL_RES_KEY = R.id.label; + private static final Typeface REGULAR = Typeface.create("sans-serif", Typeface.NORMAL); + private static final Typeface MEDIUM = Typeface.create("sans-serif-medium", Typeface.NORMAL); private final Context mContext; private final LayoutInflater mInflater; @@ -60,17 +63,15 @@ public class SegmentedButtons extends LinearLayout { final Object tag = c.getTag(); final boolean selected = Objects.equals(mSelectedValue, tag); c.setSelected(selected); - c.getCompoundDrawables()[1].setTint(mContext.getResources().getColor(selected - ? R.color.segmented_button_selected : R.color.segmented_button_unselected)); + c.setTypeface(selected ? MEDIUM : REGULAR); } fireOnSelected(); } - 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..4214091 --- /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.view.View; +import android.widget.TextView; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.Objects; + +/** + * Static helpers for the volume dialog. + */ +class Util { + + // Note: currently not shown (only used in the text footer) + private static final SimpleDateFormat HMMAA = new SimpleDateFormat("h:mm aa", Locale.US); + + private static int[] AUDIO_MANAGER_FLAGS = new int[] { + AudioManager.FLAG_SHOW_UI, + AudioManager.FLAG_VIBRATE, + AudioManager.FLAG_PLAY_SOUND, + AudioManager.FLAG_ALLOW_RINGER_MODES, + AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE, + AudioManager.FLAG_SHOW_VIBRATE_HINT, + AudioManager.FLAG_SHOW_SILENT_HINT, + AudioManager.FLAG_FROM_KEY, + AudioManager.FLAG_SHOW_UI_WARNINGS, + }; + + private static String[] AUDIO_MANAGER_FLAG_NAMES = new String[] { + "SHOW_UI", + "VIBRATE", + "PLAY_SOUND", + "ALLOW_RINGER_MODES", + "REMOVE_SOUND_AND_VIBRATE", + "SHOW_VIBRATE_HINT", + "SHOW_SILENT_HINT", + "FROM_KEY", + "SHOW_UI_WARNINGS", + }; + + public static String logTag(Class<?> c) { + final String tag = "vol." + c.getSimpleName(); + return tag.length() < 23 ? tag : tag.substring(0, 23); + } + + public static String ringerModeToString(int ringerMode) { + switch (ringerMode) { + case AudioManager.RINGER_MODE_SILENT: return "RINGER_MODE_SILENT"; + case AudioManager.RINGER_MODE_VIBRATE: return "RINGER_MODE_VIBRATE"; + case AudioManager.RINGER_MODE_NORMAL: return "RINGER_MODE_NORMAL"; + default: return "RINGER_MODE_UNKNOWN_" + ringerMode; + } + } + + public static String mediaMetadataToString(MediaMetadata metadata) { + return metadata.getDescription().toString(); + } + + public static String playbackInfoToString(PlaybackInfo info) { + if (info == null) return null; + final String type = playbackInfoTypeToString(info.getPlaybackType()); + final String vc = volumeProviderControlToString(info.getVolumeControl()); + return String.format("PlaybackInfo[vol=%s,max=%s,type=%s,vc=%s],atts=%s", + info.getCurrentVolume(), info.getMaxVolume(), type, vc, info.getAudioAttributes()); + } + + public static String playbackInfoTypeToString(int type) { + switch (type) { + case PlaybackInfo.PLAYBACK_TYPE_LOCAL: return "LOCAL"; + case PlaybackInfo.PLAYBACK_TYPE_REMOTE: return "REMOTE"; + default: return "UNKNOWN_" + type; + } + } + + public static String playbackStateStateToString(int state) { + switch (state) { + case PlaybackState.STATE_NONE: return "STATE_NONE"; + case PlaybackState.STATE_STOPPED: return "STATE_STOPPED"; + case PlaybackState.STATE_PAUSED: return "STATE_PAUSED"; + case PlaybackState.STATE_PLAYING: return "STATE_PLAYING"; + default: return "UNKNOWN_" + state; + } + } + + public static String volumeProviderControlToString(int control) { + switch (control) { + case VolumeProvider.VOLUME_CONTROL_ABSOLUTE: return "VOLUME_CONTROL_ABSOLUTE"; + case VolumeProvider.VOLUME_CONTROL_FIXED: return "VOLUME_CONTROL_FIXED"; + case VolumeProvider.VOLUME_CONTROL_RELATIVE: return "VOLUME_CONTROL_RELATIVE"; + default: return "VOLUME_CONTROL_UNKNOWN_" + control; + } + } + + public static String playbackStateToString(PlaybackState playbackState) { + if (playbackState == null) return null; + return playbackStateStateToString(playbackState.getState()) + " " + playbackState; + } + + public static String audioManagerFlagsToString(int value) { + return bitFieldToString(value, AUDIO_MANAGER_FLAGS, AUDIO_MANAGER_FLAG_NAMES); + } + + private static String bitFieldToString(int value, int[] values, String[] names) { + if (value == 0) return ""; + final StringBuilder sb = new StringBuilder(); + for (int i = 0; i < values.length; i++) { + if ((value & values[i]) != 0) { + if (sb.length() > 0) sb.append(','); + sb.append(names[i]); + } + value &= ~values[i]; + } + if (value != 0) { + if (sb.length() > 0) sb.append(','); + sb.append("UNKNOWN_").append(value); + } + return sb.toString(); + } + + public static String getShortTime(long millis) { + return HMMAA.format(new Date(millis)); + } + + private static CharSequence emptyToNull(CharSequence str) { + return str == null || str.length() == 0 ? null : str; + } + + public static boolean setText(TextView tv, CharSequence text) { + if (Objects.equals(emptyToNull(tv.getText()), emptyToNull(text))) return false; + tv.setText(text); + return true; + } + + public static final void setVisOrGone(View v, boolean vis) { + if (v == null || (v.getVisibility() == View.VISIBLE) == vis) return; + v.setVisibility(vis ? View.VISIBLE : View.GONE); + } + + public static final void setVisOrInvis(View v, boolean vis) { + if (v == null || (v.getVisibility() == View.VISIBLE) == vis) return; + v.setVisibility(vis ? View.VISIBLE : View.INVISIBLE); + } + +} 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..9434036 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java @@ -0,0 +1,963 @@ +/* + * 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.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.ImageButton; +import android.widget.LinearLayout; +import android.widget.SeekBar; +import android.widget.SeekBar.OnSeekBarChangeListener; +import android.widget.TextView; + +import com.android.systemui.R; +import com.android.systemui.statusbar.policy.ZenModeController; +import com.android.systemui.volume.VolumeDialogController.State; +import com.android.systemui.volume.VolumeDialogController.StreamState; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +/** + * Visual presentation of the volume dialog. + * + * A client of VolumeDialogController and its state model. + * + * Methods ending in "H" must be called on the (ui) handler. + */ +public class VolumeDialog { + private static final String TAG = Util.logTag(VolumeDialog.class); + + private static final long USER_ATTEMPT_GRACE_PERIOD = 1000; + private static final int WAIT_FOR_RIPPLE = 200; + private static final int UPDATE_ANIMATION_DURATION = 80; + + private final Context mContext; + private final H mHandler = new H(); + private final VolumeDialogController mController; + + private final CustomDialog mDialog; + private final ViewGroup mDialogView; + private final ViewGroup mDialogContentView; + private final ImageButton mExpandButton; + private final View mSettingsButton; + private final List<VolumeRow> mRows = new ArrayList<VolumeRow>(); + private final SpTexts mSpTexts; + private final SparseBooleanArray mDynamic = new SparseBooleanArray(); + private final KeyguardManager mKeyguard; + private final int mExpandButtonAnimationDuration; + private final ZenFooter mZenFooter; + private final LayoutTransition mLayoutTransition; + private final Object mSafetyWarningLock = new Object(); + + private boolean mShowing; + private boolean mExpanded; + private int mActiveStream; + private boolean mShowHeaders = VolumePrefs.DEFAULT_SHOW_HEADERS; + private boolean mAutomute = VolumePrefs.DEFAULT_ENABLE_AUTOMUTE; + private boolean mSilentMode = VolumePrefs.DEFAULT_ENABLE_SILENT_MODE; + private State mState; + private int mExpandButtonRes; + private boolean mExpanding; + private SafetyWarningDialog mSafetyWarning; + private Callback mCallback; + + public VolumeDialog(Context context, int windowType, VolumeDialogController controller, + ZenModeController zenModeController, Callback callback) { + mContext = context; + mController = controller; + mCallback = callback; + mSpTexts = new SpTexts(mContext); + mKeyguard = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); + + mDialog = new CustomDialog(mContext); + + final Window window = mDialog.getWindow(); + window.requestFeature(Window.FEATURE_NO_TITLE); + window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); + window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED + | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH + | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); + mDialog.setCanceledOnTouchOutside(true); + final Resources res = mContext.getResources(); + final WindowManager.LayoutParams lp = window.getAttributes(); + lp.type = windowType; + lp.format = PixelFormat.TRANSLUCENT; + lp.setTitle(VolumeDialog.class.getSimpleName()); + lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL; + lp.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); + + 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); + + 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 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(" 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(); + } + + 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 (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(); + if (!mShowing) { + trimObsoleteH(); + } + // apply changes to all rows + for (VolumeRow row : mRows) { + final boolean isActive = row == activeRow; + final boolean visible = isVisibleH(row, isActive); + Util.setVisOrGone(row.view, visible); + Util.setVisOrGone(row.space, visible && mExpanded); + final int expandButtonRes = mExpanded ? R.drawable.ic_volume_settings : 0; + if (expandButtonRes != row.cachedExpandButtonRes) { + row.cachedExpandButtonRes = expandButtonRes; + if (expandButtonRes == 0) { + row.settingsButton.setImageDrawable(null); + } else { + row.settingsButton.setImageResource(expandButtonRes); + } + } + Util.setVisOrInvis(row.settingsButton, false); + 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 updateFooterH() { + Util.setVisOrGone(mZenFooter, mState.zenMode != Global.ZEN_MODE_OFF); + mZenFooter.update(); + } + + 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 isAlarmStream = row.stream == AudioManager.STREAM_ALARM; + final boolean isMusicStream = row.stream == AudioManager.STREAM_MUSIC; + final boolean isRingVibrate = isRingStream + && mState.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE; + final boolean isRingSilent = isRingStream + && mState.ringerModeInternal == AudioManager.RINGER_MODE_SILENT; + final boolean isZenAlarms = mState.zenMode == Global.ZEN_MODE_ALARMS; + final boolean isZenNone = mState.zenMode == Global.ZEN_MODE_NO_INTERRUPTIONS; + final boolean isZenPriority = mState.zenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + final boolean isRingZenNone = (isRingStream || isSystemStream) && isZenNone; + final boolean isRingLimited = isRingStream && isZenPriority; + final boolean zenMuted = isZenAlarms ? (isRingStream || isSystemStream) + : isZenNone ? (isRingStream || isSystemStream || isAlarmStream || isMusicStream) + : false; + + // update slider max + final int max = ss.levelMax * 100; + if (max != row.slider.getMax()) { + row.slider.setMax(max); + } + + // update header visible + if (row.cachedShowHeaders != mShowHeaders) { + row.cachedShowHeaders = mShowHeaders; + Util.setVisOrGone(row.header, mShowHeaders); + } + + // update header text + final String text; + if (isRingZenNone) { + text = mContext.getString(R.string.volume_stream_muted_dnd, ss.name); + } else if (isRingVibrate && isRingLimited) { + text = mContext.getString(R.string.volume_stream_vibrate_dnd, ss.name); + } else if (isRingVibrate) { + text = mContext.getString(R.string.volume_stream_vibrate, ss.name); + } else if (ss.muted || mAutomute && ss.level == 0) { + text = mContext.getString(R.string.volume_stream_muted, ss.name); + } else if (isRingLimited) { + text = mContext.getString(R.string.volume_stream_limited_dnd, ss.name); + } else { + text = ss.name; + } + Util.setText(row.header, text); + + // update icon + final boolean iconEnabled = (mAutomute || ss.muteSupported) && !zenMuted; + row.icon.setEnabled(iconEnabled); + row.icon.setAlpha(iconEnabled ? 1 : 0.5f); + final int iconRes = + isRingVibrate ? R.drawable.ic_volume_ringer_vibrate + : isRingSilent || zenMuted ? row.cachedIconRes + : ss.routedToBluetooth ? + (ss.muted ? R.drawable.ic_volume_media_bt_mute + : R.drawable.ic_volume_media_bt) + : mAutomute && ss.level == 0 ? row.iconMuteRes + : (ss.muted ? row.iconMuteRes : row.iconRes); + if (iconRes != row.cachedIconRes) { + if (row.cachedIconRes != 0 && isRingVibrate) { + mController.vibrate(); + } + row.cachedIconRes = iconRes; + row.icon.setImageResource(iconRes); + } + row.iconState = + iconRes == R.drawable.ic_volume_ringer_vibrate ? Events.ICON_STATE_VIBRATE + : (iconRes == R.drawable.ic_volume_media_bt_mute || iconRes == row.iconMuteRes) + ? Events.ICON_STATE_MUTE + : (iconRes == R.drawable.ic_volume_media_bt || iconRes == row.iconRes) + ? Events.ICON_STATE_UNMUTE + : Events.ICON_STATE_UNKNOWN; + + // update slider + updateVolumeRowSliderH(row, zenMuted); + } + + private void updateVolumeRowSliderH(VolumeRow row, boolean zenMuted) { + row.slider.setEnabled(!zenMuted); + 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); + if (mCallback != null) { + mCallback.onSettingsClicked(); + } + } + }, WAIT_FOR_RIPPLE); + } + }; + + private final class H extends Handler { + private static final int SHOW = 1; + private static final int DISMISS = 2; + private static final int RECHECK = 3; + private static final int RECHECK_ALL = 4; + private static final int SET_STREAM_IMPORTANT = 5; + private static final int RESCHEDULE_TIMEOUT = 6; + + 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 = VolumePrefs.DEFAULT_SHOW_HEADERS; + private int cachedExpandButtonRes; + private ObjectAnimator anim; // slider progress animation for non-touch-related updates + private int animTargetProgress; + private int lastAudibleLevel = 1; + } + + public interface Callback { + void onSettingsClicked(); + void onZenSettingsClicked(); + void onZenPrioritySettingsClicked(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java new file mode 100644 index 0000000..1083f40 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume; + +import android.content.Context; +import android.content.Intent; +import android.content.res.Configuration; +import android.media.AudioManager; +import android.media.VolumePolicy; +import android.os.Bundle; +import android.os.Handler; +import android.provider.Settings; +import android.view.WindowManager; + +import com.android.systemui.SystemUI; +import com.android.systemui.keyguard.KeyguardViewMediator; +import com.android.systemui.qs.tiles.DndTile; +import com.android.systemui.statusbar.phone.PhoneStatusBar; +import com.android.systemui.statusbar.policy.ZenModeController; + +import java.io.FileDescriptor; +import java.io.PrintWriter; + +/** + * Implementation of VolumeComponent backed by the new volume dialog. + */ +public class VolumeDialogComponent implements VolumeComponent { + private final SystemUI mSysui; + private final Context mContext; + private final VolumeDialogController mController; + private final ZenModeController mZenModeController; + private final VolumeDialog mDialog; + private final VolumePolicy mVolumePolicy = new VolumePolicy( + true, // volumeDownToEnterSilent + true, // volumeUpToExitSilent + true, // doNotDisturbWhenSilent + 400 // vibrateToSilentDebounce + ); + + public VolumeDialogComponent(SystemUI sysui, Context context, Handler handler, + ZenModeController zen) { + mSysui = sysui; + mContext = context; + mController = new VolumeDialogController(context, null) { + @Override + protected void onUserActivityW() { + sendUserActivity(); + } + }; + mZenModeController = zen; + mDialog = new VolumeDialog(context, WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY, + mController, zen, mVolumeDialogCallback); + applyConfiguration(); + } + + private void sendUserActivity() { + final KeyguardViewMediator kvm = mSysui.getComponent(KeyguardViewMediator.class); + if (kvm != null) { + kvm.userActivity(); + } + } + + private void applyConfiguration() { + mDialog.setStreamImportant(AudioManager.STREAM_ALARM, true); + mDialog.setStreamImportant(AudioManager.STREAM_SYSTEM, false); + mDialog.setShowHeaders(false); + mDialog.setAutomute(true); + mDialog.setSilentMode(false); + mController.setVolumePolicy(mVolumePolicy); + mController.showDndTile(true); + } + + @Override + public ZenModeController getZenController() { + return mZenModeController; + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + // noop + } + + @Override + public void dismissNow() { + mController.dismiss(); + } + + @Override + public void dispatchDemoCommand(String command, Bundle args) { + // noop + } + + @Override + public void register() { + mController.register(); + DndTile.setCombinedIcon(mContext, true); + } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + mController.dump(fd, pw, args); + mDialog.dump(pw); + } + + private void startSettings(Intent intent) { + mSysui.getComponent(PhoneStatusBar.class).startActivityDismissingKeyguard(intent, + true /* onlyProvisioned */, true /* dismissShade */); + } + + private final VolumeDialog.Callback mVolumeDialogCallback = new VolumeDialog.Callback() { + @Override + public void onSettingsClicked() { + startSettings(new Intent(Settings.ACTION_NOTIFICATION_SETTINGS)); + } + + @Override + public void onZenSettingsClicked() { + startSettings(ZenModePanel.ZEN_SETTINGS); + } + + @Override + public void onZenPrioritySettingsClicked() { + startSettings(ZenModePanel.ZEN_PRIORITY_SETTINGS); + } + }; + +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java new file mode 100644 index 0000000..3a8081f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java @@ -0,0 +1,999 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume; + +import android.app.NotificationManager; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.database.ContentObserver; +import android.media.AudioManager; +import android.media.AudioSystem; +import android.media.IVolumeController; +import android.media.VolumePolicy; +import android.media.session.MediaController.PlaybackInfo; +import android.media.session.MediaSession.Token; +import android.net.Uri; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.os.Vibrator; +import android.provider.Settings; +import android.service.notification.Condition; +import android.service.notification.ZenModeConfig; +import android.util.Log; +import android.util.SparseArray; + +import com.android.systemui.R; +import com.android.systemui.qs.tiles.DndTile; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * Source of truth for all state / events related to the volume dialog. No presentation. + * + * All work done on a dedicated background worker thread & associated worker. + * + * Methods ending in "W" must be called on the worker thread. + */ +public class VolumeDialogController { + private static final String TAG = Util.logTag(VolumeDialogController.class); + + private static final int DYNAMIC_STREAM_START_INDEX = 100; + private static final int VIBRATE_HINT_DURATION = 50; + + private static final int[] STREAMS = { + AudioSystem.STREAM_ALARM, + AudioSystem.STREAM_BLUETOOTH_SCO, + AudioSystem.STREAM_DTMF, + AudioSystem.STREAM_MUSIC, + AudioSystem.STREAM_NOTIFICATION, + AudioSystem.STREAM_RING, + AudioSystem.STREAM_SYSTEM, + AudioSystem.STREAM_SYSTEM_ENFORCED, + AudioSystem.STREAM_TTS, + AudioSystem.STREAM_VOICE_CALL, + }; + + private final HandlerThread mWorkerThread; + private final W mWorker; + private final Context mContext; + private final AudioManager mAudio; + private final NotificationManager mNoMan; + private final ComponentName mComponent; + private final SettingObserver mObserver; + private final Receiver mReceiver = new Receiver(); + private final MediaSessions mMediaSessions; + private final VC mVolumeController = new VC(); + private final C mCallbacks = new C(); + private final State mState = new State(); + private final String[] mStreamTitles; + private final MediaSessionsCallbacks mMediaSessionsCallbacksW = new MediaSessionsCallbacks(); + private final Vibrator mVibrator; + private final boolean mHasVibrator; + + private boolean mEnabled; + private boolean mDestroyed; + private VolumePolicy mVolumePolicy; + private boolean mShowDndTile = true; + + public VolumeDialogController(Context context, ComponentName component) { + mContext = context.getApplicationContext(); + Events.writeEvent(Events.EVENT_COLLECTION_STARTED); + mComponent = component; + mWorkerThread = new HandlerThread(VolumeDialogController.class.getSimpleName()); + mWorkerThread.start(); + mWorker = new W(mWorkerThread.getLooper()); + mMediaSessions = createMediaSessions(mContext, mWorkerThread.getLooper(), + mMediaSessionsCallbacksW); + mAudio = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + mNoMan = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); + mObserver = new SettingObserver(mWorker); + mObserver.init(); + mReceiver.init(); + mStreamTitles = mContext.getResources().getStringArray(R.array.volume_stream_titles); + mVibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE); + mHasVibrator = mVibrator != null && mVibrator.hasVibrator(); + } + + public AudioManager getAudioManager() { + return mAudio; + } + + public ZenModeConfig getZenModeConfig() { + return mNoMan.getZenModeConfig(); + } + + public void dismiss() { + mCallbacks.onDismissRequested(Events.DISMISS_REASON_VOLUME_CONTROLLER); + } + + public void register() { + try { + mAudio.setVolumeController(mVolumeController); + } catch (SecurityException e) { + Log.w(TAG, "Unable to set the volume controller", e); + return; + } + setVolumePolicy(mVolumePolicy); + showDndTile(mShowDndTile); + try { + mMediaSessions.init(); + } catch (SecurityException e) { + Log.w(TAG, "No access to media sessions", e); + } + } + + public void setVolumePolicy(VolumePolicy policy) { + mVolumePolicy = policy; + if (mVolumePolicy == null) return; + try { + mAudio.setVolumePolicy(mVolumePolicy); + } catch (NoSuchMethodError e) { + Log.w(TAG, "No volume policy api"); + } + } + + protected MediaSessions createMediaSessions(Context context, Looper looper, + MediaSessions.Callbacks callbacks) { + return new MediaSessions(context, looper, callbacks); + } + + public void destroy() { + if (D.BUG) Log.d(TAG, "destroy"); + if (mDestroyed) return; + mDestroyed = true; + Events.writeEvent(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()); + updateZenModeConfigW(); + mCallbacks.onStateChanged(mState); + } + + private boolean updateStreamRoutedToBluetoothW(int stream, boolean routedToBluetooth) { + final StreamState ss = streamStateW(stream); + if (ss.routedToBluetooth == routedToBluetooth) return false; + ss.routedToBluetooth = routedToBluetooth; + if (D.BUG) Log.d(TAG, "updateStreamRoutedToBluetoothW stream=" + stream + + " routedToBluetooth=" + routedToBluetooth); + return true; + } + + private boolean updateStreamLevelW(int stream, int level) { + final StreamState ss = streamStateW(stream); + if (ss.level == level) return false; + ss.level = level; + if (isLogWorthy(stream)) { + Events.writeEvent(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 updateZenModeConfigW() { + final ZenModeConfig zenModeConfig = getZenModeConfig(); + if (Objects.equals(mState.zenModeConfig, zenModeConfig)) return false; + mState.zenModeConfig = zenModeConfig; + return true; + } + + private boolean updateEffectsSuppressorW(ComponentName effectsSuppressor) { + if (Objects.equals(mState.effectsSuppressor, effectsSuppressor)) return false; + mState.effectsSuppressor = effectsSuppressor; + mState.effectsSuppressorName = getApplicationName(mContext, mState.effectsSuppressor); + Events.writeEvent(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.setZenMode(mState.zenMode, condition != null ? condition.id : null, TAG); + } + + private void onSetZenModeW(int mode) { + if (D.BUG) Log.d(TAG, "onSetZenModeW " + mode); + mNoMan.setZenMode(mode, null, TAG); + } + + private void onDismissRequestedW(int reason) { + mCallbacks.onDismissRequested(reason); + } + + public void showDndTile(boolean visible) { + if (D.BUG) Log.d(TAG, "showDndTile"); + DndTile.setVisible(mContext, visible); + } + + private final class VC extends IVolumeController.Stub { + private final String TAG = VolumeDialogController.TAG + ".VC"; + + @Override + public void displaySafeVolumeWarning(int flags) throws RemoteException { + if (D.BUG) Log.d(TAG, "displaySafeVolumeWarning " + + Util.audioManagerFlagsToString(flags)); + if (mDestroyed) return; + mWorker.obtainMessage(W.SHOW_SAFETY_WARNING, flags, 0).sendToTarget(); + } + + @Override + public void volumeChanged(int streamType, int flags) throws RemoteException { + if (D.BUG) Log.d(TAG, "volumeChanged " + AudioSystem.streamToString(streamType) + + " " + Util.audioManagerFlagsToString(flags)); + if (mDestroyed) return; + mWorker.obtainMessage(W.VOLUME_CHANGED, streamType, flags).sendToTarget(); + } + + @Override + public void masterMuteChanged(int flags) throws RemoteException { + if (D.BUG) Log.d(TAG, "masterMuteChanged"); + } + + @Override + public void setLayoutDirection(int layoutDirection) throws RemoteException { + if (D.BUG) Log.d(TAG, "setLayoutDirection"); + if (mDestroyed) return; + mWorker.obtainMessage(W.LAYOUT_DIRECTION_CHANGED, layoutDirection, 0).sendToTarget(); + } + + @Override + public void dismiss() throws RemoteException { + if (D.BUG) Log.d(TAG, "dismiss requested"); + if (mDestroyed) return; + mWorker.obtainMessage(W.DISMISS_REQUESTED, Events.DISMISS_REASON_VOLUME_CONTROLLER, 0) + .sendToTarget(); + mWorker.sendEmptyMessage(W.DISMISS_REQUESTED); + } + } + + private final class W extends Handler { + private static final int VOLUME_CHANGED = 1; + private static final int DISMISS_REQUESTED = 2; + private static final int GET_STATE = 3; + private static final int SET_RINGER_MODE = 4; + private static final int SET_ZEN_MODE = 5; + private static final int SET_EXIT_CONDITION = 6; + private static final int SET_STREAM_MUTE = 7; + private static final int LAYOUT_DIRECTION_CHANGED = 8; + private static final int CONFIGURATION_CHANGED = 9; + private static final int SET_STREAM_VOLUME = 10; + private static final int SET_ACTIVE_STREAM = 11; + private static final int NOTIFY_VISIBLE = 12; + private static final int USER_ACTIVITY = 13; + private static final int SHOW_SAFETY_WARNING = 14; + + W(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case VOLUME_CHANGED: onVolumeChangedW(msg.arg1, msg.arg2); break; + case DISMISS_REQUESTED: onDismissRequestedW(msg.arg1); break; + case GET_STATE: onGetStateW(); break; + case SET_RINGER_MODE: onSetRingerModeW(msg.arg1, msg.arg2 != 0); break; + case SET_ZEN_MODE: onSetZenModeW(msg.arg1); break; + case SET_EXIT_CONDITION: onSetExitConditionW((Condition) msg.obj); break; + case SET_STREAM_MUTE: onSetStreamMuteW(msg.arg1, msg.arg2 != 0); break; + case LAYOUT_DIRECTION_CHANGED: mCallbacks.onLayoutDirectionChanged(msg.arg1); break; + case CONFIGURATION_CHANGED: mCallbacks.onConfigurationChanged(); break; + case SET_STREAM_VOLUME: onSetStreamVolumeW(msg.arg1, msg.arg2); break; + case SET_ACTIVE_STREAM: onSetActiveStreamW(msg.arg1); break; + case NOTIFY_VISIBLE: onNotifyVisibleW(msg.arg1 != 0); break; + case USER_ACTIVITY: onUserActivityW(); break; + case SHOW_SAFETY_WARNING: onShowSafetyWarningW(msg.arg1); break; + } + } + } + + private final class C implements Callbacks { + private final HashMap<Callbacks, Handler> mCallbackMap = new HashMap<>(); + + public void add(Callbacks callback, Handler handler) { + if (callback == null || handler == null) throw new IllegalArgumentException(); + mCallbackMap.put(callback, handler); + } + + public void remove(Callbacks callback) { + mCallbackMap.remove(callback); + } + + @Override + public void onShowRequested(final int reason) { + for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) { + entry.getValue().post(new Runnable() { + @Override + public void run() { + entry.getKey().onShowRequested(reason); + } + }); + } + } + + @Override + public void onDismissRequested(final int reason) { + for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) { + entry.getValue().post(new Runnable() { + @Override + public void run() { + entry.getKey().onDismissRequested(reason); + } + }); + } + } + + @Override + public void onStateChanged(final State state) { + final long time = System.currentTimeMillis(); + final State copy = state.copy(); + for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) { + entry.getValue().post(new Runnable() { + @Override + public void run() { + entry.getKey().onStateChanged(copy); + } + }); + } + Events.writeState(time, copy); + } + + @Override + public void onLayoutDirectionChanged(final int layoutDirection) { + for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) { + entry.getValue().post(new Runnable() { + @Override + public void run() { + entry.getKey().onLayoutDirectionChanged(layoutDirection); + } + }); + } + } + + @Override + public void onConfigurationChanged() { + for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) { + entry.getValue().post(new Runnable() { + @Override + public void run() { + entry.getKey().onConfigurationChanged(); + } + }); + } + } + + @Override + public void onShowVibrateHint() { + for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) { + entry.getValue().post(new Runnable() { + @Override + public void run() { + entry.getKey().onShowVibrateHint(); + } + }); + } + } + + @Override + public void onShowSilentHint() { + for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) { + entry.getValue().post(new Runnable() { + @Override + public void run() { + entry.getKey().onShowSilentHint(); + } + }); + } + } + + @Override + public void onScreenOff() { + for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) { + entry.getValue().post(new Runnable() { + @Override + public void run() { + entry.getKey().onScreenOff(); + } + }); + } + } + + @Override + public void onShowSafetyWarning(final int flags) { + for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) { + entry.getValue().post(new Runnable() { + @Override + public void run() { + entry.getKey().onShowSafetyWarning(flags); + } + }); + } + } + } + + + private final class SettingObserver extends ContentObserver { + private final Uri SERVICE_URI = Settings.Secure.getUriFor( + Settings.Secure.VOLUME_CONTROLLER_SERVICE_COMPONENT); + private final Uri ZEN_MODE_URI = + Settings.Global.getUriFor(Settings.Global.ZEN_MODE); + private final Uri ZEN_MODE_CONFIG_URI = + Settings.Global.getUriFor(Settings.Global.ZEN_MODE_CONFIG_ETAG); + + public SettingObserver(Handler handler) { + super(handler); + } + + public void init() { + mContext.getContentResolver().registerContentObserver(SERVICE_URI, false, this); + mContext.getContentResolver().registerContentObserver(ZEN_MODE_URI, false, this); + mContext.getContentResolver().registerContentObserver(ZEN_MODE_CONFIG_URI, false, this); + onChange(true, SERVICE_URI); + } + + public void destroy() { + mContext.getContentResolver().unregisterContentObserver(this); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + boolean changed = false; + if (SERVICE_URI.equals(uri)) { + final String setting = Settings.Secure.getString(mContext.getContentResolver(), + Settings.Secure.VOLUME_CONTROLLER_SERVICE_COMPONENT); + final boolean enabled = setting != null && mComponent != null + && mComponent.equals(ComponentName.unflattenFromString(setting)); + if (enabled == mEnabled) return; + if (enabled) { + register(); + } + mEnabled = enabled; + } + if (ZEN_MODE_URI.equals(uri)) { + changed = updateZenModeW(); + } + if (ZEN_MODE_CONFIG_URI.equals(uri)) { + changed = updateZenModeConfigW(); + } + if (changed) { + mCallbacks.onStateChanged(mState); + } + } + } + + private final class Receiver extends BroadcastReceiver { + + public void init() { + final IntentFilter filter = new IntentFilter(); + filter.addAction(AudioManager.VOLUME_CHANGED_ACTION); + filter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION); + filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); + filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION); + filter.addAction(AudioManager.STREAM_MUTE_CHANGED_ACTION); + filter.addAction(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED); + filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); + filter.addAction(Intent.ACTION_SCREEN_OFF); + 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 ZenModeConfig zenModeConfig; + public int activeStream = NO_ACTIVE_STREAM; + + public State copy() { + final State rt = new State(); + for (int i = 0; i < states.size(); i++) { + rt.states.put(states.keyAt(i), states.valueAt(i).copy()); + } + rt.ringerModeExternal = ringerModeExternal; + rt.ringerModeInternal = ringerModeInternal; + rt.zenMode = zenMode; + if (effectsSuppressor != null) rt.effectsSuppressor = effectsSuppressor.clone(); + rt.effectsSuppressorName = effectsSuppressorName; + if (zenModeConfig != null) rt.zenModeConfig = zenModeConfig.copy(); + rt.activeStream = activeStream; + return rt; + } + + @Override + public String toString() { + 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(",zenModeConfig:").append(zenModeConfig); + sb.append(",activeStream:").append(activeStream); + return sb.append('}').toString(); + } + + public Condition getManualExitCondition() { + return zenModeConfig != null && zenModeConfig.manualRule != null + ? zenModeConfig.manualRule.condition : null; + } + } + + public interface Callbacks { + void onShowRequested(int reason); + void onDismissRequested(int reason); + void onStateChanged(State state); + void onLayoutDirectionChanged(int layoutDirection); + void onConfigurationChanged(); + void onShowVibrateHint(); + void onShowSilentHint(); + void onScreenOff(); + void onShowSafetyWarning(int flags); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java deleted file mode 100644 index acdcfc1..0000000 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java +++ /dev/null @@ -1,1664 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.volume; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnDismissListener; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.PackageManager; -import android.content.pm.ServiceInfo; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.PixelFormat; -import android.graphics.drawable.ColorDrawable; -import android.media.AudioAttributes; -import android.media.AudioManager; -import android.media.AudioService; -import android.media.AudioSystem; -import android.media.RingtoneManager; -import android.media.ToneGenerator; -import android.media.VolumeProvider; -import android.media.session.MediaController; -import android.media.session.MediaController.PlaybackInfo; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.Debug; -import android.os.Handler; -import android.os.Message; -import android.os.Vibrator; -import android.util.Log; -import android.util.SparseArray; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.view.Window; -import android.view.WindowManager; -import android.view.WindowManager.LayoutParams; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityManager; -import android.view.animation.AnimationUtils; -import android.view.animation.Interpolator; -import android.widget.ImageView; -import android.widget.SeekBar; -import android.widget.SeekBar.OnSeekBarChangeListener; -import android.widget.TextView; - -import com.android.internal.R; -import com.android.systemui.DemoMode; -import com.android.systemui.statusbar.phone.SystemUIDialog; -import com.android.systemui.statusbar.policy.ZenModeController; - -import java.io.FileDescriptor; -import java.io.PrintWriter; - -/** - * Handles the user interface for the volume keys. - * - * @hide - */ -public class VolumePanel extends Handler implements DemoMode { - private static final String TAG = "VolumePanel"; - private static boolean LOGD = Log.isLoggable(TAG, Log.DEBUG); - - private static final int PLAY_SOUND_DELAY = AudioService.PLAY_SOUND_DELAY; - - /** - * The delay before vibrating. This small period exists so if the user is - * moving to silent mode, it will not emit a short vibrate (it normally - * would since vibrate is between normal mode and silent mode using hardware - * keys). - */ - public static final int VIBRATE_DELAY = 300; - - private static final int VIBRATE_DURATION = 300; - private static final int BEEP_DURATION = 150; - private static final int MAX_VOLUME = 100; - private static final int FREE_DELAY = 10000; - private static final int TIMEOUT_DELAY = 3000; - private static final int TIMEOUT_DELAY_SHORT = 1500; - private static final int TIMEOUT_DELAY_COLLAPSED = 4500; - private static final int TIMEOUT_DELAY_SAFETY_WARNING = 5000; - private static final int TIMEOUT_DELAY_EXPANDED = 10000; - - private static final int MSG_VOLUME_CHANGED = 0; - private static final int MSG_FREE_RESOURCES = 1; - private static final int MSG_PLAY_SOUND = 2; - private static final int MSG_STOP_SOUNDS = 3; - private static final int MSG_VIBRATE = 4; - private static final int MSG_TIMEOUT = 5; - private static final int MSG_RINGER_MODE_CHANGED = 6; - private static final int MSG_MUTE_CHANGED = 7; - private static final int MSG_REMOTE_VOLUME_CHANGED = 8; - private static final int MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN = 9; - private static final int MSG_SLIDER_VISIBILITY_CHANGED = 10; - private static final int MSG_DISPLAY_SAFE_VOLUME_WARNING = 11; - private static final int MSG_LAYOUT_DIRECTION = 12; - private static final int MSG_ZEN_MODE_AVAILABLE_CHANGED = 13; - private static final int MSG_USER_ACTIVITY = 14; - private static final int MSG_NOTIFICATION_EFFECTS_SUPPRESSOR_CHANGED = 15; - private static final int MSG_INTERNAL_RINGER_MODE_CHANGED = 16; - - // Pseudo stream type for master volume - private static final int STREAM_MASTER = -100; - // Pseudo stream type for remote volume - private static final int STREAM_REMOTE_MUSIC = -200; - - private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder() - .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) - .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) - .build(); - - private static final int IC_AUDIO_VOL = com.android.systemui.R.drawable.ic_audio_vol; - private static final int IC_AUDIO_VOL_MUTE = com.android.systemui.R.drawable.ic_audio_vol_mute; - private static final int IC_AUDIO_BT = com.android.systemui.R.drawable.ic_audio_bt; - private static final int IC_AUDIO_BT_MUTE = com.android.systemui.R.drawable.ic_audio_bt_mute; - - private final String mTag; - protected final Context mContext; - private final AudioManager mAudioManager; - private final ZenModeController mZenController; - private boolean mRingIsSilent; - private boolean mVoiceCapable; - private boolean mZenModeAvailable; - private boolean mZenPanelExpanded; - private int mTimeoutDelay = TIMEOUT_DELAY; - private float mDisabledAlpha; - private int mLastRingerMode = AudioManager.RINGER_MODE_NORMAL; - private int mLastRingerProgress = 0; - private int mDemoIcon; - - // True if we want to play tones on the system stream when the master stream is specified. - private final boolean mPlayMasterStreamTones; - - - /** Volume panel content view */ - private final View mView; - /** Dialog hosting the panel */ - private final Dialog mDialog; - - /** The visible portion of the volume overlay */ - private final ViewGroup mPanel; - /** Contains the slider and its touchable icons */ - private final ViewGroup mSliderPanel; - /** The zen mode configuration panel view */ - private ZenModePanel mZenPanel; - /** The component currently suppressing notification stream effects */ - private ComponentName mNotificationEffectsSuppressor; - - private Callback mCallback; - - /** Currently active stream that shows up at the top of the list of sliders */ - private int mActiveStreamType = -1; - /** All the slider controls mapped by stream type */ - private SparseArray<StreamControl> mStreamControls; - private final AccessibilityManager mAccessibilityManager; - private final SecondaryIconTransition mSecondaryIconTransition; - private final IconPulser mIconPulser; - - private enum StreamResources { - BluetoothSCOStream(AudioManager.STREAM_BLUETOOTH_SCO, - R.string.volume_icon_description_bluetooth, - IC_AUDIO_BT, - IC_AUDIO_BT_MUTE, - false), - RingerStream(AudioManager.STREAM_RING, - R.string.volume_icon_description_ringer, - com.android.systemui.R.drawable.ic_ringer_audible, - com.android.systemui.R.drawable.ic_ringer_mute, - false), - VoiceStream(AudioManager.STREAM_VOICE_CALL, - R.string.volume_icon_description_incall, - com.android.systemui.R.drawable.ic_audio_phone, - com.android.systemui.R.drawable.ic_audio_phone, - false), - AlarmStream(AudioManager.STREAM_ALARM, - R.string.volume_alarm, - com.android.systemui.R.drawable.ic_audio_alarm, - com.android.systemui.R.drawable.ic_audio_alarm_mute, - false), - MediaStream(AudioManager.STREAM_MUSIC, - R.string.volume_icon_description_media, - IC_AUDIO_VOL, - IC_AUDIO_VOL_MUTE, - true), - NotificationStream(AudioManager.STREAM_NOTIFICATION, - R.string.volume_icon_description_notification, - com.android.systemui.R.drawable.ic_ringer_audible, - com.android.systemui.R.drawable.ic_ringer_mute, - true), - // for now, use media resources for master volume - MasterStream(STREAM_MASTER, - R.string.volume_icon_description_media, //FIXME should have its own description - IC_AUDIO_VOL, - IC_AUDIO_VOL_MUTE, - false), - RemoteStream(STREAM_REMOTE_MUSIC, - R.string.volume_icon_description_media, //FIXME should have its own description - com.android.systemui.R.drawable.ic_audio_remote, - com.android.systemui.R.drawable.ic_audio_remote, - false);// will be dynamically updated - - int streamType; - int descRes; - int iconRes; - int iconMuteRes; - // RING, VOICE_CALL & BLUETOOTH_SCO are hidden unless explicitly requested - boolean show; - - StreamResources(int streamType, int descRes, int iconRes, int iconMuteRes, boolean show) { - this.streamType = streamType; - this.descRes = descRes; - this.iconRes = iconRes; - this.iconMuteRes = iconMuteRes; - this.show = show; - } - } - - // List of stream types and their order - private static final StreamResources[] STREAMS = { - StreamResources.BluetoothSCOStream, - StreamResources.RingerStream, - StreamResources.VoiceStream, - StreamResources.MediaStream, - StreamResources.NotificationStream, - StreamResources.AlarmStream, - StreamResources.MasterStream, - StreamResources.RemoteStream - }; - - /** Object that contains data for each slider */ - private class StreamControl { - int streamType; - MediaController controller; - ViewGroup group; - ImageView icon; - SeekBar seekbarView; - TextView suppressorView; - View divider; - ImageView secondaryIcon; - int iconRes; - int iconMuteRes; - int iconSuppressedRes; - } - - // Synchronize when accessing this - private ToneGenerator mToneGenerators[]; - private Vibrator mVibrator; - private boolean mHasVibrator; - - private static AlertDialog sSafetyWarning; - private static Object sSafetyWarningLock = new Object(); - - private static class SafetyWarning extends SystemUIDialog - implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener { - private final Context mContext; - private final VolumePanel mVolumePanel; - private final AudioManager mAudioManager; - - private boolean mNewVolumeUp; - - SafetyWarning(Context context, VolumePanel volumePanel, AudioManager audioManager) { - super(context); - mContext = context; - mVolumePanel = volumePanel; - mAudioManager = audioManager; - - setMessage(mContext.getString(com.android.internal.R.string.safe_media_volume_warning)); - setButton(DialogInterface.BUTTON_POSITIVE, - mContext.getString(com.android.internal.R.string.yes), this); - setButton(DialogInterface.BUTTON_NEGATIVE, - mContext.getString(com.android.internal.R.string.no), (OnClickListener) null); - setOnDismissListener(this); - - IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); - context.registerReceiver(mReceiver, filter); - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_VOLUME_UP && event.getRepeatCount() == 0) { - mNewVolumeUp = true; - } - return super.onKeyDown(keyCode, event); - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_VOLUME_UP && mNewVolumeUp) { - if (LOGD) Log.d(TAG, "Confirmed warning via VOLUME_UP"); - mAudioManager.disableSafeMediaVolume(); - dismiss(); - } - return super.onKeyUp(keyCode, event); - } - - @Override - public void onClick(DialogInterface dialog, int which) { - mAudioManager.disableSafeMediaVolume(); - } - - @Override - public void onDismiss(DialogInterface unused) { - mContext.unregisterReceiver(mReceiver); - cleanUp(); - } - - private void cleanUp() { - synchronized (sSafetyWarningLock) { - sSafetyWarning = null; - } - mVolumePanel.forceTimeout(0); - mVolumePanel.updateStates(); - } - - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) { - if (LOGD) Log.d(TAG, "Received ACTION_CLOSE_SYSTEM_DIALOGS"); - cancel(); - cleanUp(); - } - } - }; - } - - public VolumePanel(Context context, ZenModeController zenController) { - mTag = String.format("%s.%08x", TAG, hashCode()); - mContext = context; - mZenController = zenController; - mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); - mAccessibilityManager = (AccessibilityManager) context.getSystemService( - Context.ACCESSIBILITY_SERVICE); - mSecondaryIconTransition = new SecondaryIconTransition(); - mIconPulser = new IconPulser(context); - - // For now, only show master volume if master volume is supported - final Resources res = context.getResources(); - final boolean useMasterVolume = res.getBoolean(R.bool.config_useMasterVolume); - if (useMasterVolume) { - for (int i = 0; i < STREAMS.length; i++) { - StreamResources streamRes = STREAMS[i]; - streamRes.show = (streamRes.streamType == STREAM_MASTER); - } - } - if (LOGD) Log.d(mTag, "new VolumePanel"); - - mDisabledAlpha = 0.5f; - if (mContext.getTheme() != null) { - final TypedArray arr = mContext.getTheme().obtainStyledAttributes( - new int[] { android.R.attr.disabledAlpha }); - mDisabledAlpha = arr.getFloat(0, mDisabledAlpha); - arr.recycle(); - } - - mDialog = new Dialog(context) { - @Override - public boolean onTouchEvent(MotionEvent event) { - if (isShowing() && event.getAction() == MotionEvent.ACTION_OUTSIDE && - sSafetyWarning == null) { - forceTimeout(0); - return true; - } - return false; - } - }; - - final Window window = mDialog.getWindow(); - window.requestFeature(Window.FEATURE_NO_TITLE); - mDialog.setCanceledOnTouchOutside(true); - mDialog.setContentView(com.android.systemui.R.layout.volume_dialog); - mDialog.setOnDismissListener(new OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialog) { - mActiveStreamType = -1; - mAudioManager.forceVolumeControlStream(mActiveStreamType); - setZenPanelVisible(false); - mDemoIcon = 0; - mSecondaryIconTransition.cancel(); - } - }); - - mDialog.create(); - - final LayoutParams lp = window.getAttributes(); - lp.token = null; - lp.y = res.getDimensionPixelOffset(com.android.systemui.R.dimen.volume_panel_top); - lp.type = LayoutParams.TYPE_STATUS_BAR_PANEL; - lp.format = PixelFormat.TRANSLUCENT; - lp.windowAnimations = com.android.systemui.R.style.VolumePanelAnimation; - lp.setTitle(TAG); - window.setAttributes(lp); - - updateWidth(); - - window.setBackgroundDrawable(new ColorDrawable(0x00000000)); - window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); - window.addFlags(LayoutParams.FLAG_NOT_FOCUSABLE - | LayoutParams.FLAG_NOT_TOUCH_MODAL - | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH - | LayoutParams.FLAG_HARDWARE_ACCELERATED); - mView = window.findViewById(R.id.content); - Interaction.register(mView, new Interaction.Callback() { - @Override - public void onInteraction() { - resetTimeout(); - } - }); - - mPanel = (ViewGroup) mView.findViewById(com.android.systemui.R.id.visible_panel); - mSliderPanel = (ViewGroup) mView.findViewById(com.android.systemui.R.id.slider_panel); - mZenPanel = (ZenModePanel) mView.findViewById(com.android.systemui.R.id.zen_mode_panel); - initZenModePanel(); - - mToneGenerators = new ToneGenerator[AudioSystem.getNumStreamTypes()]; - mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); - mHasVibrator = mVibrator != null && mVibrator.hasVibrator(); - mVoiceCapable = context.getResources().getBoolean(R.bool.config_voice_capable); - - if (mZenController != null && !useMasterVolume) { - mZenModeAvailable = mZenController.isZenAvailable(); - mNotificationEffectsSuppressor = mZenController.getEffectsSuppressor(); - mZenController.addCallback(mZenCallback); - } - - final boolean masterVolumeOnly = res.getBoolean(R.bool.config_useMasterVolume); - final boolean masterVolumeKeySounds = res.getBoolean(R.bool.config_useVolumeKeySounds); - mPlayMasterStreamTones = masterVolumeOnly && masterVolumeKeySounds; - - registerReceiver(); - } - - public void onConfigurationChanged(Configuration newConfig) { - updateWidth(); - if (mZenPanel != null) { - mZenPanel.updateLocale(); - } - } - - private void updateWidth() { - final Resources res = mContext.getResources(); - final LayoutParams lp = mDialog.getWindow().getAttributes(); - lp.width = res.getDimensionPixelSize(com.android.systemui.R.dimen.notification_panel_width); - lp.gravity = - res.getInteger(com.android.systemui.R.integer.notification_panel_layout_gravity); - mDialog.getWindow().setAttributes(lp); - } - - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println("VolumePanel state:"); - pw.print(" mTag="); pw.println(mTag); - pw.print(" mRingIsSilent="); pw.println(mRingIsSilent); - pw.print(" mVoiceCapable="); pw.println(mVoiceCapable); - pw.print(" mHasVibrator="); pw.println(mHasVibrator); - pw.print(" mZenModeAvailable="); pw.println(mZenModeAvailable); - pw.print(" mZenPanelExpanded="); pw.println(mZenPanelExpanded); - pw.print(" mNotificationEffectsSuppressor="); pw.println(mNotificationEffectsSuppressor); - pw.print(" mTimeoutDelay="); pw.println(mTimeoutDelay); - pw.print(" mDisabledAlpha="); pw.println(mDisabledAlpha); - pw.print(" mLastRingerMode="); pw.println(mLastRingerMode); - pw.print(" mLastRingerProgress="); pw.println(mLastRingerProgress); - pw.print(" mPlayMasterStreamTones="); pw.println(mPlayMasterStreamTones); - pw.print(" isShowing()="); pw.println(isShowing()); - pw.print(" mCallback="); pw.println(mCallback); - pw.print(" sConfirmSafeVolumeDialog="); - pw.println(sSafetyWarning != null ? "<not null>" : null); - pw.print(" mActiveStreamType="); pw.println(mActiveStreamType); - pw.print(" mStreamControls="); - if (mStreamControls == null) { - pw.println("null"); - } else { - final int N = mStreamControls.size(); - pw.print("<size "); pw.print(N); pw.println('>'); - for (int i = 0; i < N; i++) { - final StreamControl sc = mStreamControls.valueAt(i); - pw.print(" stream "); pw.print(sc.streamType); pw.print(":"); - if (sc.seekbarView != null) { - pw.print(" progress="); pw.print(sc.seekbarView.getProgress()); - pw.print(" of "); pw.print(sc.seekbarView.getMax()); - if (!sc.seekbarView.isEnabled()) pw.print(" (disabled)"); - } - if (sc.icon != null && sc.icon.isClickable()) pw.print(" (clickable)"); - pw.println(); - } - } - if (mZenPanel != null) { - mZenPanel.dump(fd, pw, args); - } - } - - private void initZenModePanel() { - mZenPanel.init(mZenController); - mZenPanel.setCallback(new ZenModePanel.Callback() { - @Override - public void onMoreSettings() { - if (mCallback != null) { - mCallback.onZenSettings(); - } - } - - @Override - public void onInteraction() { - resetTimeout(); - } - - @Override - public void onExpanded(boolean expanded) { - if (mZenPanelExpanded == expanded) return; - mZenPanelExpanded = expanded; - updateTimeoutDelay(); - resetTimeout(); - } - }); - } - - private void setLayoutDirection(int layoutDirection) { - mPanel.setLayoutDirection(layoutDirection); - updateStates(); - } - - private void registerReceiver() { - final IntentFilter filter = new IntentFilter(); - filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); - filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION); - filter.addAction(Intent.ACTION_SCREEN_OFF); - mContext.registerReceiver(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - - if (AudioManager.RINGER_MODE_CHANGED_ACTION.equals(action)) { - removeMessages(MSG_RINGER_MODE_CHANGED); - sendEmptyMessage(MSG_RINGER_MODE_CHANGED); - } - - if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) { - removeMessages(MSG_INTERNAL_RINGER_MODE_CHANGED); - sendEmptyMessage(MSG_INTERNAL_RINGER_MODE_CHANGED); - } - - if (Intent.ACTION_SCREEN_OFF.equals(action)) { - postDismiss(0); - } - } - }, filter); - } - - private boolean isMuted(int streamType) { - if (streamType == STREAM_MASTER) { - return mAudioManager.isMasterMute(); - } else if (streamType == STREAM_REMOTE_MUSIC) { - // TODO do we need to support a distinct mute property for remote? - return false; - } else { - return mAudioManager.isStreamMute(streamType); - } - } - - private int getStreamMaxVolume(int streamType) { - if (streamType == STREAM_MASTER) { - return mAudioManager.getMasterMaxVolume(); - } else if (streamType == STREAM_REMOTE_MUSIC) { - if (mStreamControls != null) { - StreamControl sc = mStreamControls.get(streamType); - if (sc != null && sc.controller != null) { - PlaybackInfo ai = sc.controller.getPlaybackInfo(); - return ai.getMaxVolume(); - } - } - return -1; - } else { - return mAudioManager.getStreamMaxVolume(streamType); - } - } - - private int getStreamVolume(int streamType) { - if (streamType == STREAM_MASTER) { - return mAudioManager.getMasterVolume(); - } else if (streamType == STREAM_REMOTE_MUSIC) { - if (mStreamControls != null) { - StreamControl sc = mStreamControls.get(streamType); - if (sc != null && sc.controller != null) { - PlaybackInfo ai = sc.controller.getPlaybackInfo(); - return ai.getCurrentVolume(); - } - } - return -1; - } else { - return mAudioManager.getStreamVolume(streamType); - } - } - - private void setStreamVolume(StreamControl sc, int index, int flags) { - if (sc.streamType == STREAM_REMOTE_MUSIC) { - if (sc.controller != null) { - sc.controller.setVolumeTo(index, flags); - } else { - Log.w(mTag, "Adjusting remote volume without a controller!"); - } - } else if (getStreamVolume(sc.streamType) != index) { - if (sc.streamType == STREAM_MASTER) { - mAudioManager.setMasterVolume(index, flags); - } else { - mAudioManager.setStreamVolume(sc.streamType, index, flags); - } - } - } - - private void createSliders() { - final Resources res = mContext.getResources(); - final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService( - Context.LAYOUT_INFLATER_SERVICE); - - mStreamControls = new SparseArray<StreamControl>(STREAMS.length); - - final StreamResources notificationStream = StreamResources.NotificationStream; - for (int i = 0; i < STREAMS.length; i++) { - StreamResources streamRes = STREAMS[i]; - - final int streamType = streamRes.streamType; - final boolean isNotification = isNotificationOrRing(streamType); - - final StreamControl sc = new StreamControl(); - sc.streamType = streamType; - sc.group = (ViewGroup) inflater.inflate( - com.android.systemui.R.layout.volume_panel_item, null); - sc.group.setTag(sc); - sc.icon = (ImageView) sc.group.findViewById(com.android.systemui.R.id.stream_icon); - sc.icon.setTag(sc); - sc.icon.setContentDescription(res.getString(streamRes.descRes)); - sc.iconRes = streamRes.iconRes; - sc.iconMuteRes = streamRes.iconMuteRes; - sc.icon.setImageResource(sc.iconRes); - sc.icon.setClickable(isNotification && mHasVibrator); - if (isNotification) { - if (mHasVibrator) { - sc.icon.setSoundEffectsEnabled(false); - sc.iconMuteRes = com.android.systemui.R.drawable.ic_ringer_vibrate; - sc.icon.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - resetTimeout(); - toggleRinger(sc); - } - }); - } - sc.iconSuppressedRes = com.android.systemui.R.drawable.ic_ringer_mute; - } - sc.seekbarView = (SeekBar) sc.group.findViewById(com.android.systemui.R.id.seekbar); - sc.suppressorView = - (TextView) sc.group.findViewById(com.android.systemui.R.id.suppressor); - sc.suppressorView.setVisibility(View.GONE); - final boolean showSecondary = !isNotification && notificationStream.show; - sc.divider = sc.group.findViewById(com.android.systemui.R.id.divider); - sc.secondaryIcon = (ImageView) sc.group - .findViewById(com.android.systemui.R.id.secondary_icon); - sc.secondaryIcon.setImageResource(com.android.systemui.R.drawable.ic_ringer_audible); - sc.secondaryIcon.setContentDescription(res.getString(notificationStream.descRes)); - sc.secondaryIcon.setClickable(showSecondary); - sc.divider.setVisibility(showSecondary ? View.VISIBLE : View.GONE); - sc.secondaryIcon.setVisibility(showSecondary ? View.VISIBLE : View.GONE); - if (showSecondary) { - sc.secondaryIcon.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - mSecondaryIconTransition.start(sc); - } - }); - } - final int plusOne = (streamType == AudioSystem.STREAM_BLUETOOTH_SCO || - streamType == AudioSystem.STREAM_VOICE_CALL) ? 1 : 0; - sc.seekbarView.setMax(getStreamMaxVolume(streamType) + plusOne); - sc.seekbarView.setOnSeekBarChangeListener(mSeekListener); - sc.seekbarView.setTag(sc); - mStreamControls.put(streamType, sc); - } - } - - private void toggleRinger(StreamControl sc) { - if (!mHasVibrator) return; - if (mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_NORMAL) { - mAudioManager.setRingerModeInternal(AudioManager.RINGER_MODE_VIBRATE); - postVolumeChanged(sc.streamType, AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE); - } else { - mAudioManager.setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL); - postVolumeChanged(sc.streamType, AudioManager.FLAG_PLAY_SOUND); - } - } - - private void reorderSliders(int activeStreamType) { - mSliderPanel.removeAllViews(); - - final StreamControl active = mStreamControls.get(activeStreamType); - if (active == null) { - Log.e(TAG, "Missing stream type! - " + activeStreamType); - mActiveStreamType = -1; - } else { - mSliderPanel.addView(active.group); - mActiveStreamType = activeStreamType; - active.group.setVisibility(View.VISIBLE); - updateSlider(active, true /*forceReloadIcon*/); - updateTimeoutDelay(); - updateZenPanelVisible(); - } - } - - private void updateSliderProgress(StreamControl sc, int progress) { - final boolean isRinger = isNotificationOrRing(sc.streamType); - if (isRinger && mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_SILENT) { - progress = mLastRingerProgress; - } - if (progress < 0) { - progress = getStreamVolume(sc.streamType); - } - sc.seekbarView.setProgress(progress); - if (isRinger) { - mLastRingerProgress = progress; - } - } - - private void updateSliderIcon(StreamControl sc, boolean muted) { - ComponentName suppressor = null; - if (isNotificationOrRing(sc.streamType)) { - suppressor = mNotificationEffectsSuppressor; - int ringerMode = mAudioManager.getRingerModeInternal(); - if (ringerMode == AudioManager.RINGER_MODE_SILENT) { - ringerMode = mLastRingerMode; - } else { - mLastRingerMode = ringerMode; - } - if (mHasVibrator) { - muted = ringerMode == AudioManager.RINGER_MODE_VIBRATE; - } else { - muted = false; - } - } - sc.icon.setImageResource(mDemoIcon != 0 ? mDemoIcon - : suppressor != null ? sc.iconSuppressedRes - : muted ? sc.iconMuteRes - : sc.iconRes); - } - - private void updateSliderSuppressor(StreamControl sc) { - final ComponentName suppressor = isNotificationOrRing(sc.streamType) - ? mNotificationEffectsSuppressor : null; - if (suppressor == null) { - sc.seekbarView.setVisibility(View.VISIBLE); - sc.suppressorView.setVisibility(View.GONE); - } else { - sc.seekbarView.setVisibility(View.GONE); - sc.suppressorView.setVisibility(View.VISIBLE); - sc.suppressorView.setText(mContext.getString(R.string.muted_by, - getSuppressorCaption(suppressor))); - } - } - - private String getSuppressorCaption(ComponentName suppressor) { - final PackageManager pm = mContext.getPackageManager(); - try { - final ServiceInfo info = pm.getServiceInfo(suppressor, 0); - if (info != null) { - final CharSequence seq = info.loadLabel(pm); - if (seq != null) { - final String str = seq.toString().trim(); - if (str.length() > 0) { - return str; - } - } - } - } catch (Throwable e) { - Log.w(TAG, "Error loading suppressor caption", e); - } - return suppressor.getPackageName(); - } - - /** Update the mute and progress state of a slider */ - private void updateSlider(StreamControl sc, boolean forceReloadIcon) { - updateSliderProgress(sc, -1); - final boolean muted = isMuted(sc.streamType); - if (forceReloadIcon) { - sc.icon.setImageDrawable(null); - } - updateSliderIcon(sc, muted); - updateSliderEnabled(sc, muted, false); - updateSliderSuppressor(sc); - } - - private void updateSliderEnabled(final StreamControl sc, boolean muted, boolean fixedVolume) { - final boolean wasEnabled = sc.seekbarView.isEnabled(); - final boolean isRinger = isNotificationOrRing(sc.streamType); - if (sc.streamType == STREAM_REMOTE_MUSIC) { - // never disable touch interactions for remote playback, the muting is not tied to - // the state of the phone. - sc.seekbarView.setEnabled(!fixedVolume); - } else if (isRinger && mNotificationEffectsSuppressor != null) { - sc.icon.setEnabled(true); - sc.icon.setAlpha(1f); - sc.icon.setClickable(false); - } else if (isRinger - && mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_SILENT) { - sc.seekbarView.setEnabled(false); - sc.icon.setEnabled(false); - sc.icon.setAlpha(mDisabledAlpha); - sc.icon.setClickable(false); - } else if (fixedVolume || - (sc.streamType != mAudioManager.getMasterStreamType() && !isRinger && muted) || - (sSafetyWarning != null)) { - sc.seekbarView.setEnabled(false); - } else { - sc.seekbarView.setEnabled(true); - sc.icon.setEnabled(true); - sc.icon.setAlpha(1f); - } - // show the silent hint when the disabled slider is touched in silent mode - if (isRinger && wasEnabled != sc.seekbarView.isEnabled()) { - if (sc.seekbarView.isEnabled()) { - sc.group.setOnTouchListener(null); - sc.icon.setClickable(mHasVibrator); - } else { - final View.OnTouchListener showHintOnTouch = new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - resetTimeout(); - showSilentHint(); - return false; - } - }; - sc.group.setOnTouchListener(showHintOnTouch); - } - } - } - - private void showSilentHint() { - if (mZenPanel != null) { - mZenPanel.showSilentHint(); - } - } - - private void showVibrateHint() { - final StreamControl active = mStreamControls.get(mActiveStreamType); - if (active != null) { - mIconPulser.start(active.icon); - if (!hasMessages(MSG_VIBRATE)) { - sendEmptyMessageDelayed(MSG_VIBRATE, VIBRATE_DELAY); - } - } - } - - private static boolean isNotificationOrRing(int streamType) { - return streamType == AudioManager.STREAM_RING - || streamType == AudioManager.STREAM_NOTIFICATION; - } - - public void setCallback(Callback callback) { - mCallback = callback; - } - - private void updateTimeoutDelay() { - mTimeoutDelay = mDemoIcon != 0 ? TIMEOUT_DELAY_EXPANDED - : sSafetyWarning != null ? TIMEOUT_DELAY_SAFETY_WARNING - : mActiveStreamType == AudioManager.STREAM_MUSIC ? TIMEOUT_DELAY_SHORT - : mZenPanelExpanded ? TIMEOUT_DELAY_EXPANDED - : isZenPanelVisible() ? TIMEOUT_DELAY_COLLAPSED - : TIMEOUT_DELAY; - } - - private boolean isZenPanelVisible() { - return mZenPanel != null && mZenPanel.getVisibility() == View.VISIBLE; - } - - private void setZenPanelVisible(boolean visible) { - if (LOGD) Log.d(mTag, "setZenPanelVisible " + visible + " mZenPanel=" + mZenPanel); - final boolean changing = visible != isZenPanelVisible(); - if (visible) { - mZenPanel.setHidden(false); - resetTimeout(); - } else { - mZenPanel.setHidden(true); - } - if (changing) { - updateTimeoutDelay(); - resetTimeout(); - } - } - - private void updateStates() { - final int count = mSliderPanel.getChildCount(); - for (int i = 0; i < count; i++) { - StreamControl sc = (StreamControl) mSliderPanel.getChildAt(i).getTag(); - updateSlider(sc, true /*forceReloadIcon*/); - } - } - - private void updateActiveSlider() { - final StreamControl active = mStreamControls.get(mActiveStreamType); - if (active != null) { - updateSlider(active, false /*forceReloadIcon*/); - } - } - - private void updateZenPanelVisible() { - setZenPanelVisible(mZenModeAvailable && isNotificationOrRing(mActiveStreamType)); - } - - public void postVolumeChanged(int streamType, int flags) { - if (hasMessages(MSG_VOLUME_CHANGED)) return; - synchronized (this) { - if (mStreamControls == null) { - createSliders(); - } - } - removeMessages(MSG_FREE_RESOURCES); - obtainMessage(MSG_VOLUME_CHANGED, streamType, flags).sendToTarget(); - } - - public void postRemoteVolumeChanged(MediaController controller, int flags) { - if (hasMessages(MSG_REMOTE_VOLUME_CHANGED)) return; - synchronized (this) { - if (mStreamControls == null) { - createSliders(); - } - } - removeMessages(MSG_FREE_RESOURCES); - obtainMessage(MSG_REMOTE_VOLUME_CHANGED, flags, 0, controller).sendToTarget(); - } - - public void postRemoteSliderVisibility(boolean visible) { - obtainMessage(MSG_SLIDER_VISIBILITY_CHANGED, - STREAM_REMOTE_MUSIC, visible ? 1 : 0).sendToTarget(); - } - - /** - * Called by AudioService when it has received new remote playback information that - * would affect the VolumePanel display (mainly volumes). The difference with - * {@link #postRemoteVolumeChanged(int, int)} is that the handling of the posted message - * (MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN) will only update the volume slider if it is being - * displayed. - * This special code path is due to the fact that remote volume updates arrive to AudioService - * asynchronously. So after AudioService has sent the volume update (which should be treated - * as a request to update the volume), the application will likely set a new volume. If the UI - * is still up, we need to refresh the display to show this new value. - */ - public void postHasNewRemotePlaybackInfo() { - if (hasMessages(MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN)) return; - // don't create or prevent resources to be freed, if they disappear, this update came too - // late and shouldn't warrant the panel to be displayed longer - obtainMessage(MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN).sendToTarget(); - } - - public void postMasterVolumeChanged(int flags) { - postVolumeChanged(STREAM_MASTER, flags); - } - - public void postMuteChanged(int streamType, int flags) { - if (hasMessages(MSG_VOLUME_CHANGED)) return; - synchronized (this) { - if (mStreamControls == null) { - createSliders(); - } - } - removeMessages(MSG_FREE_RESOURCES); - obtainMessage(MSG_MUTE_CHANGED, streamType, flags).sendToTarget(); - } - - public void postMasterMuteChanged(int flags) { - postMuteChanged(STREAM_MASTER, flags); - } - - public void postDisplaySafeVolumeWarning(int flags) { - if (hasMessages(MSG_DISPLAY_SAFE_VOLUME_WARNING)) return; - obtainMessage(MSG_DISPLAY_SAFE_VOLUME_WARNING, flags, 0).sendToTarget(); - } - - public void postDismiss(long delay) { - forceTimeout(delay); - } - - public void postLayoutDirection(int layoutDirection) { - removeMessages(MSG_LAYOUT_DIRECTION); - obtainMessage(MSG_LAYOUT_DIRECTION, layoutDirection, 0).sendToTarget(); - } - - private static String flagsToString(int flags) { - return flags == 0 ? "0" : (flags + "=" + AudioManager.flagsToString(flags)); - } - - private static String streamToString(int stream) { - return AudioService.streamToString(stream); - } - - /** - * Override this if you have other work to do when the volume changes (for - * example, vibrating, playing a sound, etc.). Make sure to call through to - * the superclass implementation. - */ - protected void onVolumeChanged(int streamType, int flags) { - - if (LOGD) Log.d(mTag, "onVolumeChanged(streamType: " + streamToString(streamType) - + ", flags: " + flagsToString(flags) + ")"); - - if ((flags & AudioManager.FLAG_SHOW_UI) != 0) { - synchronized (this) { - if (mActiveStreamType != streamType) { - reorderSliders(streamType); - } - onShowVolumeChanged(streamType, flags, null); - } - } - - if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && ! mRingIsSilent) { - removeMessages(MSG_PLAY_SOUND); - sendMessageDelayed(obtainMessage(MSG_PLAY_SOUND, streamType, flags), PLAY_SOUND_DELAY); - } - - if ((flags & AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE) != 0) { - removeMessages(MSG_PLAY_SOUND); - removeMessages(MSG_VIBRATE); - onStopSounds(); - } - - removeMessages(MSG_FREE_RESOURCES); - sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY); - resetTimeout(); - } - - protected void onMuteChanged(int streamType, int flags) { - - if (LOGD) Log.d(mTag, "onMuteChanged(streamType: " + streamToString(streamType) - + ", flags: " + flagsToString(flags) + ")"); - - StreamControl sc = mStreamControls.get(streamType); - if (sc != null) { - updateSliderIcon(sc, isMuted(sc.streamType)); - } - - onVolumeChanged(streamType, flags); - } - - protected void onShowVolumeChanged(int streamType, int flags, MediaController controller) { - int index = getStreamVolume(streamType); - - mRingIsSilent = false; - - if (LOGD) { - Log.d(mTag, "onShowVolumeChanged(streamType: " + streamToString(streamType) - + ", flags: " + flagsToString(flags) + "), index: " + index); - } - - // get max volume for progress bar - - int max = getStreamMaxVolume(streamType); - StreamControl sc = mStreamControls.get(streamType); - - switch (streamType) { - - case AudioManager.STREAM_RING: { - Uri ringuri = RingtoneManager.getActualDefaultRingtoneUri( - mContext, RingtoneManager.TYPE_RINGTONE); - if (ringuri == null) { - mRingIsSilent = true; - } - break; - } - - case AudioManager.STREAM_MUSIC: { - // Special case for when Bluetooth is active for music - if ((mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC) & - (AudioManager.DEVICE_OUT_BLUETOOTH_A2DP | - AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES | - AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER)) != 0) { - setMusicIcon(IC_AUDIO_BT, IC_AUDIO_BT_MUTE); - } else { - setMusicIcon(IC_AUDIO_VOL, IC_AUDIO_VOL_MUTE); - } - break; - } - - case AudioManager.STREAM_VOICE_CALL: { - /* - * For in-call voice call volume, there is no inaudible volume. - * Rescale the UI control so the progress bar doesn't go all - * the way to zero and don't show the mute icon. - */ - index++; - max++; - break; - } - - case AudioManager.STREAM_ALARM: { - break; - } - - case AudioManager.STREAM_NOTIFICATION: { - Uri ringuri = RingtoneManager.getActualDefaultRingtoneUri( - mContext, RingtoneManager.TYPE_NOTIFICATION); - if (ringuri == null) { - mRingIsSilent = true; - } - break; - } - - case AudioManager.STREAM_BLUETOOTH_SCO: { - /* - * For in-call voice call volume, there is no inaudible volume. - * Rescale the UI control so the progress bar doesn't go all - * the way to zero and don't show the mute icon. - */ - index++; - max++; - break; - } - - case STREAM_REMOTE_MUSIC: { - if (controller == null && sc != null) { - // If we weren't passed one try using the last one set. - controller = sc.controller; - } - if (controller == null) { - // We still don't have one, ignore the command. - Log.w(mTag, "sent remote volume change without a controller!"); - } else { - PlaybackInfo vi = controller.getPlaybackInfo(); - index = vi.getCurrentVolume(); - max = vi.getMaxVolume(); - if ((vi.getVolumeControl() & VolumeProvider.VOLUME_CONTROL_FIXED) != 0) { - // if the remote volume is fixed add the flag for the UI - flags |= AudioManager.FLAG_FIXED_VOLUME; - } - } - if (LOGD) { Log.d(mTag, "showing remote volume "+index+" over "+ max); } - break; - } - } - - if (sc != null) { - if (streamType == STREAM_REMOTE_MUSIC && controller != sc.controller) { - if (sc.controller != null) { - sc.controller.unregisterCallback(mMediaControllerCb); - } - sc.controller = controller; - if (controller != null) { - sc.controller.registerCallback(mMediaControllerCb); - } - } - if (sc.seekbarView.getMax() != max) { - sc.seekbarView.setMax(max); - } - updateSliderProgress(sc, index); - final boolean muted = isMuted(streamType); - updateSliderEnabled(sc, muted, (flags & AudioManager.FLAG_FIXED_VOLUME) != 0); - if (isNotificationOrRing(streamType)) { - // check for secondary-icon transition completion - if (mSecondaryIconTransition.isRunning()) { - mSecondaryIconTransition.cancel(); // safe to reset - sc.seekbarView.setAlpha(0); sc.seekbarView.animate().alpha(1); - mZenPanel.setAlpha(0); mZenPanel.animate().alpha(1); - } - updateSliderIcon(sc, muted); - } - } - - if (!isShowing()) { - int stream = (streamType == STREAM_REMOTE_MUSIC) ? -1 : streamType; - // when the stream is for remote playback, use -1 to reset the stream type evaluation - if (stream != STREAM_MASTER) { - mAudioManager.forceVolumeControlStream(stream); - } - mDialog.show(); - if (mCallback != null) { - mCallback.onVisible(true); - } - announceDialogShown(); - } - - // Do a little vibrate if applicable (only when going into vibrate mode) - if ((streamType != STREAM_REMOTE_MUSIC) && - ((flags & AudioManager.FLAG_VIBRATE) != 0) && - isNotificationOrRing(streamType) && - mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_VIBRATE) { - sendMessageDelayed(obtainMessage(MSG_VIBRATE), VIBRATE_DELAY); - } - - // Pulse the zen icon if an adjustment was suppressed due to silent mode. - if ((flags & AudioManager.FLAG_SHOW_SILENT_HINT) != 0) { - showSilentHint(); - } - - // Pulse the slider icon & vibrate if an adjustment down was suppressed due to vibrate mode. - if ((flags & AudioManager.FLAG_SHOW_VIBRATE_HINT) != 0) { - showVibrateHint(); - } - } - - private void announceDialogShown() { - mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); - } - - private boolean isShowing() { - return mDialog.isShowing(); - } - - protected void onPlaySound(int streamType, int flags) { - - if (hasMessages(MSG_STOP_SOUNDS)) { - removeMessages(MSG_STOP_SOUNDS); - // Force stop right now - onStopSounds(); - } - - synchronized (this) { - ToneGenerator toneGen = getOrCreateToneGenerator(streamType); - if (toneGen != null) { - toneGen.startTone(ToneGenerator.TONE_PROP_BEEP); - sendMessageDelayed(obtainMessage(MSG_STOP_SOUNDS), BEEP_DURATION); - } - } - } - - protected void onStopSounds() { - - synchronized (this) { - int numStreamTypes = AudioSystem.getNumStreamTypes(); - for (int i = numStreamTypes - 1; i >= 0; i--) { - ToneGenerator toneGen = mToneGenerators[i]; - if (toneGen != null) { - toneGen.stopTone(); - } - } - } - } - - protected void onVibrate() { - - // Make sure we ended up in vibrate ringer mode - if (mAudioManager.getRingerModeInternal() != AudioManager.RINGER_MODE_VIBRATE) { - return; - } - if (mVibrator != null) { - mVibrator.vibrate(VIBRATE_DURATION, VIBRATION_ATTRIBUTES); - } - } - - protected void onRemoteVolumeChanged(MediaController controller, int flags) { - if (LOGD) Log.d(mTag, "onRemoteVolumeChanged(controller:" + controller + ", flags: " - + flagsToString(flags) + ")"); - - if (((flags & AudioManager.FLAG_SHOW_UI) != 0) || isShowing()) { - synchronized (this) { - if (mActiveStreamType != STREAM_REMOTE_MUSIC) { - reorderSliders(STREAM_REMOTE_MUSIC); - } - onShowVolumeChanged(STREAM_REMOTE_MUSIC, flags, controller); - } - } else { - if (LOGD) Log.d(mTag, "not calling onShowVolumeChanged(), no FLAG_SHOW_UI or no UI"); - } - - removeMessages(MSG_FREE_RESOURCES); - sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY); - resetTimeout(); - } - - protected void onRemoteVolumeUpdateIfShown() { - if (LOGD) Log.d(mTag, "onRemoteVolumeUpdateIfShown()"); - if (isShowing() - && (mActiveStreamType == STREAM_REMOTE_MUSIC) - && (mStreamControls != null)) { - onShowVolumeChanged(STREAM_REMOTE_MUSIC, 0, null); - } - } - - /** - * Clear the current remote stream controller. - */ - private void clearRemoteStreamController() { - if (mStreamControls != null) { - StreamControl sc = mStreamControls.get(STREAM_REMOTE_MUSIC); - if (sc != null) { - if (sc.controller != null) { - sc.controller.unregisterCallback(mMediaControllerCb); - sc.controller = null; - } - } - } - } - - /** - * Handler for MSG_SLIDER_VISIBILITY_CHANGED Hide or show a slider - * - * @param streamType can be a valid stream type value, or - * VolumePanel.STREAM_MASTER, or VolumePanel.STREAM_REMOTE_MUSIC - * @param visible - */ - synchronized protected void onSliderVisibilityChanged(int streamType, int visible) { - if (LOGD) Log.d(mTag, "onSliderVisibilityChanged(stream="+streamType+", visi="+visible+")"); - boolean isVisible = (visible == 1); - for (int i = STREAMS.length - 1 ; i >= 0 ; i--) { - StreamResources streamRes = STREAMS[i]; - if (streamRes.streamType == streamType) { - streamRes.show = isVisible; - if (!isVisible && (mActiveStreamType == streamType)) { - mActiveStreamType = -1; - } - break; - } - } - } - - protected void onDisplaySafeVolumeWarning(int flags) { - if ((flags & (AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_SHOW_UI_WARNINGS)) != 0 - || isShowing()) { - synchronized (sSafetyWarningLock) { - if (sSafetyWarning != null) { - return; - } - sSafetyWarning = new SafetyWarning(mContext, this, mAudioManager); - sSafetyWarning.show(); - } - updateStates(); - } - if (mAccessibilityManager.isTouchExplorationEnabled()) { - removeMessages(MSG_TIMEOUT); - } else { - updateTimeoutDelay(); - resetTimeout(); - } - } - - /** - * Lock on this VolumePanel instance as long as you use the returned ToneGenerator. - */ - private ToneGenerator getOrCreateToneGenerator(int streamType) { - if (streamType == STREAM_MASTER) { - // For devices that use the master volume setting only but still want to - // play a volume-changed tone, direct the master volume pseudostream to - // the system stream's tone generator. - if (mPlayMasterStreamTones) { - streamType = AudioManager.STREAM_SYSTEM; - } else { - return null; - } - } - synchronized (this) { - if (mToneGenerators[streamType] == null) { - try { - mToneGenerators[streamType] = new ToneGenerator(streamType, MAX_VOLUME); - } catch (RuntimeException e) { - if (LOGD) { - Log.d(mTag, "ToneGenerator constructor failed with " - + "RuntimeException: " + e); - } - } - } - return mToneGenerators[streamType]; - } - } - - - /** - * Switch between icons because Bluetooth music is same as music volume, but with - * different icons. - */ - private void setMusicIcon(int resId, int resMuteId) { - StreamControl sc = mStreamControls.get(AudioManager.STREAM_MUSIC); - if (sc != null) { - sc.iconRes = resId; - sc.iconMuteRes = resMuteId; - updateSliderIcon(sc, isMuted(sc.streamType)); - } - } - - protected void onFreeResources() { - synchronized (this) { - for (int i = mToneGenerators.length - 1; i >= 0; i--) { - if (mToneGenerators[i] != null) { - mToneGenerators[i].release(); - } - mToneGenerators[i] = null; - } - } - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - - case MSG_VOLUME_CHANGED: { - onVolumeChanged(msg.arg1, msg.arg2); - break; - } - - case MSG_MUTE_CHANGED: { - onMuteChanged(msg.arg1, msg.arg2); - break; - } - - case MSG_FREE_RESOURCES: { - onFreeResources(); - break; - } - - case MSG_STOP_SOUNDS: { - onStopSounds(); - break; - } - - case MSG_PLAY_SOUND: { - onPlaySound(msg.arg1, msg.arg2); - break; - } - - case MSG_VIBRATE: { - onVibrate(); - break; - } - - case MSG_TIMEOUT: { - if (isShowing()) { - mDialog.dismiss(); - clearRemoteStreamController(); - mActiveStreamType = -1; - if (mCallback != null) { - mCallback.onVisible(false); - } - } - synchronized (sSafetyWarningLock) { - if (sSafetyWarning != null) { - if (LOGD) Log.d(mTag, "SafetyWarning timeout"); - sSafetyWarning.dismiss(); - } - } - break; - } - - case MSG_RINGER_MODE_CHANGED: - case MSG_INTERNAL_RINGER_MODE_CHANGED: - case MSG_NOTIFICATION_EFFECTS_SUPPRESSOR_CHANGED: { - if (isShowing()) { - updateActiveSlider(); - } - break; - } - - case MSG_REMOTE_VOLUME_CHANGED: { - onRemoteVolumeChanged((MediaController) msg.obj, msg.arg1); - break; - } - - case MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN: - onRemoteVolumeUpdateIfShown(); - break; - - case MSG_SLIDER_VISIBILITY_CHANGED: - onSliderVisibilityChanged(msg.arg1, msg.arg2); - break; - - case MSG_DISPLAY_SAFE_VOLUME_WARNING: - onDisplaySafeVolumeWarning(msg.arg1); - break; - - case MSG_LAYOUT_DIRECTION: - setLayoutDirection(msg.arg1); - break; - - case MSG_ZEN_MODE_AVAILABLE_CHANGED: - mZenModeAvailable = msg.arg1 != 0; - updateZenPanelVisible(); - break; - - case MSG_USER_ACTIVITY: - if (mCallback != null) { - mCallback.onInteraction(); - } - break; - } - } - - private void resetTimeout() { - final boolean touchExploration = mAccessibilityManager.isTouchExplorationEnabled(); - if (LOGD) Log.d(mTag, "resetTimeout at " + System.currentTimeMillis() - + " delay=" + mTimeoutDelay + " touchExploration=" + touchExploration); - if (sSafetyWarning == null || !touchExploration) { - removeMessages(MSG_TIMEOUT); - sendEmptyMessageDelayed(MSG_TIMEOUT, mTimeoutDelay); - removeMessages(MSG_USER_ACTIVITY); - sendEmptyMessage(MSG_USER_ACTIVITY); - } - } - - private void forceTimeout(long delay) { - if (LOGD) Log.d(mTag, "forceTimeout delay=" + delay + " callers=" + Debug.getCallers(3)); - removeMessages(MSG_TIMEOUT); - sendEmptyMessageDelayed(MSG_TIMEOUT, delay); - } - - public ZenModeController getZenController() { - return mZenController; - } - - @Override - public void dispatchDemoCommand(String command, Bundle args) { - if (!COMMAND_VOLUME.equals(command)) return; - String icon = args.getString("icon"); - final String iconMute = args.getString("iconmute"); - final boolean mute = iconMute != null; - icon = mute ? iconMute : icon; - icon = icon.endsWith("Stream") ? icon : (icon + "Stream"); - final StreamResources sr = StreamResources.valueOf(icon); - mDemoIcon = mute ? sr.iconMuteRes : sr.iconRes; - final int forcedStreamType = StreamResources.MediaStream.streamType; - mAudioManager.forceVolumeControlStream(forcedStreamType); - mAudioManager.adjustStreamVolume(forcedStreamType, AudioManager.ADJUST_SAME, - AudioManager.FLAG_SHOW_UI); - } - - private final OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() { - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - final Object tag = seekBar.getTag(); - if (fromUser && tag instanceof StreamControl) { - StreamControl sc = (StreamControl) tag; - setStreamVolume(sc, progress, - AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE); - } - resetTimeout(); - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) { - } - - @Override - public void onStopTrackingTouch(SeekBar seekBar) { - } - }; - - private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() { - @Override - public void onZenAvailableChanged(boolean available) { - obtainMessage(MSG_ZEN_MODE_AVAILABLE_CHANGED, available ? 1 : 0, 0).sendToTarget(); - } - - @Override - public void onEffectsSupressorChanged() { - mNotificationEffectsSuppressor = mZenController.getEffectsSuppressor(); - sendEmptyMessage(MSG_NOTIFICATION_EFFECTS_SUPPRESSOR_CHANGED); - } - }; - - private final MediaController.Callback mMediaControllerCb = new MediaController.Callback() { - public void onAudioInfoChanged(PlaybackInfo info) { - onRemoteVolumeUpdateIfShown(); - } - }; - - private final class SecondaryIconTransition extends AnimatorListenerAdapter - implements Runnable { - private static final int ANIMATION_TIME = 400; - private static final int WAIT_FOR_SWITCH_TIME = 1000; - - private final int mAnimationTime = (int)(ANIMATION_TIME * ValueAnimator.getDurationScale()); - private final int mFadeOutTime = mAnimationTime / 2; - private final int mDelayTime = mAnimationTime / 3; - - private final Interpolator mIconInterpolator = - AnimationUtils.loadInterpolator(mContext, android.R.interpolator.fast_out_slow_in); - - private StreamControl mTarget; - - public void start(StreamControl sc) { - if (sc == null) throw new IllegalArgumentException(); - if (LOGD) Log.d(mTag, "Secondary icon animation start"); - if (mTarget != null) { - cancel(); - } - mTarget = sc; - mTimeoutDelay = mAnimationTime + WAIT_FOR_SWITCH_TIME; - resetTimeout(); - mTarget.secondaryIcon.setClickable(false); - final int N = mTarget.group.getChildCount(); - for (int i = 0; i < N; i++) { - final View child = mTarget.group.getChildAt(i); - if (child != mTarget.secondaryIcon) { - child.animate().alpha(0).setDuration(mFadeOutTime).start(); - } - } - mTarget.secondaryIcon.animate() - .translationXBy(mTarget.icon.getX() - mTarget.secondaryIcon.getX()) - .setInterpolator(mIconInterpolator) - .setStartDelay(mDelayTime) - .setDuration(mAnimationTime - mDelayTime) - .setListener(this) - .start(); - } - - public boolean isRunning() { - return mTarget != null; - } - - public void cancel() { - if (mTarget == null) return; - mTarget.secondaryIcon.setClickable(true); - final int N = mTarget.group.getChildCount(); - for (int i = 0; i < N; i++) { - final View child = mTarget.group.getChildAt(i); - if (child != mTarget.secondaryIcon) { - child.animate().cancel(); - child.setAlpha(1); - } - } - mTarget.secondaryIcon.animate().cancel(); - mTarget.secondaryIcon.setTranslationX(0); - mTarget = null; - } - - @Override - public void onAnimationEnd(Animator animation) { - if (mTarget == null) return; - AsyncTask.execute(this); - } - - @Override - public void run() { - if (mTarget == null) return; - if (LOGD) Log.d(mTag, "Secondary icon animation complete, show notification slider"); - mAudioManager.forceVolumeControlStream(StreamResources.NotificationStream.streamType); - mAudioManager.adjustStreamVolume(StreamResources.NotificationStream.streamType, - AudioManager.ADJUST_SAME, AudioManager.FLAG_SHOW_UI); - } - } - - public interface Callback { - void onZenSettings(); - void onInteraction(); - void onVisible(boolean visible); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePrefs.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePrefs.java new file mode 100644 index 0000000..04339eb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePrefs.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; +import android.preference.PreferenceManager; + +/** + * Configuration for the volume dialog + related policy. + */ +public class VolumePrefs { + + public static final String PREF_ENABLE_PROTOTYPE = "pref_enable_prototype"; // not persistent + public static final String PREF_SHOW_ALARMS = "pref_show_alarms"; + public static final String PREF_SHOW_SYSTEM = "pref_show_system"; + public static final String PREF_SHOW_HEADERS = "pref_show_headers"; + public static final String PREF_SHOW_FAKE_REMOTE_1 = "pref_show_fake_remote_1"; + public static final String PREF_SHOW_FAKE_REMOTE_2 = "pref_show_fake_remote_2"; + public static final String PREF_ENABLE_AUTOMUTE = "pref_enable_automute"; + public static final String PREF_ENABLE_SILENT_MODE = "pref_enable_silent_mode"; + public static final String PREF_DEBUG_LOGGING = "pref_debug_logging"; + public static final String PREF_SEND_LOGS = "pref_send_logs"; + public static final String PREF_ADJUST_SYSTEM = "pref_adjust_system"; + public static final String PREF_ADJUST_VOICE_CALLS = "pref_adjust_voice_calls"; + public static final String PREF_ADJUST_BLUETOOTH_SCO = "pref_adjust_bluetooth_sco"; + public static final String PREF_ADJUST_MEDIA = "pref_adjust_media"; + public static final String PREF_ADJUST_ALARMS = "pref_adjust_alarms"; + public static final String PREF_ADJUST_NOTIFICATION = "pref_adjust_notification"; + + public static final boolean DEFAULT_SHOW_HEADERS = true; + public static final boolean DEFAULT_ENABLE_AUTOMUTE = true; + public static final boolean DEFAULT_ENABLE_SILENT_MODE = true; + + public static void unregisterCallbacks(Context c, OnSharedPreferenceChangeListener listener) { + prefs(c).unregisterOnSharedPreferenceChangeListener(listener); + } + + public static void registerCallbacks(Context c, OnSharedPreferenceChangeListener listener) { + prefs(c).registerOnSharedPreferenceChangeListener(listener); + } + + private static SharedPreferences prefs(Context context) { + return PreferenceManager.getDefaultSharedPreferences(context); + } + + public static boolean get(Context context, String key, boolean def) { + return prefs(context).getBoolean(key, def); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java index 7102c2a..2688813 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java @@ -1,216 +1,249 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.systemui.volume; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; import android.content.res.Configuration; -import android.database.ContentObserver; import android.media.AudioManager; -import android.media.IRemoteVolumeController; -import android.media.IVolumeController; -import android.media.session.ISessionController; -import android.media.session.MediaController; import android.media.session.MediaSessionManager; -import android.net.Uri; -import android.os.Bundle; import android.os.Handler; -import android.os.RemoteException; import android.provider.Settings; +import android.text.TextUtils; import android.util.Log; +import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.SystemUI; -import com.android.systemui.keyguard.KeyguardViewMediator; -import com.android.systemui.statusbar.phone.PhoneStatusBar; +import com.android.systemui.qs.tiles.DndTile; +import com.android.systemui.statusbar.ServiceMonitor; +import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.statusbar.policy.ZenModeControllerImpl; import java.io.FileDescriptor; import java.io.PrintWriter; -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - public class VolumeUI extends SystemUI { private static final String TAG = "VolumeUI"; - private static final String SETTING = "systemui_volume_controller"; // for testing - private static final Uri SETTING_URI = Settings.Global.getUriFor(SETTING); - private static final int DEFAULT = 1; // enabled by default + private static boolean LOGD = Log.isLoggable(TAG, Log.DEBUG); private final Handler mHandler = new Handler(); + private final Receiver mReceiver = new Receiver(); + private final RestorationNotification mRestorationNotification = new RestorationNotification(); private boolean mEnabled; private AudioManager mAudioManager; + private NotificationManager mNotificationManager; private MediaSessionManager mMediaSessionManager; - private VolumeController mVolumeController; - private RemoteVolumeController mRemoteVolumeController; + private ServiceMonitor mVolumeControllerService; - private VolumePanel mPanel; - private int mDismissDelay; + private VolumeDialogComponent mVolumeComponent; @Override public void start() { mEnabled = mContext.getResources().getBoolean(R.bool.enable_volume_ui); if (!mEnabled) return; mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + mNotificationManager = + (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); mMediaSessionManager = (MediaSessionManager) mContext .getSystemService(Context.MEDIA_SESSION_SERVICE); - initPanel(); - mVolumeController = new VolumeController(); - mRemoteVolumeController = new RemoteVolumeController(); - putComponent(VolumeComponent.class, mVolumeController); - updateController(); - mContext.getContentResolver().registerContentObserver(SETTING_URI, false, mObserver); + final ZenModeController zenController = new ZenModeControllerImpl(mContext, mHandler); + mVolumeComponent = new VolumeDialogComponent(this, mContext, null, zenController); + putComponent(VolumeComponent.class, getVolumeComponent()); + mReceiver.start(); + mVolumeControllerService = new ServiceMonitor(TAG, LOGD, + mContext, Settings.Secure.VOLUME_CONTROLLER_SERVICE_COMPONENT, + new ServiceMonitorCallbacks()); + mVolumeControllerService.start(); + } + + private VolumeComponent getVolumeComponent() { + return mVolumeComponent; } @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - if (mPanel != null) { - mPanel.onConfigurationChanged(newConfig); - } + if (!mEnabled) return; + getVolumeComponent().onConfigurationChanged(newConfig); } @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.print("mEnabled="); pw.println(mEnabled); - if (mPanel != null) { - mPanel.dump(fd, pw, args); - } + if (!mEnabled) return; + pw.print("mVolumeControllerService="); pw.println(mVolumeControllerService.getComponent()); + getVolumeComponent().dump(fd, pw, args); } - private void updateController() { - if (Settings.Global.getInt(mContext.getContentResolver(), SETTING, DEFAULT) != 0) { - Log.d(TAG, "Registering volume controller"); - mAudioManager.setVolumeController(mVolumeController); - mMediaSessionManager.setRemoteVolumeController(mRemoteVolumeController); + private void setDefaultVolumeController(boolean register) { + if (register) { + DndTile.setVisible(mContext, true); + if (LOGD) Log.d(TAG, "Registering default volume controller"); + getVolumeComponent().register(); } else { - Log.d(TAG, "Unregistering volume controller"); + if (LOGD) Log.d(TAG, "Unregistering default volume controller"); mAudioManager.setVolumeController(null); mMediaSessionManager.setRemoteVolumeController(null); } } - private void initPanel() { - mDismissDelay = mContext.getResources().getInteger(R.integer.volume_panel_dismiss_delay); - mPanel = new VolumePanel(mContext, new ZenModeControllerImpl(mContext, mHandler)); - mPanel.setCallback(new VolumePanel.Callback() { - @Override - public void onZenSettings() { - mHandler.removeCallbacks(mStartZenSettings); - mHandler.post(mStartZenSettings); - } - - @Override - public void onInteraction() { - final KeyguardViewMediator kvm = getComponent(KeyguardViewMediator.class); - if (kvm != null) { - kvm.userActivity(); - } + private String getAppLabel(ComponentName component) { + final String pkg = component.getPackageName(); + try { + final ApplicationInfo ai = mContext.getPackageManager().getApplicationInfo(pkg, 0); + final String rt = mContext.getPackageManager().getApplicationLabel(ai).toString(); + if (!TextUtils.isEmpty(rt)) { + return rt; } + } catch (Exception e) { + Log.w(TAG, "Error loading app label", e); + } + return pkg; + } + private void showServiceActivationDialog(final ComponentName component) { + final SystemUIDialog d = new SystemUIDialog(mContext); + d.setMessage(mContext.getString(R.string.volumeui_prompt_message, getAppLabel(component))); + d.setPositiveButton(R.string.volumeui_prompt_allow, new OnClickListener() { @Override - public void onVisible(boolean visible) { - if (mAudioManager != null && mVolumeController != null) { - mAudioManager.notifyVolumeControllerVisible(mVolumeController, visible); - } + public void onClick(DialogInterface dialog, int which) { + mVolumeControllerService.setComponent(component); } }); + d.setNegativeButton(R.string.volumeui_prompt_deny, null); + d.show(); } - private final ContentObserver mObserver = new ContentObserver(mHandler) { - public void onChange(boolean selfChange, Uri uri) { - if (SETTING_URI.equals(uri)) { - updateController(); - } - } - }; - - private final Runnable mStartZenSettings = new Runnable() { - @Override - public void run() { - getComponent(PhoneStatusBar.class).startActivityDismissingKeyguard( - ZenModePanel.ZEN_SETTINGS, true /* onlyProvisioned */, true /* dismissShade */); - mPanel.postDismiss(mDismissDelay); - } - }; - - /** For now, simply host an unmodified base volume panel in this process. */ - private final class VolumeController extends IVolumeController.Stub implements VolumeComponent { - - @Override - public void displaySafeVolumeWarning(int flags) throws RemoteException { - mPanel.postDisplaySafeVolumeWarning(flags); - } - + private final class ServiceMonitorCallbacks implements ServiceMonitor.Callbacks { @Override - public void volumeChanged(int streamType, int flags) - throws RemoteException { - mPanel.postVolumeChanged(streamType, flags); - } - - @Override - public void masterVolumeChanged(int flags) throws RemoteException { - mPanel.postMasterVolumeChanged(flags); - } - - @Override - public void masterMuteChanged(int flags) throws RemoteException { - mPanel.postMasterMuteChanged(flags); + public void onNoService() { + if (LOGD) Log.d(TAG, "onNoService"); + setDefaultVolumeController(true); + mRestorationNotification.hide(); + if (!mVolumeControllerService.isPackageAvailable()) { + mVolumeControllerService.setComponent(null); + } } @Override - public void setLayoutDirection(int layoutDirection) - throws RemoteException { - mPanel.postLayoutDirection(layoutDirection); + public long onServiceStartAttempt() { + if (LOGD) Log.d(TAG, "onServiceStartAttempt"); + // poke the setting to update the uid + mVolumeControllerService.setComponent(mVolumeControllerService.getComponent()); + setDefaultVolumeController(false); + getVolumeComponent().dismissNow(); + mRestorationNotification.show(); + return 0; } + } - @Override - public void dismiss() throws RemoteException { - dismissNow(); - } + private final class Receiver extends BroadcastReceiver { + private static final String ENABLE = "com.android.systemui.vui.ENABLE"; + private static final String DISABLE = "com.android.systemui.vui.DISABLE"; + private static final String EXTRA_COMPONENT = "component"; - @Override - public ZenModeController getZenController() { - return mPanel.getZenController(); - } + private static final String PREF = "com.android.systemui.PREF"; + private static final String EXTRA_KEY = "key"; + private static final String EXTRA_VALUE = "value"; - @Override - public void dispatchDemoCommand(String command, Bundle args) { - mPanel.dispatchDemoCommand(command, args); + public void start() { + final IntentFilter filter = new IntentFilter(); + filter.addAction(ENABLE); + filter.addAction(DISABLE); + filter.addAction(PREF); + mContext.registerReceiver(this, filter, null, mHandler); } @Override - public void dismissNow() { - mPanel.postDismiss(0); + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (PREF.equals(action)) { + final String key = intent.getStringExtra(EXTRA_KEY); + if (key != null && intent.getExtras() != null) { + final Object value = intent.getExtras().get(EXTRA_VALUE); + if (value == null) { + Prefs.remove(mContext, key); + } else if (value instanceof Boolean) { + Prefs.putBoolean(mContext, key, (Boolean) value); + } else if (value instanceof Integer) { + Prefs.putInt(mContext, key, (Integer) value); + } else if (value instanceof Long) { + Prefs.putLong(mContext, key, (Long) value); + } + } + return; + } + final ComponentName component = intent.getParcelableExtra(EXTRA_COMPONENT); + final boolean current = component != null + && component.equals(mVolumeControllerService.getComponent()); + if (ENABLE.equals(action) && component != null) { + if (!current) { + showServiceActivationDialog(component); + } + } + if (DISABLE.equals(action) && component != null) { + if (current) { + mVolumeControllerService.setComponent(null); + } + } } } - private final class RemoteVolumeController extends IRemoteVolumeController.Stub { - - @Override - public void remoteVolumeChanged(ISessionController binder, int flags) - throws RemoteException { - MediaController controller = new MediaController(mContext, binder); - mPanel.postRemoteVolumeChanged(controller, flags); + private final class RestorationNotification { + public void hide() { + mNotificationManager.cancel(R.id.notification_volumeui); } - @Override - public void updateRemoteController(ISessionController session) throws RemoteException { - mPanel.postRemoteSliderVisibility(session != null); - // TODO stash default session in case the slider can be opened other - // than by remoteVolumeChanged. + public void show() { + final ComponentName component = mVolumeControllerService.getComponent(); + if (component == null) { + Log.w(TAG, "Not showing restoration notification, component not active"); + return; + } + final Intent intent = new Intent(Receiver.DISABLE) + .putExtra(Receiver.EXTRA_COMPONENT, component); + mNotificationManager.notify(R.id.notification_volumeui, + new Notification.Builder(mContext) + .setSmallIcon(R.drawable.ic_volume_media) + .setWhen(0) + .setShowWhen(false) + .setOngoing(true) + .setContentTitle(mContext.getString( + R.string.volumeui_notification_title, getAppLabel(component))) + .setContentText(mContext.getString(R.string.volumeui_notification_text)) + .setContentIntent(PendingIntent.getBroadcast(mContext, 0, intent, + PendingIntent.FLAG_UPDATE_CURRENT)) + .setPriority(Notification.PRIORITY_MIN) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .setColor(mContext.getColor( + com.android.internal.R.color.system_notification_accent_color)) + .build()); } } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java b/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java new file mode 100644 index 0000000..8aded45 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java @@ -0,0 +1,125 @@ +/* + * 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.content.Context; +import android.provider.Settings.Global; +import android.service.notification.ZenModeConfig; +import android.util.AttributeSet; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.systemui.R; +import com.android.systemui.statusbar.policy.ZenModeController; + +import java.util.Objects; + +/** + * Zen mode information (and end button) attached to the bottom of the volume dialog. + */ +public class ZenFooter extends LinearLayout { + private static final String TAG = Util.logTag(ZenFooter.class); + + private final Context mContext; + + private TextView mSummaryLine1; + private TextView mSummaryLine2; + private View mEndNowButton; + private int mZen = -1; + private ZenModeConfig mConfig; + private ZenModeController mController; + + public ZenFooter(Context context, AttributeSet attrs) { + super(context, attrs); + mContext = context; + setLayoutTransition(new LayoutTransition()); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mSummaryLine1 = (TextView) findViewById(R.id.volume_zen_summary_line_1); + mSummaryLine2 = (TextView) findViewById(R.id.volume_zen_summary_line_2); + mEndNowButton = findViewById(R.id.volume_zen_end_now); + } + + public void init(final ZenModeController controller) { + controller.addCallback(new ZenModeController.Callback() { + @Override + public void onZenChanged(int zen) { + setZen(zen); + } + @Override + public void onConfigChanged(ZenModeConfig config) { + setConfig(config); + } + }); + mEndNowButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + controller.setZen(Global.ZEN_MODE_OFF, null, TAG); + } + }); + mZen = controller.getZen(); + mConfig = controller.getConfig(); + mController = controller; + update(); + } + + private void setZen(int zen) { + if (mZen == zen) return; + mZen = zen; + update(); + } + + private void setConfig(ZenModeConfig config) { + if (Objects.equals(mConfig, config)) return; + mConfig = config; + update(); + } + + public boolean isZen() { + return isZenPriority() || isZenAlarms() || isZenNone(); + } + + private boolean isZenPriority() { + return mZen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + } + + private boolean isZenAlarms() { + return mZen == Global.ZEN_MODE_ALARMS; + } + + private boolean isZenNone() { + return mZen == Global.ZEN_MODE_NO_INTERRUPTIONS; + } + + public void update() { + final String line1 = + isZenPriority() ? mContext.getString(R.string.interruption_level_priority) + : isZenAlarms() ? mContext.getString(R.string.interruption_level_alarms) + : isZenNone() ? mContext.getString(R.string.interruption_level_none) + : null; + Util.setText(mSummaryLine1, line1); + + final String line2 = ZenModeConfig.getConditionSummary(mContext, mConfig, + mController.getCurrentUser()); + Util.setText(mSummaryLine2, line2); + } + +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java index d40a2c0..9f9c9ac 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; @@ -34,6 +32,7 @@ import android.provider.Settings; import android.provider.Settings.Global; import android.service.notification.Condition; import android.service.notification.ZenModeConfig; +import android.service.notification.ZenModeConfig.ZenRule; import android.text.TextUtils; import android.util.ArraySet; import android.util.AttributeSet; @@ -42,8 +41,6 @@ import android.util.MathUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.view.animation.AnimationUtils; -import android.view.animation.Interpolator; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.ImageView; @@ -51,6 +48,7 @@ import android.widget.LinearLayout; import android.widget.RadioButton; import android.widget.TextView; +import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.statusbar.policy.ZenModeController; @@ -75,37 +73,36 @@ public class ZenModePanel extends LinearLayout { private static final int FOREVER_CONDITION_INDEX = 0; private static final int COUNTDOWN_CONDITION_INDEX = 1; - public static final Intent ZEN_SETTINGS = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS); + public static final Intent ZEN_SETTINGS + = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS); + public static final Intent ZEN_PRIORITY_SETTINGS + = new Intent(Settings.ACTION_ZEN_MODE_PRIORITY_SETTINGS); private final Context mContext; private final LayoutInflater mInflater; private final H mHandler = new H(); - private final Prefs mPrefs; + private final ZenPrefs mPrefs; private final IconPulser mIconPulser; - private final int mSubheadWarningColor; - private final int mSubheadColor; - private final Interpolator mInterpolator; - private final int mMaxConditions; - private final int mMaxOptionalConditions; - private final boolean mCountdownConditionSupported; - private final int mFirstConditionIndex; private final TransitionHelper mTransitionHelper = new TransitionHelper(); private final Uri mForeverId; private String mTag = TAG + "/" + Integer.toHexString(System.identityHashCode(this)); private SegmentedButtons mZenButtons; - private View mZenSubhead; - private TextView mZenSubheadCollapsed; - private TextView mZenSubheadExpanded; - private View mMoreSettings; + private View mZenIntroduction; + private TextView mZenIntroductionMessage; + private View mZenIntroductionConfirm; + private View mZenIntroductionCustomize; 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; private int mBucketIndex = -1; private boolean mExpanded; private boolean mHidden; @@ -119,22 +116,9 @@ public class ZenModePanel extends LinearLayout { public ZenModePanel(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; - mPrefs = new Prefs(); + mPrefs = new ZenPrefs(); mInflater = LayoutInflater.from(mContext.getApplicationContext()); mIconPulser = new IconPulser(mContext); - final Resources res = mContext.getResources(); - mSubheadWarningColor = res.getColor(R.color.system_warning_color); - mSubheadColor = res.getColor(R.color.qs_subhead); - mInterpolator = AnimationUtils.loadInterpolator(mContext, - com.android.internal.R.interpolator.fast_out_slow_in); - mCountdownConditionSupported = NotificationManager.from(mContext) - .isSystemConditionProviderEnabled(ZenModeConfig.COUNTDOWN_PATH); - final int countdownDelta = mCountdownConditionSupported ? 1 : 0; - mFirstConditionIndex = COUNTDOWN_CONDITION_INDEX + countdownDelta; - final int minConditions = 1 /*forever*/ + countdownDelta; - mMaxConditions = MathUtils.constrain(res.getInteger(R.integer.zen_mode_max_conditions), - minConditions, 100); - mMaxOptionalConditions = mMaxConditions - minConditions; mForeverId = Condition.newId(mContext).appendPath("forever").build(); if (DEBUG) Log.d(mTag, "new ZenModePanel"); } @@ -149,6 +133,10 @@ public class ZenModePanel extends LinearLayout { pw.print(" mExpanded="); pw.println(mExpanded); pw.print(" mSessionZen="); pw.println(mSessionZen); pw.print(" mAttachedZen="); pw.println(mAttachedZen); + pw.print(" mConfirmedPriorityIntroduction="); + pw.println(mPrefs.mConfirmedPriorityIntroduction); + pw.print(" mConfirmedSilenceIntroduction="); + pw.println(mPrefs.mConfirmedSilenceIntroduction); mTransitionHelper.dump(fd, pw, args); } @@ -157,58 +145,55 @@ public class ZenModePanel extends LinearLayout { super.onFinishInflate(); mZenButtons = (SegmentedButtons) findViewById(R.id.zen_buttons); - mZenButtons.addButton(R.string.interruption_level_none, R.drawable.ic_zen_none, + mZenButtons.addButton(R.string.interruption_level_none_twoline, 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.setCallback(mZenButtonsCallback); - final ViewGroup zenButtonsContainer = (ViewGroup) findViewById(R.id.zen_buttons_container); - zenButtonsContainer.setLayoutTransition(newLayoutTransition(null)); - - mZenSubhead = findViewById(R.id.zen_subhead); - - mZenSubheadCollapsed = (TextView) findViewById(R.id.zen_subhead_collapsed); - mZenSubheadCollapsed.setOnClickListener(new View.OnClickListener() { + mZenIntroduction = findViewById(R.id.zen_introduction); + mZenIntroductionMessage = (TextView) findViewById(R.id.zen_introduction_message); + mZenIntroductionConfirm = findViewById(R.id.zen_introduction_confirm); + mZenIntroductionConfirm.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - setExpanded(true); + confirmZenIntroduction(); } }); - Interaction.register(mZenSubheadCollapsed, mInteractionCallback); - - mZenSubheadExpanded = (TextView) findViewById(R.id.zen_subhead_expanded); - Interaction.register(mZenSubheadExpanded, mInteractionCallback); - - mMoreSettings = findViewById(R.id.zen_more_settings); - mMoreSettings.setOnClickListener(new View.OnClickListener() { + mZenIntroductionCustomize = findViewById(R.id.zen_introduction_customize); + mZenIntroductionCustomize.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - fireMoreSettings(); + confirmZenIntroduction(); + if (mCallback != null) { + mCallback.onPrioritySettings(); + } } }); - Interaction.register(mMoreSettings, mInteractionCallback); mZenConditions = (LinearLayout) findViewById(R.id.zen_conditions); - for (int i = 0; i < mMaxConditions; i++) { - mZenConditions.addView(mInflater.inflate(R.layout.zen_mode_condition, this, false)); - } - setLayoutTransition(newLayoutTransition(mTransitionHelper)); } - private LayoutTransition newLayoutTransition(TransitionListener listener) { - final LayoutTransition transition = new LayoutTransition(); - transition.disableTransitionType(LayoutTransition.DISAPPEARING); - transition.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING); - transition.disableTransitionType(LayoutTransition.APPEARING); - transition.setInterpolator(LayoutTransition.CHANGE_APPEARING, mInterpolator); - if (listener != null) { - transition.addTransitionListener(listener); + private void confirmZenIntroduction() { + final String prefKey = prefKeyForConfirmation(getSelectedZen(Global.ZEN_MODE_OFF)); + if (prefKey == null) return; + if (DEBUG) Log.d(TAG, "confirmZenIntroduction " + prefKey); + Prefs.putBoolean(mContext, prefKey, true); + mHandler.sendEmptyMessage(H.UPDATE_WIDGETS); + } + + private static String prefKeyForConfirmation(int zen) { + switch (zen) { + case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: + return Prefs.Key.DND_CONFIRMED_PRIORITY_INTRODUCTION; + case Global.ZEN_MODE_NO_INTERRUPTIONS: + return Prefs.Key.DND_CONFIRMED_SILENCE_INTRODUCTION; + default: + return null; } - return transition; } @Override @@ -220,7 +205,6 @@ public class ZenModePanel extends LinearLayout { mSessionZen = mAttachedZen; mTransitionHelper.clear(); setSessionExitCondition(copy(mExitCondition)); - refreshExitConditionText(); updateWidgets(); setRequestingConditions(!mHidden); } @@ -234,7 +218,6 @@ public class ZenModePanel extends LinearLayout { mAttachedZen = -1; mSessionZen = -1; setSessionExitCondition(null); - setExpanded(false); setRequestingConditions(false); mTransitionHelper.clear(); } @@ -266,8 +249,9 @@ public class ZenModePanel extends LinearLayout { private void setExpanded(boolean expanded) { if (expanded == mExpanded) return; + if (DEBUG) Log.d(mTag, "setExpanded " + expanded); mExpanded = expanded; - if (mExpanded) { + if (mExpanded && isShown()) { ensureSelection(); } updateWidgets(); @@ -288,7 +272,7 @@ public class ZenModePanel extends LinearLayout { }); } if (mRequestingConditions) { - mTimeCondition = parseExistingTimeCondition(mExitCondition); + mTimeCondition = parseExistingTimeCondition(mContext, mExitCondition); if (mTimeCondition != null) { mBucketIndex = -1; } else { @@ -306,10 +290,18 @@ public class ZenModePanel extends LinearLayout { public void init(ZenModeController controller) { mController = controller; - setExitCondition(mController.getExitCondition()); - refreshExitConditionText(); + mCountdownConditionSupported = mController.isCountdownConditionSupported(); + final int countdownDelta = mCountdownConditionSupported ? 1 : 0; + mFirstConditionIndex = COUNTDOWN_CONDITION_INDEX + countdownDelta; + final int minConditions = 1 /*forever*/ + countdownDelta; + mMaxConditions = MathUtils.constrain(mContext.getResources() + .getInteger(R.integer.zen_mode_max_conditions), minConditions, 100); + mMaxOptionalConditions = mMaxConditions - minConditions; + for (int i = 0; i < mMaxConditions; i++) { + mZenConditions.addView(mInflater.inflate(R.layout.zen_mode_condition, this, false)); + } mSessionZen = getSelectedZen(-1); - handleUpdateZen(mController.getZen()); + handleUpdateManualRule(mController.getManualRule()); if (DEBUG) Log.d(mTag, "init mExitCondition=" + mExitCondition); hideAllConditions(); mController.addCallback(mZenCallback); @@ -323,7 +315,6 @@ public class ZenModePanel extends LinearLayout { if (Objects.equals(mExitCondition, exitCondition)) return; mExitCondition = exitCondition; if (DEBUG) Log.d(mTag, "mExitCondition=" + getConditionId(mExitCondition)); - refreshExitConditionText(); updateWidgets(); } @@ -331,6 +322,10 @@ public class ZenModePanel extends LinearLayout { return condition != null ? condition.id : null; } + private Uri getRealConditionId(Condition condition) { + return isForever(condition) ? null : getConditionId(condition); + } + private static boolean sameConditionId(Condition lhs, Condition rhs) { return lhs == null ? rhs == null : rhs != null && lhs.id.equals(rhs.id); } @@ -339,14 +334,14 @@ public class ZenModePanel extends LinearLayout { return condition == null ? null : condition.copy(); } - private void refreshExitConditionText() { - if (mExitCondition == null) { - mExitConditionText = foreverSummary(); - } else if (isCountdown(mExitCondition)) { - final Condition condition = parseExistingTimeCondition(mExitCondition); - mExitConditionText = condition != null ? condition.summary : foreverSummary(); + public static String getExitConditionText(Context context, Condition exitCondition) { + if (exitCondition == null) { + return foreverSummary(context); + } else if (isCountdown(exitCondition)) { + final Condition condition = parseExistingTimeCondition(context, exitCondition); + return condition != null ? condition.summary : foreverSummary(context); } else { - mExitConditionText = mExitCondition.summary; + return exitCondition.summary; } } @@ -361,9 +356,16 @@ public class ZenModePanel extends LinearLayout { mIconPulser.start(noneButton); } + private void handleUpdateManualRule(ZenRule rule) { + final int zen = rule != null ? rule.zenMode : Global.ZEN_MODE_OFF; + handleUpdateZen(zen); + final Condition c = rule != null ? rule.condition : null; + handleExitConditionChanged(c); + } + private void handleUpdateZen(int zen) { if (mSessionZen != -1 && mSessionZen != zen) { - setExpanded(zen != Global.ZEN_MODE_OFF); + setExpanded(isShown()); mSessionZen = zen; } mZenButtons.setSelectedValue(zen); @@ -377,6 +379,20 @@ public class ZenModePanel extends LinearLayout { } } + private void handleExitConditionChanged(Condition exitCondition) { + setExitCondition(exitCondition); + if (DEBUG) Log.d(mTag, "handleExitConditionChanged " + mExitCondition); + final int N = getVisibleConditions(); + for (int i = 0; i < N; i++) { + final ConditionTag tag = getConditionTagAt(i); + if (tag != null) { + if (sameConditionId(tag.condition, mExitCondition)) { + bind(exitCondition, mZenConditions.getChildAt(i)); + } + } + } + } + private Condition getSelectedCondition() { final int N = getVisibleConditions(); for (int i = 0; i < N; i++) { @@ -399,37 +415,28 @@ public class ZenModePanel extends LinearLayout { return; } final int zen = getSelectedZen(Global.ZEN_MODE_OFF); - final boolean zenOff = zen == Global.ZEN_MODE_OFF; final boolean zenImportant = zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; final boolean zenNone = zen == Global.ZEN_MODE_NO_INTERRUPTIONS; - final boolean expanded = !mHidden && mExpanded; + final boolean introduction = (zenImportant && !mPrefs.mConfirmedPriorityIntroduction + || zenNone && !mPrefs.mConfirmedSilenceIntroduction); mZenButtons.setVisibility(mHidden ? GONE : VISIBLE); - mZenSubhead.setVisibility(!mHidden && !zenOff ? VISIBLE : GONE); - mZenSubheadExpanded.setVisibility(expanded ? VISIBLE : GONE); - mZenSubheadCollapsed.setVisibility(!expanded ? VISIBLE : GONE); - mMoreSettings.setVisibility(zenImportant && expanded ? VISIBLE : GONE); - mZenConditions.setVisibility(!zenOff && expanded ? VISIBLE : GONE); - - if (zenNone) { - mZenSubheadExpanded.setText(R.string.zen_no_interruptions_with_warning); - mZenSubheadCollapsed.setText(mExitConditionText); - } else if (zenImportant) { - mZenSubheadExpanded.setText(R.string.zen_important_interruptions); - mZenSubheadCollapsed.setText(mExitConditionText); + mZenIntroduction.setVisibility(introduction ? VISIBLE : GONE); + if (introduction) { + mZenIntroductionMessage.setText(zenImportant ? R.string.zen_priority_introduction + : R.string.zen_silence_introduction); + mZenIntroductionCustomize.setVisibility(zenImportant ? VISIBLE : GONE); } - mZenSubheadExpanded.setTextColor(zenNone && mPrefs.isNoneDangerous() - ? mSubheadWarningColor : mSubheadColor); } - private Condition parseExistingTimeCondition(Condition condition) { + private static Condition parseExistingTimeCondition(Context context, Condition condition) { if (condition == null) return null; final long time = ZenModeConfig.tryParseCountdownConditionId(condition.id); if (time == 0) return null; final long now = System.currentTimeMillis(); final long span = time - now; if (span <= 0 || span > MAX_BUCKET_MINUTES * MINUTES_MS) return null; - return ZenModeConfig.toTimeCondition(mContext, + return ZenModeConfig.toTimeCondition(context, time, Math.round(span / (float) MINUTES_MS), now, ActivityManager.getCurrentUser()); } @@ -489,18 +496,18 @@ public class ZenModePanel extends LinearLayout { mZenConditions.getChildAt(i).setVisibility(GONE); } // ensure something is selected - if (mExpanded) { + if (mExpanded && isShown()) { ensureSelection(); } } private Condition forever() { - return new Condition(mForeverId, foreverSummary(), "", "", 0 /*icon*/, Condition.STATE_TRUE, - 0 /*flags*/); + return new Condition(mForeverId, foreverSummary(mContext), "", "", 0 /*icon*/, + Condition.STATE_TRUE, 0 /*flags*/); } - private String foreverSummary() { - return mContext.getString(com.android.internal.R.string.zen_mode_forever); + private static String foreverSummary(Context context) { + return context.getString(com.android.internal.R.string.zen_mode_forever); } private ConditionTag getConditionTagAt(int index) { @@ -549,21 +556,7 @@ public class ZenModePanel extends LinearLayout { } } - private void handleExitConditionChanged(Condition exitCondition) { - setExitCondition(exitCondition); - if (DEBUG) Log.d(mTag, "handleExitConditionChanged " + mExitCondition); - final int N = getVisibleConditions(); - for (int i = 0; i < N; i++) { - final ConditionTag tag = getConditionTagAt(i); - if (tag != null) { - if (sameConditionId(tag.condition, mExitCondition)) { - bind(exitCondition, mZenConditions.getChildAt(i)); - } - } - } - } - - private boolean isCountdown(Condition c) { + private static boolean isCountdown(Condition c) { return c != null && ZenModeConfig.isValidCountdownConditionId(c.id); } @@ -691,12 +684,15 @@ public class ZenModePanel extends LinearLayout { String modeText; switch(zen) { case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: - modeText = mContext.getString(R.string.zen_important_interruptions); + modeText = mContext.getString(R.string.interruption_level_priority); break; case Global.ZEN_MODE_NO_INTERRUPTIONS: - modeText = mContext.getString(R.string.zen_no_interruptions); + modeText = mContext.getString(R.string.interruption_level_none); + break; + case Global.ZEN_MODE_ALARMS: + modeText = mContext.getString(R.string.interruption_level_alarms); break; - default: + default: return; } announceForAccessibility(mContext.getString(R.string.zen_mode_and_condition, modeText, @@ -742,17 +738,21 @@ public class ZenModePanel extends LinearLayout { private void select(final Condition condition) { if (DEBUG) Log.d(mTag, "select " + condition); - final boolean isForever = isForever(condition); + if (mSessionZen == -1 || mSessionZen == Global.ZEN_MODE_OFF) { + if (DEBUG) Log.d(mTag, "Ignoring condition selection outside of manual zen"); + return; + } + final Uri realConditionId = getRealConditionId(condition); if (mController != null) { AsyncTask.execute(new Runnable() { @Override public void run() { - mController.setExitCondition(isForever ? null : condition); + mController.setZen(mSessionZen, realConditionId, TAG + ".selectCondition"); } }); } setExitCondition(condition); - if (isForever) { + if (realConditionId == null) { mPrefs.setMinuteIndex(-1); } else if (isCountdown(condition) && mBucketIndex != -1) { mPrefs.setMinuteIndex(mBucketIndex); @@ -760,12 +760,6 @@ public class ZenModePanel extends LinearLayout { setSessionExitCondition(copy(condition)); } - private void fireMoreSettings() { - if (mCallback != null) { - mCallback.onMoreSettings(); - } - } - private void fireInteraction() { if (mCallback != null) { mCallback.onInteraction(); @@ -780,24 +774,20 @@ public class ZenModePanel extends LinearLayout { private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() { @Override - public void onZenChanged(int zen) { - mHandler.obtainMessage(H.UPDATE_ZEN, zen, 0).sendToTarget(); - } - @Override public void onConditionsChanged(Condition[] conditions) { mHandler.obtainMessage(H.UPDATE_CONDITIONS, conditions).sendToTarget(); } @Override - public void onExitConditionChanged(Condition exitCondition) { - mHandler.obtainMessage(H.EXIT_CONDITION_CHANGED, exitCondition).sendToTarget(); + public void onManualRuleChanged(ZenRule rule) { + mHandler.obtainMessage(H.MANUAL_RULE_CHANGED, rule).sendToTarget(); } }; private final class H extends Handler { private static final int UPDATE_CONDITIONS = 1; - private static final int EXIT_CONDITION_CHANGED = 2; - private static final int UPDATE_ZEN = 3; + private static final int MANUAL_RULE_CHANGED = 2; + private static final int UPDATE_WIDGETS = 3; private H() { super(Looper.getMainLooper()); @@ -805,18 +795,16 @@ public class ZenModePanel extends LinearLayout { @Override public void handleMessage(Message msg) { - if (msg.what == UPDATE_CONDITIONS) { - handleUpdateConditions((Condition[]) msg.obj); - } else if (msg.what == EXIT_CONDITION_CHANGED) { - handleExitConditionChanged((Condition) msg.obj); - } else if (msg.what == UPDATE_ZEN) { - handleUpdateZen(msg.arg1); + switch (msg.what) { + case UPDATE_CONDITIONS: handleUpdateConditions((Condition[]) msg.obj); break; + case MANUAL_RULE_CHANGED: handleUpdateManualRule((ZenRule) msg.obj); break; + case UPDATE_WIDGETS: updateWidgets(); break; } } } public interface Callback { - void onMoreSettings(); + void onPrioritySettings(); void onInteraction(); void onExpanded(boolean expanded); } @@ -830,32 +818,29 @@ public class ZenModePanel extends LinearLayout { Condition condition; } - private final class Prefs implements OnSharedPreferenceChangeListener { - private static final String KEY_MINUTE_INDEX = "minuteIndex"; - private static final String KEY_NONE_SELECTED = "noneSelected"; - + private final class ZenPrefs implements OnSharedPreferenceChangeListener { private final int mNoneDangerousThreshold; private int mMinuteIndex; private int mNoneSelected; + private boolean mConfirmedPriorityIntroduction; + private boolean mConfirmedSilenceIntroduction; - private Prefs() { + private ZenPrefs() { mNoneDangerousThreshold = mContext.getResources() .getInteger(R.integer.zen_mode_alarm_warning_threshold); - prefs().registerOnSharedPreferenceChangeListener(this); + Prefs.registerListener(mContext, this); updateMinuteIndex(); updateNoneSelected(); - } - - public boolean isNoneDangerous() { - return mNoneSelected < mNoneDangerousThreshold; + updateConfirmedPriorityIntroduction(); + updateConfirmedSilenceIntroduction(); } public void trackNoneSelected() { mNoneSelected = clampNoneSelected(mNoneSelected + 1); if (DEBUG) Log.d(mTag, "Setting none selected: " + mNoneSelected + " threshold=" + mNoneDangerousThreshold); - prefs().edit().putInt(KEY_NONE_SELECTED, mNoneSelected).apply(); + Prefs.putInt(mContext, Prefs.Key.DND_NONE_SELECTED, mNoneSelected); } public int getMinuteIndex() { @@ -867,21 +852,20 @@ public class ZenModePanel extends LinearLayout { if (minuteIndex == mMinuteIndex) return; mMinuteIndex = clampIndex(minuteIndex); if (DEBUG) Log.d(mTag, "Setting favorite minute index: " + mMinuteIndex); - prefs().edit().putInt(KEY_MINUTE_INDEX, mMinuteIndex).apply(); + Prefs.putInt(mContext, Prefs.Key.DND_FAVORITE_BUCKET_INDEX, mMinuteIndex); } @Override public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { updateMinuteIndex(); updateNoneSelected(); - } - - private SharedPreferences prefs() { - return mContext.getSharedPreferences(mContext.getPackageName(), 0); + updateConfirmedPriorityIntroduction(); + updateConfirmedSilenceIntroduction(); } private void updateMinuteIndex() { - mMinuteIndex = clampIndex(prefs().getInt(KEY_MINUTE_INDEX, DEFAULT_BUCKET_INDEX)); + mMinuteIndex = clampIndex(Prefs.getInt(mContext, + Prefs.Key.DND_FAVORITE_BUCKET_INDEX, DEFAULT_BUCKET_INDEX)); if (DEBUG) Log.d(mTag, "Favorite minute index: " + mMinuteIndex); } @@ -890,24 +874,48 @@ public class ZenModePanel extends LinearLayout { } private void updateNoneSelected() { - mNoneSelected = clampNoneSelected(prefs().getInt(KEY_NONE_SELECTED, 0)); + mNoneSelected = clampNoneSelected(Prefs.getInt(mContext, + Prefs.Key.DND_NONE_SELECTED, 0)); if (DEBUG) Log.d(mTag, "None selected: " + mNoneSelected); } private int clampNoneSelected(int noneSelected) { return MathUtils.constrain(noneSelected, 0, Integer.MAX_VALUE); } + + private void updateConfirmedPriorityIntroduction() { + final boolean confirmed = Prefs.getBoolean(mContext, + Prefs.Key.DND_CONFIRMED_PRIORITY_INTRODUCTION, false); + if (confirmed == mConfirmedPriorityIntroduction) return; + mConfirmedPriorityIntroduction = confirmed; + if (DEBUG) Log.d(mTag, "Confirmed priority introduction: " + + mConfirmedPriorityIntroduction); + } + + private void updateConfirmedSilenceIntroduction() { + final boolean confirmed = Prefs.getBoolean(mContext, + Prefs.Key.DND_CONFIRMED_SILENCE_INTRODUCTION, false); + if (confirmed == mConfirmedSilenceIntroduction) return; + mConfirmedSilenceIntroduction = confirmed; + if (DEBUG) Log.d(mTag, "Confirmed silence introduction: " + + mConfirmedSilenceIntroduction); + } } private final SegmentedButtons.Callback mZenButtonsCallback = new SegmentedButtons.Callback() { @Override public void onSelected(final Object value) { - if (value != null && mZenButtons.isShown()) { - if (DEBUG) Log.d(mTag, "mZenButtonsCallback selected=" + value); + if (value != null && mZenButtons.isShown() && isAttachedToWindow()) { + final int zen = (Integer) value; + if (DEBUG) Log.d(mTag, "mZenButtonsCallback selected=" + zen); + final Uri realConditionId = getRealConditionId(mSessionExitCondition); AsyncTask.execute(new Runnable() { @Override public void run() { - mController.setZen((Integer) value); + mController.setZen(zen, realConditionId, TAG + ".selectZen"); + if (zen != Global.ZEN_MODE_OFF) { + Prefs.putInt(mContext, Prefs.Key.DND_FAVORITE_ZEN, zen); + } } }); } |
