diff options
Diffstat (limited to 'packages/SystemUI/src')
161 files changed, 11156 insertions, 9749 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java index 7bdbd0a..292c9c2 100755 --- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java +++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java @@ -16,6 +16,7 @@ package com.android.systemui; +import android.animation.ArgbEvaluator; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -23,8 +24,11 @@ import android.content.IntentFilter; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.graphics.RectF; import android.graphics.Typeface; import android.os.BatteryManager; @@ -56,12 +60,13 @@ public class BatteryMeterView extends View implements DemoMode, private float mSubpixelSmoothingRight; private final Paint mFramePaint, mBatteryPaint, mWarningTextPaint, mTextPaint, mBoltPaint; private float mTextHeight, mWarningTextHeight; + private int mIconTint = Color.WHITE; private int mHeight; private int mWidth; private String mWarningString; private final int mCriticalLevel; - private final int mChargeColor; + private int mChargeColor; private final float[] mBoltPoints; private final Path mBoltPath = new Path(); @@ -76,6 +81,12 @@ public class BatteryMeterView extends View implements DemoMode, private BatteryController mBatteryController; private boolean mPowerSaveEnabled; + private int mDarkModeBackgroundColor; + private int mDarkModeFillColor; + + private int mLightModeBackgroundColor; + private int mLightModeFillColor; + private class BatteryTracker extends BroadcastReceiver { public static final int UNKNOWN_LEVEL = -1; @@ -189,7 +200,7 @@ public class BatteryMeterView extends View implements DemoMode, TypedArray atts = context.obtainStyledAttributes(attrs, R.styleable.BatteryMeterView, defStyle, 0); final int frameColor = atts.getColor(R.styleable.BatteryMeterView_frameColor, - res.getColor(R.color.batterymeter_frame_color)); + context.getColor(R.color.batterymeter_frame_color)); TypedArray levels = res.obtainTypedArray(R.array.batterymeter_color_levels); TypedArray colors = res.obtainTypedArray(R.array.batterymeter_color_values); @@ -236,11 +247,18 @@ public class BatteryMeterView extends View implements DemoMode, mWarningTextPaint.setTypeface(font); mWarningTextPaint.setTextAlign(Paint.Align.CENTER); - mChargeColor = getResources().getColor(R.color.batterymeter_charge_color); + mChargeColor = context.getColor(R.color.batterymeter_charge_color); mBoltPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mBoltPaint.setColor(res.getColor(R.color.batterymeter_bolt_color)); + mBoltPaint.setColor(context.getColor(R.color.batterymeter_bolt_color)); mBoltPoints = loadBoltPoints(res); + + mDarkModeBackgroundColor = + context.getColor(R.color.dark_mode_icon_color_dual_tone_background); + mDarkModeFillColor = context.getColor(R.color.dark_mode_icon_color_dual_tone_fill); + mLightModeBackgroundColor = + context.getColor(R.color.light_mode_icon_color_dual_tone_background); + mLightModeFillColor = context.getColor(R.color.light_mode_icon_color_dual_tone_fill); } public void setBatteryController(BatteryController batteryController) { @@ -292,11 +310,43 @@ public class BatteryMeterView extends View implements DemoMode, for (int i=0; i<mColors.length; i+=2) { thresh = mColors[i]; color = mColors[i+1]; - if (percent <= thresh) return color; + if (percent <= thresh) { + + // Respect tinting for "normal" level + if (i == mColors.length-2) { + return mIconTint; + } else { + return color; + } + } } return color; } + public void setDarkIntensity(float darkIntensity) { + int backgroundColor = getBackgroundColor(darkIntensity); + int fillColor = getFillColor(darkIntensity); + mIconTint = fillColor; + mFramePaint.setColor(backgroundColor); + mBoltPaint.setColor(backgroundColor); + mChargeColor = fillColor; + invalidate(); + } + + private int getBackgroundColor(float darkIntensity) { + return getColorForDarkIntensity( + darkIntensity, mLightModeBackgroundColor, mDarkModeBackgroundColor); + } + + private int getFillColor(float darkIntensity) { + return getColorForDarkIntensity( + darkIntensity, mLightModeFillColor, mDarkModeFillColor); + } + + private int getColorForDarkIntensity(float darkIntensity, int lightColor, int darkColor) { + return (int) ArgbEvaluator.getInstance().evaluate(darkIntensity, lightColor, darkColor); + } + @Override public void draw(Canvas c) { BatteryTracker tracker = mDemoMode ? mDemoTracker : mTracker; diff --git a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java index d42ac61..bc7f745 100644 --- a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java +++ b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java @@ -22,7 +22,6 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.content.Context; import android.media.AudioAttributes; -import android.media.AudioManager; import android.os.Vibrator; import android.util.Log; import android.view.Gravity; @@ -147,14 +146,14 @@ public class ExpandHelper implements Gefingerpoken { } public void setHeight(float h) { if (DEBUG_SCALE) Log.v(TAG, "SetHeight: setting to " + h); - mView.setActualHeight((int) h); + mView.setContentHeight((int) h); mCurrentHeight = h; } public float getHeight() { - return mView.getActualHeight(); + return mView.getContentHeight(); } public int getNaturalHeight(int maximum) { - return Math.min(maximum, mView.getMaxHeight()); + return Math.min(maximum, mView.getMaxContentHeight()); } } @@ -387,7 +386,8 @@ public class ExpandHelper implements Gefingerpoken { } private boolean isFullyExpanded(ExpandableView underFocus) { - return underFocus.getIntrinsicHeight() == underFocus.getMaxHeight(); + return underFocus.areChildrenExpanded() || underFocus.getIntrinsicHeight() + - underFocus.getBottomDecorHeight() == underFocus.getMaxContentHeight(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java index 7c725b3..6888d0e 100644 --- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java @@ -21,10 +21,8 @@ import static javax.microedition.khronos.egl.EGL10.*; import android.app.ActivityManager; import android.app.WallpaperManager; -import android.content.BroadcastReceiver; import android.content.ComponentCallbacks2; import android.content.Context; -import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Point; diff --git a/packages/SystemUI/src/com/android/systemui/SearchPanelCircleView.java b/packages/SystemUI/src/com/android/systemui/SearchPanelCircleView.java deleted file mode 100644 index d8fb6da..0000000 --- a/packages/SystemUI/src/com/android/systemui/SearchPanelCircleView.java +++ /dev/null @@ -1,592 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.PropertyValuesHolder; -import android.animation.ValueAnimator; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Outline; -import android.graphics.Paint; -import android.graphics.Rect; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewOutlineProvider; -import android.view.animation.AnimationUtils; -import android.view.animation.Interpolator; -import android.view.animation.LinearInterpolator; -import android.widget.FrameLayout; -import android.widget.ImageView; -import com.android.systemui.statusbar.phone.PhoneStatusBar; - -import java.util.ArrayList; - -public class SearchPanelCircleView extends FrameLayout { - - private final int mCircleMinSize; - private final int mBaseMargin; - private final int mStaticOffset; - private final Paint mBackgroundPaint = new Paint(); - private final Paint mRipplePaint = new Paint(); - private final Rect mCircleRect = new Rect(); - private final Rect mStaticRect = new Rect(); - private final Interpolator mFastOutSlowInInterpolator; - private final Interpolator mAppearInterpolator; - private final Interpolator mDisappearInterpolator; - - private boolean mClipToOutline; - private final int mMaxElevation; - private boolean mAnimatingOut; - private float mOutlineAlpha; - private float mOffset; - private float mCircleSize; - private boolean mHorizontal; - private boolean mCircleHidden; - private ImageView mLogo; - private boolean mDraggedFarEnough; - private boolean mOffsetAnimatingIn; - private float mCircleAnimationEndValue; - private ArrayList<Ripple> mRipples = new ArrayList<Ripple>(); - - private ValueAnimator mOffsetAnimator; - private ValueAnimator mCircleAnimator; - private ValueAnimator mFadeOutAnimator; - private ValueAnimator.AnimatorUpdateListener mCircleUpdateListener - = new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - applyCircleSize((float) animation.getAnimatedValue()); - updateElevation(); - } - }; - private AnimatorListenerAdapter mClearAnimatorListener = new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mCircleAnimator = null; - } - }; - private ValueAnimator.AnimatorUpdateListener mOffsetUpdateListener - = new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - setOffset((float) animation.getAnimatedValue()); - } - }; - - - public SearchPanelCircleView(Context context) { - this(context, null); - } - - public SearchPanelCircleView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public SearchPanelCircleView(Context context, AttributeSet attrs, int defStyleAttr) { - this(context, attrs, defStyleAttr, 0); - } - - public SearchPanelCircleView(Context context, AttributeSet attrs, int defStyleAttr, - int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - setOutlineProvider(new ViewOutlineProvider() { - @Override - public void getOutline(View view, Outline outline) { - if (mCircleSize > 0.0f) { - outline.setOval(mCircleRect); - } else { - outline.setEmpty(); - } - outline.setAlpha(mOutlineAlpha); - } - }); - setWillNotDraw(false); - mCircleMinSize = context.getResources().getDimensionPixelSize( - R.dimen.search_panel_circle_size); - mBaseMargin = context.getResources().getDimensionPixelSize( - R.dimen.search_panel_circle_base_margin); - mStaticOffset = context.getResources().getDimensionPixelSize( - R.dimen.search_panel_circle_travel_distance); - mMaxElevation = context.getResources().getDimensionPixelSize( - R.dimen.search_panel_circle_elevation); - mAppearInterpolator = AnimationUtils.loadInterpolator(mContext, - android.R.interpolator.linear_out_slow_in); - mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(mContext, - android.R.interpolator.fast_out_slow_in); - mDisappearInterpolator = AnimationUtils.loadInterpolator(mContext, - android.R.interpolator.fast_out_linear_in); - mBackgroundPaint.setAntiAlias(true); - mBackgroundPaint.setColor(getResources().getColor(R.color.search_panel_circle_color)); - mRipplePaint.setColor(getResources().getColor(R.color.search_panel_ripple_color)); - mRipplePaint.setAntiAlias(true); - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - drawBackground(canvas); - drawRipples(canvas); - } - - private void drawRipples(Canvas canvas) { - for (int i = 0; i < mRipples.size(); i++) { - Ripple ripple = mRipples.get(i); - ripple.draw(canvas); - } - } - - private void drawBackground(Canvas canvas) { - canvas.drawCircle(mCircleRect.centerX(), mCircleRect.centerY(), mCircleSize / 2, - mBackgroundPaint); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mLogo = (ImageView) findViewById(R.id.search_logo); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - mLogo.layout(0, 0, mLogo.getMeasuredWidth(), mLogo.getMeasuredHeight()); - if (changed) { - updateCircleRect(mStaticRect, mStaticOffset, true); - } - } - - public void setCircleSize(float circleSize) { - setCircleSize(circleSize, false, null, 0, null); - } - - public void setCircleSize(float circleSize, boolean animated, final Runnable endRunnable, - int startDelay, Interpolator interpolator) { - boolean isAnimating = mCircleAnimator != null; - boolean animationPending = isAnimating && !mCircleAnimator.isRunning(); - boolean animatingOut = isAnimating && mCircleAnimationEndValue == 0; - if (animated || animationPending || animatingOut) { - if (isAnimating) { - if (circleSize == mCircleAnimationEndValue) { - return; - } - mCircleAnimator.cancel(); - } - mCircleAnimator = ValueAnimator.ofFloat(mCircleSize, circleSize); - mCircleAnimator.addUpdateListener(mCircleUpdateListener); - mCircleAnimator.addListener(mClearAnimatorListener); - mCircleAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (endRunnable != null) { - endRunnable.run(); - } - } - }); - Interpolator desiredInterpolator = interpolator != null ? interpolator - : circleSize == 0 ? mDisappearInterpolator : mAppearInterpolator; - mCircleAnimator.setInterpolator(desiredInterpolator); - mCircleAnimator.setDuration(300); - mCircleAnimator.setStartDelay(startDelay); - mCircleAnimator.start(); - mCircleAnimationEndValue = circleSize; - } else { - if (isAnimating) { - float diff = circleSize - mCircleAnimationEndValue; - PropertyValuesHolder[] values = mCircleAnimator.getValues(); - values[0].setFloatValues(diff, circleSize); - mCircleAnimator.setCurrentPlayTime(mCircleAnimator.getCurrentPlayTime()); - mCircleAnimationEndValue = circleSize; - } else { - applyCircleSize(circleSize); - updateElevation(); - } - } - } - - private void applyCircleSize(float circleSize) { - mCircleSize = circleSize; - updateLayout(); - } - - private void updateElevation() { - float t = (mStaticOffset - mOffset) / (float) mStaticOffset; - t = 1.0f - Math.max(t, 0.0f); - float offset = t * mMaxElevation; - setElevation(offset); - } - - /** - * Sets the offset to the edge of the screen. By default this not not animated. - * - * @param offset The offset to apply. - */ - public void setOffset(float offset) { - setOffset(offset, false, 0, null, null); - } - - /** - * Sets the offset to the edge of the screen. - * - * @param offset The offset to apply. - * @param animate Whether an animation should be performed. - * @param startDelay The desired start delay if animated. - * @param interpolator The desired interpolator if animated. If null, - * a default interpolator will be taken designed for appearing or - * disappearing. - * @param endRunnable The end runnable which should be executed when the animation is finished. - */ - private void setOffset(float offset, boolean animate, int startDelay, - Interpolator interpolator, final Runnable endRunnable) { - if (!animate) { - mOffset = offset; - updateLayout(); - if (endRunnable != null) { - endRunnable.run(); - } - } else { - if (mOffsetAnimator != null) { - mOffsetAnimator.removeAllListeners(); - mOffsetAnimator.cancel(); - } - mOffsetAnimator = ValueAnimator.ofFloat(mOffset, offset); - mOffsetAnimator.addUpdateListener(mOffsetUpdateListener); - mOffsetAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mOffsetAnimator = null; - if (endRunnable != null) { - endRunnable.run(); - } - } - }); - Interpolator desiredInterpolator = interpolator != null ? - interpolator : offset == 0 ? mDisappearInterpolator : mAppearInterpolator; - mOffsetAnimator.setInterpolator(desiredInterpolator); - mOffsetAnimator.setStartDelay(startDelay); - mOffsetAnimator.setDuration(300); - mOffsetAnimator.start(); - mOffsetAnimatingIn = offset != 0; - } - } - - private void updateLayout() { - updateCircleRect(); - updateLogo(); - invalidateOutline(); - invalidate(); - updateClipping(); - } - - private void updateClipping() { - boolean clip = mCircleSize < mCircleMinSize || !mRipples.isEmpty(); - if (clip != mClipToOutline) { - setClipToOutline(clip); - mClipToOutline = clip; - } - } - - private void updateLogo() { - boolean exitAnimationRunning = mFadeOutAnimator != null; - Rect rect = exitAnimationRunning ? mCircleRect : mStaticRect; - float translationX = (rect.left + rect.right) / 2.0f - mLogo.getWidth() / 2.0f; - float translationY = (rect.top + rect.bottom) / 2.0f - mLogo.getHeight() / 2.0f; - float t = (mStaticOffset - mOffset) / (float) mStaticOffset; - if (!exitAnimationRunning) { - if (mHorizontal) { - translationX += t * mStaticOffset * 0.3f; - } else { - translationY += t * mStaticOffset * 0.3f; - } - float alpha = 1.0f-t; - alpha = Math.max((alpha - 0.5f) * 2.0f, 0); - mLogo.setAlpha(alpha); - } else { - translationY += (mOffset - mStaticOffset) / 2; - } - mLogo.setTranslationX(translationX); - mLogo.setTranslationY(translationY); - } - - private void updateCircleRect() { - updateCircleRect(mCircleRect, mOffset, false); - } - - private void updateCircleRect(Rect rect, float offset, boolean useStaticSize) { - int left, top; - float circleSize = useStaticSize ? mCircleMinSize : mCircleSize; - if (mHorizontal) { - left = (int) (getWidth() - circleSize / 2 - mBaseMargin - offset); - top = (int) ((getHeight() - circleSize) / 2); - } else { - left = (int) (getWidth() - circleSize) / 2; - top = (int) (getHeight() - circleSize / 2 - mBaseMargin - offset); - } - rect.set(left, top, (int) (left + circleSize), (int) (top + circleSize)); - } - - public void setHorizontal(boolean horizontal) { - mHorizontal = horizontal; - updateCircleRect(mStaticRect, mStaticOffset, true); - updateLayout(); - } - - public void setDragDistance(float distance) { - if (!mAnimatingOut && (!mCircleHidden || mDraggedFarEnough)) { - float circleSize = mCircleMinSize + rubberband(distance); - setCircleSize(circleSize); - } - - } - - private float rubberband(float diff) { - return (float) Math.pow(Math.abs(diff), 0.6f); - } - - public void startAbortAnimation(Runnable endRunnable) { - if (mAnimatingOut) { - if (endRunnable != null) { - endRunnable.run(); - } - return; - } - setCircleSize(0, true, null, 0, null); - setOffset(0, true, 0, null, endRunnable); - mCircleHidden = true; - } - - public void startEnterAnimation() { - if (mAnimatingOut) { - return; - } - applyCircleSize(0); - setOffset(0); - setCircleSize(mCircleMinSize, true, null, 50, null); - setOffset(mStaticOffset, true, 50, null, null); - mCircleHidden = false; - } - - - public void startExitAnimation(final Runnable endRunnable) { - if (!mHorizontal) { - float offset = getHeight() / 2.0f; - setOffset(offset - mBaseMargin, true, 50, mFastOutSlowInInterpolator, null); - float xMax = getWidth() / 2; - float yMax = getHeight() / 2; - float maxRadius = (float) Math.ceil(Math.hypot(xMax, yMax) * 2); - setCircleSize(maxRadius, true, null, 50, mFastOutSlowInInterpolator); - performExitFadeOutAnimation(50, 300, endRunnable); - } else { - - // when in landscape, we don't wan't the animation as it interferes with the general - // rotation animation to the homescreen. - endRunnable.run(); - } - } - - private void performExitFadeOutAnimation(int startDelay, int duration, - final Runnable endRunnable) { - mFadeOutAnimator = ValueAnimator.ofFloat(mBackgroundPaint.getAlpha() / 255.0f, 0.0f); - - // Linear since we are animating multiple values - mFadeOutAnimator.setInterpolator(new LinearInterpolator()); - mFadeOutAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float animatedFraction = animation.getAnimatedFraction(); - float logoValue = animatedFraction > 0.5f ? 1.0f : animatedFraction / 0.5f; - logoValue = PhoneStatusBar.ALPHA_OUT.getInterpolation(1.0f - logoValue); - float backgroundValue = animatedFraction < 0.2f ? 0.0f : - PhoneStatusBar.ALPHA_OUT.getInterpolation((animatedFraction - 0.2f) / 0.8f); - backgroundValue = 1.0f - backgroundValue; - mBackgroundPaint.setAlpha((int) (backgroundValue * 255)); - mOutlineAlpha = backgroundValue; - mLogo.setAlpha(logoValue); - invalidateOutline(); - invalidate(); - } - }); - mFadeOutAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (endRunnable != null) { - endRunnable.run(); - } - mLogo.setAlpha(1.0f); - mBackgroundPaint.setAlpha(255); - mOutlineAlpha = 1.0f; - mFadeOutAnimator = null; - } - }); - mFadeOutAnimator.setStartDelay(startDelay); - mFadeOutAnimator.setDuration(duration); - mFadeOutAnimator.start(); - } - - public void setDraggedFarEnough(boolean farEnough) { - if (farEnough != mDraggedFarEnough) { - if (farEnough) { - if (mCircleHidden) { - startEnterAnimation(); - } - if (mOffsetAnimator == null) { - addRipple(); - } else { - postDelayed(new Runnable() { - @Override - public void run() { - addRipple(); - } - }, 100); - } - } else { - startAbortAnimation(null); - } - mDraggedFarEnough = farEnough; - } - - } - - private void addRipple() { - if (mRipples.size() > 1) { - // we only want 2 ripples at the time - return; - } - float xInterpolation, yInterpolation; - if (mHorizontal) { - xInterpolation = 0.75f; - yInterpolation = 0.5f; - } else { - xInterpolation = 0.5f; - yInterpolation = 0.75f; - } - float circleCenterX = mStaticRect.left * (1.0f - xInterpolation) - + mStaticRect.right * xInterpolation; - float circleCenterY = mStaticRect.top * (1.0f - yInterpolation) - + mStaticRect.bottom * yInterpolation; - float radius = Math.max(mCircleSize, mCircleMinSize * 1.25f) * 0.75f; - Ripple ripple = new Ripple(circleCenterX, circleCenterY, radius); - ripple.start(); - } - - public void reset() { - mDraggedFarEnough = false; - mAnimatingOut = false; - mCircleHidden = true; - mClipToOutline = false; - if (mFadeOutAnimator != null) { - mFadeOutAnimator.cancel(); - } - mBackgroundPaint.setAlpha(255); - mOutlineAlpha = 1.0f; - } - - /** - * Check if an animation is currently running - * - * @param enterAnimation Is the animating queried the enter animation. - */ - public boolean isAnimationRunning(boolean enterAnimation) { - return mOffsetAnimator != null && (enterAnimation == mOffsetAnimatingIn); - } - - public void performOnAnimationFinished(final Runnable runnable) { - if (mOffsetAnimator != null) { - mOffsetAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (runnable != null) { - runnable.run(); - } - } - }); - } else { - if (runnable != null) { - runnable.run(); - } - } - } - - public void setAnimatingOut(boolean animatingOut) { - mAnimatingOut = animatingOut; - } - - /** - * @return Whether the circle is currently launching to the search activity or aborting the - * interaction - */ - public boolean isAnimatingOut() { - return mAnimatingOut; - } - - @Override - public boolean hasOverlappingRendering() { - // not really true but it's ok during an animation, as it's never permanent - return false; - } - - private class Ripple { - float x; - float y; - float radius; - float endRadius; - float alpha; - - Ripple(float x, float y, float endRadius) { - this.x = x; - this.y = y; - this.endRadius = endRadius; - } - - void start() { - ValueAnimator animator = ValueAnimator.ofFloat(0.0f, 1.0f); - - // Linear since we are animating multiple values - animator.setInterpolator(new LinearInterpolator()); - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - alpha = 1.0f - animation.getAnimatedFraction(); - alpha = mDisappearInterpolator.getInterpolation(alpha); - radius = mAppearInterpolator.getInterpolation(animation.getAnimatedFraction()); - radius *= endRadius; - invalidate(); - } - }); - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mRipples.remove(Ripple.this); - updateClipping(); - } - - public void onAnimationStart(Animator animation) { - mRipples.add(Ripple.this); - updateClipping(); - } - }); - animator.setDuration(400); - animator.start(); - } - - public void draw(Canvas canvas) { - mRipplePaint.setAlpha((int) (alpha * 255)); - canvas.drawCircle(x, y, radius, mRipplePaint); - } - } - -} diff --git a/packages/SystemUI/src/com/android/systemui/SearchPanelView.java b/packages/SystemUI/src/com/android/systemui/SearchPanelView.java deleted file mode 100644 index 445b499..0000000 --- a/packages/SystemUI/src/com/android/systemui/SearchPanelView.java +++ /dev/null @@ -1,345 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui; - -import android.app.ActivityOptions; -import android.app.SearchManager; -import android.content.ActivityNotFoundException; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.media.AudioAttributes; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.UserHandle; -import android.os.Vibrator; -import android.provider.Settings; -import android.util.AttributeSet; -import android.util.Log; -import android.view.MotionEvent; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.ImageView; - -import com.android.systemui.statusbar.BaseStatusBar; -import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.StatusBarPanel; -import com.android.systemui.statusbar.phone.PhoneStatusBar; - -public class SearchPanelView extends FrameLayout implements StatusBarPanel { - - private static final String TAG = "SearchPanelView"; - private static final String ASSIST_ICON_METADATA_NAME = - "com.android.systemui.action_assist_icon"; - - private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder() - .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) - .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) - .build(); - - private final Context mContext; - private BaseStatusBar mBar; - - private SearchPanelCircleView mCircle; - private ImageView mLogo; - private View mScrim; - - private int mThreshold; - private boolean mHorizontal; - - private boolean mLaunching; - private boolean mDragging; - private boolean mDraggedFarEnough; - private float mStartTouch; - private float mStartDrag; - private boolean mLaunchPending; - - public SearchPanelView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public SearchPanelView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - mContext = context; - mThreshold = context.getResources().getDimensionPixelSize(R.dimen.search_panel_threshold); - } - - private void startAssistActivity() { - if (!mBar.isDeviceProvisioned()) return; - - // Close Recent Apps if needed - mBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL); - - final Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE)) - .getAssistIntent(mContext, true, UserHandle.USER_CURRENT); - if (intent == null) return; - - try { - final ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, - R.anim.search_launch_enter, R.anim.search_launch_exit); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - AsyncTask.execute(new Runnable() { - @Override - public void run() { - mContext.startActivityAsUser(intent, opts.toBundle(), - new UserHandle(UserHandle.USER_CURRENT)); - } - }); - } catch (ActivityNotFoundException e) { - Log.w(TAG, "Activity not found for " + intent.getAction()); - } - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - mCircle = (SearchPanelCircleView) findViewById(R.id.search_panel_circle); - mLogo = (ImageView) findViewById(R.id.search_logo); - mScrim = findViewById(R.id.search_panel_scrim); - } - - private void maybeSwapSearchIcon() { - Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE)) - .getAssistIntent(mContext, false, UserHandle.USER_CURRENT); - if (intent != null) { - ComponentName component = intent.getComponent(); - replaceDrawable(mLogo, component, ASSIST_ICON_METADATA_NAME); - } else { - mLogo.setImageDrawable(null); - } - } - - public void replaceDrawable(ImageView v, ComponentName component, String name) { - if (component != null) { - try { - PackageManager packageManager = mContext.getPackageManager(); - // Look for the search icon specified in the activity meta-data - Bundle metaData = packageManager.getActivityInfo( - component, PackageManager.GET_META_DATA).metaData; - if (metaData != null) { - int iconResId = metaData.getInt(name); - if (iconResId != 0) { - Resources res = packageManager.getResourcesForActivity(component); - v.setImageDrawable(res.getDrawable(iconResId)); - return; - } - } - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "Failed to swap drawable; " - + component.flattenToShortString() + " not found", e); - } catch (Resources.NotFoundException nfe) { - Log.w(TAG, "Failed to swap drawable from " - + component.flattenToShortString(), nfe); - } - } - v.setImageDrawable(null); - } - - @Override - public boolean isInContentArea(int x, int y) { - return true; - } - - private void vibrate() { - Context context = getContext(); - if (Settings.System.getIntForUser(context.getContentResolver(), - Settings.System.HAPTIC_FEEDBACK_ENABLED, 1, UserHandle.USER_CURRENT) != 0) { - Resources res = context.getResources(); - Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); - vibrator.vibrate(res.getInteger(R.integer.config_search_panel_view_vibration_duration), - VIBRATION_ATTRIBUTES); - } - } - - public void show(final boolean show, boolean animate) { - if (show) { - maybeSwapSearchIcon(); - if (getVisibility() != View.VISIBLE) { - setVisibility(View.VISIBLE); - vibrate(); - if (animate) { - startEnterAnimation(); - } else { - mScrim.setAlpha(1f); - } - } - setFocusable(true); - setFocusableInTouchMode(true); - requestFocus(); - } else { - if (animate) { - startAbortAnimation(); - } else { - setVisibility(View.INVISIBLE); - } - } - } - - private void startEnterAnimation() { - mCircle.startEnterAnimation(); - mScrim.setAlpha(0f); - mScrim.animate() - .alpha(1f) - .setDuration(300) - .setStartDelay(50) - .setInterpolator(PhoneStatusBar.ALPHA_IN) - .start(); - - } - - private void startAbortAnimation() { - mCircle.startAbortAnimation(new Runnable() { - @Override - public void run() { - mCircle.setAnimatingOut(false); - setVisibility(View.INVISIBLE); - } - }); - mCircle.setAnimatingOut(true); - mScrim.animate() - .alpha(0f) - .setDuration(300) - .setStartDelay(0) - .setInterpolator(PhoneStatusBar.ALPHA_OUT); - } - - public void hide(boolean animate) { - if (mBar != null) { - // This will indirectly cause show(false, ...) to get called - mBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); - } else { - if (animate) { - startAbortAnimation(); - } else { - setVisibility(View.INVISIBLE); - } - } - } - - @Override - public boolean dispatchHoverEvent(MotionEvent event) { - // Ignore hover events outside of this panel bounds since such events - // generate spurious accessibility events with the panel content when - // tapping outside of it, thus confusing the user. - final int x = (int) event.getX(); - final int y = (int) event.getY(); - if (x >= 0 && x < getWidth() && y >= 0 && y < getHeight()) { - return super.dispatchHoverEvent(event); - } - return true; - } - - /** - * Whether the panel is showing, or, if it's animating, whether it will be - * when the animation is done. - */ - public boolean isShowing() { - return getVisibility() == View.VISIBLE && !mCircle.isAnimatingOut(); - } - - public void setBar(BaseStatusBar bar) { - mBar = bar; - } - - public boolean isAssistantAvailable() { - return ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE)) - .getAssistIntent(mContext, false, UserHandle.USER_CURRENT) != null; - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - if (mLaunching || mLaunchPending) { - return false; - } - int action = event.getActionMasked(); - switch (action) { - case MotionEvent.ACTION_DOWN: - mStartTouch = mHorizontal ? event.getX() : event.getY(); - mDragging = false; - mDraggedFarEnough = false; - mCircle.reset(); - break; - case MotionEvent.ACTION_MOVE: - float currentTouch = mHorizontal ? event.getX() : event.getY(); - if (getVisibility() == View.VISIBLE && !mDragging && - (!mCircle.isAnimationRunning(true /* enterAnimation */) - || Math.abs(mStartTouch - currentTouch) > mThreshold)) { - mStartDrag = currentTouch; - mDragging = true; - } - if (mDragging) { - float offset = Math.max(mStartDrag - currentTouch, 0.0f); - mCircle.setDragDistance(offset); - mDraggedFarEnough = Math.abs(mStartTouch - currentTouch) > mThreshold; - mCircle.setDraggedFarEnough(mDraggedFarEnough); - } - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - if (mDraggedFarEnough) { - if (mCircle.isAnimationRunning(true /* enterAnimation */)) { - mLaunchPending = true; - mCircle.setAnimatingOut(true); - mCircle.performOnAnimationFinished(new Runnable() { - @Override - public void run() { - startExitAnimation(); - } - }); - } else { - startExitAnimation(); - } - } else { - startAbortAnimation(); - } - break; - } - return true; - } - - private void startExitAnimation() { - mLaunchPending = false; - if (mLaunching || getVisibility() != View.VISIBLE) { - return; - } - mLaunching = true; - startAssistActivity(); - vibrate(); - mCircle.setAnimatingOut(true); - mCircle.startExitAnimation(new Runnable() { - @Override - public void run() { - mLaunching = false; - mCircle.setAnimatingOut(false); - setVisibility(View.INVISIBLE); - } - }); - mScrim.animate() - .alpha(0f) - .setDuration(300) - .setStartDelay(0) - .setInterpolator(PhoneStatusBar.ALPHA_OUT); - } - - public void setHorizontal(boolean horizontal) { - mHorizontal = horizontal; - mCircle.setHorizontal(horizontal); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index b3f90d7..e302c98 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -41,7 +41,7 @@ public class SystemUIApplication extends Application { */ private final Class<?>[] SERVICES = new Class[] { com.android.systemui.keyguard.KeyguardViewMediator.class, - com.android.systemui.recent.Recents.class, + com.android.systemui.recents.Recents.class, com.android.systemui.volume.VolumeUI.class, com.android.systemui.statusbar.SystemBars.class, com.android.systemui.usb.StorageNotification.class, diff --git a/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java b/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java index 2ff8f8a..eddf2b1 100644 --- a/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java +++ b/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java @@ -26,8 +26,6 @@ import android.view.View; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; -import com.android.systemui.statusbar.phone.NotificationPanelView; - /** * Helper to invert the colors of views and fade between the states. */ diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistGestureManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistGestureManager.java new file mode 100644 index 0000000..36be355 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistGestureManager.java @@ -0,0 +1,292 @@ +package com.android.systemui.assist; + +import android.app.ActivityManager; +import android.app.ActivityOptions; +import android.app.SearchManager; +import android.content.ActivityNotFoundException; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.graphics.PixelFormat; +import android.media.AudioAttributes; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.UserHandle; +import android.os.Vibrator; +import android.provider.Settings; +import android.util.Log; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.ImageView; + +import com.android.internal.app.IVoiceInteractionManagerService; +import com.android.internal.app.IVoiceInteractionSessionShowCallback; +import com.android.systemui.R; +import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.phone.PhoneStatusBar; + +/** + * Class to manage everything around the assist gesture. + */ +public class AssistGestureManager { + + private static final String TAG = "AssistGestureManager"; + private static final String ASSIST_ICON_METADATA_NAME = + "com.android.systemui.action_assist_icon"; + + private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) + .build(); + + private static final long TIMEOUT_SERVICE = 2500; + private static final long TIMEOUT_ACTIVITY = 1000; + + private final Context mContext; + private final WindowManager mWindowManager; + private AssistOrbContainer mView; + private final PhoneStatusBar mBar; + private final IVoiceInteractionManagerService mVoiceInteractionManagerService; + + private IVoiceInteractionSessionShowCallback mShowCallback = + new IVoiceInteractionSessionShowCallback.Stub() { + + @Override + public void onFailed() throws RemoteException { + mView.post(mHideRunnable); + } + + @Override + public void onShown() throws RemoteException { + mView.post(mHideRunnable); + } + }; + + private Runnable mHideRunnable = new Runnable() { + @Override + public void run() { + mView.removeCallbacks(this); + mView.show(false /* show */, true /* animate */); + } + }; + + public AssistGestureManager(PhoneStatusBar bar, Context context) { + mContext = context; + mBar = bar; + mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); + mVoiceInteractionManagerService = IVoiceInteractionManagerService.Stub.asInterface( + ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE)); + } + + public void onConfigurationChanged() { + boolean visible = false; + if (mView != null) { + visible = mView.isShowing(); + mWindowManager.removeView(mView); + } + + mView = (AssistOrbContainer) LayoutInflater.from(mContext).inflate( + R.layout.assist_orb, null); + mView.setVisibility(View.GONE); + mView.setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); + WindowManager.LayoutParams lp = getLayoutParams(); + mWindowManager.addView(mView, lp); + mBar.getNavigationBarView().setDelegateView(mView); + if (visible) { + mView.show(true /* show */, false /* animate */); + } + } + + public void onGestureInvoked(boolean vibrate) { + boolean isVoiceInteractorActive = getVoiceInteractorSupportsAssistGesture(); + if (!isVoiceInteractorActive && !isAssistantIntentAvailable()) { + return; + } + if (vibrate) { + vibrate(); + } + if (!isVoiceInteractorActive || !isVoiceSessionRunning()) { + showOrb(); + mView.postDelayed(mHideRunnable, isVoiceInteractorActive + ? TIMEOUT_SERVICE + : TIMEOUT_ACTIVITY); + } + startAssist(); + } + + private WindowManager.LayoutParams getLayoutParams() { + WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + mContext.getResources().getDimensionPixelSize(R.dimen.assist_orb_scrim_height), + WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING, + WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + PixelFormat.TRANSLUCENT); + if (ActivityManager.isHighEndGfx()) { + lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; + } + lp.gravity = Gravity.BOTTOM | Gravity.START; + lp.setTitle("AssistPreviewPanel"); + lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED + | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; + return lp; + } + + private void showOrb() { + maybeSwapSearchIcon(); + mView.show(true /* show */, true /* animate */); + } + + private void startAssist() { + if (getVoiceInteractorSupportsAssistGesture()) { + startVoiceInteractor(); + } else { + startAssistActivity(); + } + } + + private void startAssistActivity() { + if (!mBar.isDeviceProvisioned()) { + return; + } + + // Close Recent Apps if needed + mBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL | + CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL); + + final Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE)) + .getAssistIntent(mContext, true, UserHandle.USER_CURRENT); + if (intent == null) { + return; + } + + try { + final ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, + R.anim.search_launch_enter, R.anim.search_launch_exit); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + AsyncTask.execute(new Runnable() { + @Override + public void run() { + mContext.startActivityAsUser(intent, opts.toBundle(), + new UserHandle(UserHandle.USER_CURRENT)); + } + }); + } catch (ActivityNotFoundException e) { + Log.w(TAG, "Activity not found for " + intent.getAction()); + } + } + + private void startVoiceInteractor() { + try { + mVoiceInteractionManagerService.showSessionForActiveService(mShowCallback); + } catch (RemoteException e) { + Log.w(TAG, "Failed to call showSessionForActiveService", e); + } + } + + private boolean getVoiceInteractorSupportsAssistGesture() { + try { + return mVoiceInteractionManagerService.activeServiceSupportsAssistGesture(); + } catch (RemoteException e) { + Log.w(TAG, "Failed to call activeServiceSupportsAssistGesture", e); + return false; + } + } + + private ComponentName getVoiceInteractorComponentName() { + try { + return mVoiceInteractionManagerService.getActiveServiceComponentName(); + } catch (RemoteException e) { + Log.w(TAG, "Failed to call getActiveServiceComponentName", e); + return null; + } + } + + private boolean isVoiceSessionRunning() { + try { + return mVoiceInteractionManagerService.isSessionRunning(); + } catch (RemoteException e) { + Log.w(TAG, "Failed to call isSessionRunning", e); + return false; + } + } + + public void destroy() { + mWindowManager.removeViewImmediate(mView); + } + + private void maybeSwapSearchIcon() { + Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE)) + .getAssistIntent(mContext, false, UserHandle.USER_CURRENT); + ComponentName component = null; + boolean isService = false; + if (getVoiceInteractorSupportsAssistGesture()) { + component = getVoiceInteractorComponentName(); + isService = true; + } else if (intent != null) { + component = intent.getComponent(); + } + if (component != null) { + replaceDrawable(mView.getOrb().getLogo(), component, ASSIST_ICON_METADATA_NAME, + isService); + } else { + mView.getOrb().getLogo().setImageDrawable(null); + } + } + + public void replaceDrawable(ImageView v, ComponentName component, String name, + boolean isService) { + if (component != null) { + try { + PackageManager packageManager = mContext.getPackageManager(); + // Look for the search icon specified in the activity meta-data + Bundle metaData = isService + ? packageManager.getServiceInfo( + component, PackageManager.GET_META_DATA).metaData + : packageManager.getActivityInfo( + component, PackageManager.GET_META_DATA).metaData; + if (metaData != null) { + int iconResId = metaData.getInt(name); + if (iconResId != 0) { + Resources res = packageManager.getResourcesForApplication( + component.getPackageName()); + v.setImageDrawable(res.getDrawable(iconResId)); + return; + } + } + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Failed to swap drawable; " + + component.flattenToShortString() + " not found", e); + } catch (Resources.NotFoundException nfe) { + Log.w(TAG, "Failed to swap drawable from " + + component.flattenToShortString(), nfe); + } + } + v.setImageDrawable(null); + } + + private void vibrate() { + if (Settings.System.getIntForUser(mContext.getContentResolver(), + Settings.System.HAPTIC_FEEDBACK_ENABLED, 1, UserHandle.USER_CURRENT) != 0) { + Resources res = mContext.getResources(); + Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE); + vibrator.vibrate(res.getInteger(R.integer.config_search_panel_view_vibration_duration), + VIBRATION_ATTRIBUTES); + } + } + + public boolean isAssistantIntentAvailable() { + return ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE)) + .getAssistIntent(mContext, false, UserHandle.USER_CURRENT) != null; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistOrbContainer.java b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbContainer.java new file mode 100644 index 0000000..67017db --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbContainer.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.assist; + +import android.annotation.Nullable; +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; +import android.widget.FrameLayout; + +import com.android.systemui.R; + +public class AssistOrbContainer extends FrameLayout { + + private static final long EXIT_START_DELAY = 150; + + private final Interpolator mLinearOutSlowInInterpolator; + private final Interpolator mFastOutLinearInInterpolator; + + private View mScrim; + private View mNavbarScrim; + private AssistOrbView mOrb; + + private boolean mAnimatingOut; + + public AssistOrbContainer(Context context) { + this(context, null); + } + + public AssistOrbContainer(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public AssistOrbContainer(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, + android.R.interpolator.linear_out_slow_in); + mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context, + android.R.interpolator.fast_out_slow_in); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mScrim = findViewById(R.id.assist_orb_scrim); + mNavbarScrim = findViewById(R.id.assist_orb_navbar_scrim); + mOrb = (AssistOrbView) findViewById(R.id.assist_orb); + } + + public void show(final boolean show, boolean animate) { + if (show) { + if (getVisibility() != View.VISIBLE) { + setVisibility(View.VISIBLE); + if (animate) { + startEnterAnimation(); + } else { + reset(); + } + } + } else { + if (animate) { + startExitAnimation(new Runnable() { + @Override + public void run() { + mAnimatingOut = false; + setVisibility(View.GONE); + } + }); + } else { + setVisibility(View.GONE); + } + } + } + + private void reset() { + mAnimatingOut = false; + mOrb.reset(); + mScrim.setAlpha(1f); + mNavbarScrim.setAlpha(1f); + } + + private void startEnterAnimation() { + if (mAnimatingOut) { + return; + } + mOrb.startEnterAnimation(); + mScrim.setAlpha(0f); + mNavbarScrim.setAlpha(0f); + post(new Runnable() { + @Override + public void run() { + mScrim.animate() + .alpha(1f) + .setDuration(300) + .setStartDelay(0) + .setInterpolator(mLinearOutSlowInInterpolator); + mNavbarScrim.animate() + .alpha(1f) + .setDuration(300) + .setStartDelay(0) + .setInterpolator(mLinearOutSlowInInterpolator); + } + }); + } + + private void startExitAnimation(final Runnable endRunnable) { + if (mAnimatingOut) { + if (endRunnable != null) { + endRunnable.run(); + } + return; + } + mAnimatingOut = true; + mOrb.startExitAnimation(EXIT_START_DELAY); + mScrim.animate() + .alpha(0f) + .setDuration(250) + .setStartDelay(EXIT_START_DELAY) + .setInterpolator(mFastOutLinearInInterpolator); + mNavbarScrim.animate() + .alpha(0f) + .setDuration(250) + .setStartDelay(EXIT_START_DELAY) + .setInterpolator(mFastOutLinearInInterpolator) + .withEndAction(endRunnable); + } + + /** + * Whether the panel is showing, or, if it's animating, whether it will be + * when the animation is done. + */ + public boolean isShowing() { + return getVisibility() == View.VISIBLE && !mAnimatingOut; + } + + public AssistOrbView getOrb() { + return mOrb; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistOrbView.java b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbView.java new file mode 100644 index 0000000..a3372a8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbView.java @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.assist; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Outline; +import android.graphics.Paint; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewOutlineProvider; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; +import android.view.animation.OvershootInterpolator; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import com.android.systemui.R; + +public class AssistOrbView extends FrameLayout { + + private final int mCircleMinSize; + private final int mBaseMargin; + private final int mStaticOffset; + private final Paint mBackgroundPaint = new Paint(); + private final Rect mCircleRect = new Rect(); + private final Rect mStaticRect = new Rect(); + private final Interpolator mAppearInterpolator; + private final Interpolator mDisappearInterpolator; + private final Interpolator mOvershootInterpolator = new OvershootInterpolator(); + + private boolean mClipToOutline; + private final int mMaxElevation; + private float mOutlineAlpha; + private float mOffset; + private float mCircleSize; + private ImageView mLogo; + private float mCircleAnimationEndValue; + + private ValueAnimator mOffsetAnimator; + private ValueAnimator mCircleAnimator; + + private ValueAnimator.AnimatorUpdateListener mCircleUpdateListener + = new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + applyCircleSize((float) animation.getAnimatedValue()); + updateElevation(); + } + }; + private AnimatorListenerAdapter mClearAnimatorListener = new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mCircleAnimator = null; + } + }; + private ValueAnimator.AnimatorUpdateListener mOffsetUpdateListener + = new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + mOffset = (float) animation.getAnimatedValue(); + updateLayout(); + } + }; + + + public AssistOrbView(Context context) { + this(context, null); + } + + public AssistOrbView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public AssistOrbView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public AssistOrbView(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + setOutlineProvider(new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + if (mCircleSize > 0.0f) { + outline.setOval(mCircleRect); + } else { + outline.setEmpty(); + } + outline.setAlpha(mOutlineAlpha); + } + }); + setWillNotDraw(false); + mCircleMinSize = context.getResources().getDimensionPixelSize( + R.dimen.assist_orb_size); + mBaseMargin = context.getResources().getDimensionPixelSize( + R.dimen.assist_orb_base_margin); + mStaticOffset = context.getResources().getDimensionPixelSize( + R.dimen.assist_orb_travel_distance); + mMaxElevation = context.getResources().getDimensionPixelSize( + R.dimen.assist_orb_elevation); + mAppearInterpolator = AnimationUtils.loadInterpolator(mContext, + android.R.interpolator.linear_out_slow_in); + mDisappearInterpolator = AnimationUtils.loadInterpolator(mContext, + android.R.interpolator.fast_out_linear_in); + mBackgroundPaint.setAntiAlias(true); + mBackgroundPaint.setColor(getResources().getColor(R.color.assist_orb_color)); + } + + public ImageView getLogo() { + return mLogo; + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + drawBackground(canvas); + } + + private void drawBackground(Canvas canvas) { + canvas.drawCircle(mCircleRect.centerX(), mCircleRect.centerY(), mCircleSize / 2, + mBackgroundPaint); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mLogo = (ImageView) findViewById(R.id.search_logo); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + mLogo.layout(0, 0, mLogo.getMeasuredWidth(), mLogo.getMeasuredHeight()); + if (changed) { + updateCircleRect(mStaticRect, mStaticOffset, true); + } + } + + public void animateCircleSize(float circleSize, long duration, + long startDelay, Interpolator interpolator) { + if (circleSize == mCircleAnimationEndValue) { + return; + } + if (mCircleAnimator != null) { + mCircleAnimator.cancel(); + } + mCircleAnimator = ValueAnimator.ofFloat(mCircleSize, circleSize); + mCircleAnimator.addUpdateListener(mCircleUpdateListener); + mCircleAnimator.addListener(mClearAnimatorListener); + mCircleAnimator.setInterpolator(interpolator); + mCircleAnimator.setDuration(duration); + mCircleAnimator.setStartDelay(startDelay); + mCircleAnimator.start(); + mCircleAnimationEndValue = circleSize; + } + + private void applyCircleSize(float circleSize) { + mCircleSize = circleSize; + updateLayout(); + } + + private void updateElevation() { + float t = (mStaticOffset - mOffset) / (float) mStaticOffset; + t = 1.0f - Math.max(t, 0.0f); + float offset = t * mMaxElevation; + setElevation(offset); + } + + /** + * Animates the offset to the edge of the screen. + * + * @param offset The offset to apply. + * @param startDelay The desired start delay if animated. + * + * @param interpolator The desired interpolator if animated. If null, + * a default interpolator will be taken designed for appearing or + * disappearing. + */ + private void animateOffset(float offset, long duration, long startDelay, + Interpolator interpolator) { + if (mOffsetAnimator != null) { + mOffsetAnimator.removeAllListeners(); + mOffsetAnimator.cancel(); + } + mOffsetAnimator = ValueAnimator.ofFloat(mOffset, offset); + mOffsetAnimator.addUpdateListener(mOffsetUpdateListener); + mOffsetAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mOffsetAnimator = null; + } + }); + mOffsetAnimator.setInterpolator(interpolator); + mOffsetAnimator.setStartDelay(startDelay); + mOffsetAnimator.setDuration(duration); + mOffsetAnimator.start(); + } + + private void updateLayout() { + updateCircleRect(); + updateLogo(); + invalidateOutline(); + invalidate(); + updateClipping(); + } + + private void updateClipping() { + boolean clip = mCircleSize < mCircleMinSize; + if (clip != mClipToOutline) { + setClipToOutline(clip); + mClipToOutline = clip; + } + } + + private void updateLogo() { + float translationX = (mCircleRect.left + mCircleRect.right) / 2.0f - mLogo.getWidth() / 2.0f; + float translationY = (mCircleRect.top + mCircleRect.bottom) / 2.0f + - mLogo.getHeight() / 2.0f - mCircleMinSize / 7f; + float t = (mStaticOffset - mOffset) / (float) mStaticOffset; + translationY += t * mStaticOffset * 0.1f; + float alpha = 1.0f-t; + alpha = Math.max((alpha - 0.5f) * 2.0f, 0); + mLogo.setImageAlpha((int) (alpha * 255)); + mLogo.setTranslationX(translationX); + mLogo.setTranslationY(translationY); + } + + private void updateCircleRect() { + updateCircleRect(mCircleRect, mOffset, false); + } + + private void updateCircleRect(Rect rect, float offset, boolean useStaticSize) { + int left, top; + float circleSize = useStaticSize ? mCircleMinSize : mCircleSize; + left = (int) (getWidth() - circleSize) / 2; + top = (int) (getHeight() - circleSize / 2 - mBaseMargin - offset); + rect.set(left, top, (int) (left + circleSize), (int) (top + circleSize)); + } + + public void startExitAnimation(long delay) { + animateCircleSize(0, 200, delay, mDisappearInterpolator); + animateOffset(0, 200, delay, mDisappearInterpolator); + } + + public void startEnterAnimation() { + applyCircleSize(0); + post(new Runnable() { + @Override + public void run() { + animateCircleSize(mCircleMinSize, 300, 0 /* delay */, mOvershootInterpolator); + animateOffset(mStaticOffset, 400, 0 /* delay */, mAppearInterpolator); + } + }); + } + + public void reset() { + mClipToOutline = false; + mBackgroundPaint.setAlpha(255); + mOutlineAlpha = 1.0f; + } + + @Override + public boolean hasOverlappingRendering() { + // not really true but it's ok during an animation, as it's never permanent + return false; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/egg/LLandActivity.java b/packages/SystemUI/src/com/android/systemui/egg/LLandActivity.java index b9f8106..50221d3 100644 --- a/packages/SystemUI/src/com/android/systemui/egg/LLandActivity.java +++ b/packages/SystemUI/src/com/android/systemui/egg/LLandActivity.java @@ -18,7 +18,6 @@ package com.android.systemui.egg; import android.app.Activity; import android.os.Bundle; -import android.util.Log; import android.widget.TextView; import com.android.systemui.R; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index e66934e..dd28734 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -63,7 +63,6 @@ import com.android.keyguard.KeyguardConstants; import com.android.keyguard.KeyguardDisplayManager; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; -import com.android.keyguard.MultiUserAvatarCache; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.SystemUI; import com.android.systemui.statusbar.phone.PhoneStatusBar; @@ -173,7 +172,7 @@ public class KeyguardViewMediator extends SystemUI { private static final String KEYGUARD_ANALYTICS_SETTING = "keyguard_analytics"; /** The stream type that the lock sounds are tied to. */ - private int mMasterStreamType; + private int mUiSoundsStreamType; private AlarmManager mAlarmManager; private AudioManager mAudioManager; @@ -314,9 +313,6 @@ public class KeyguardViewMediator extends SystemUI { resetKeyguardDonePendingLocked(); resetStateLocked(); adjustStatusBarLocked(); - // When we switch users we want to bring the new user to the biometric unlock even - // if the current user has gone to the backup. - KeyguardUpdateMonitor.getInstance(mContext).setAlternateUnlockEnabled(true); } } @@ -333,14 +329,7 @@ public class KeyguardViewMediator extends SystemUI { } @Override - public void onUserRemoved(int userId) { - mLockPatternUtils.removeUser(userId); - MultiUserAvatarCache.getInstance().clear(userId); - } - - @Override public void onUserInfoChanged(int userId) { - MultiUserAvatarCache.getInstance().clear(userId); } @Override @@ -447,9 +436,12 @@ public class KeyguardViewMediator extends SystemUI { } } - public void onFingerprintRecognized(int userId) { + @Override + public void onFingerprintAuthenticated(int userId) { if (mStatusBarKeyguardViewManager.isBouncerShowing()) { mViewMediatorCallback.keyguardDone(true); + } else { + mStatusBarKeyguardViewManager.animateCollapsePanels(); } }; @@ -477,11 +469,6 @@ public class KeyguardViewMediator extends SystemUI { } @Override - public void onUserActivityTimeoutChanged() { - mStatusBarKeyguardViewManager.updateUserActivityTimeout(); - } - - @Override public void keyguardDonePending() { mKeyguardDonePending = true; mHideAnimationRun = true; @@ -505,6 +492,11 @@ public class KeyguardViewMediator extends SystemUI { } @Override + public void resetKeyguard() { + resetStateLocked(); + } + + @Override public void playTrustedSound() { KeyguardViewMediator.this.playTrustedSound(); } @@ -597,23 +589,6 @@ public class KeyguardViewMediator extends SystemUI { mSystemReady = true; mUpdateMonitor.registerCallback(mUpdateCallback); - // Suppress biometric unlock right after boot until things have settled if it is the - // selected security method, otherwise unsuppress it. It must be unsuppressed if it is - // not the selected security method for the following reason: if the user starts - // without a screen lock selected, the biometric unlock would be suppressed the first - // time they try to use it. - // - // Note that the biometric unlock will still not show if it is not the selected method. - // Calling setAlternateUnlockEnabled(true) simply says don't suppress it if it is the - // selected method. - if (mLockPatternUtils.usingBiometricWeak() - && mLockPatternUtils.isBiometricWeakInstalled()) { - if (DEBUG) Log.d(TAG, "suppressing biometric unlock during boot"); - mUpdateMonitor.setAlternateUnlockEnabled(false); - } else { - mUpdateMonitor.setAlternateUnlockEnabled(true); - } - doKeyguardLocked(null); } // Most services aren't available until the system reaches the ready state, so we @@ -662,7 +637,7 @@ public class KeyguardViewMediator extends SystemUI { doKeyguardLocked(null); } } - KeyguardUpdateMonitor.getInstance(mContext).dispatchScreenTurndOff(why); + KeyguardUpdateMonitor.getInstance(mContext).dispatchScreenTurnedOff(why); } private void doKeyguardLaterLocked() { @@ -1270,10 +1245,10 @@ public class KeyguardViewMediator extends SystemUI { if (mAudioManager == null) { mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); if (mAudioManager == null) return; - mMasterStreamType = mAudioManager.getMasterStreamType(); + mUiSoundsStreamType = mAudioManager.getUiSoundsStreamType(); } // If the stream is muted, don't play the sound - if (mAudioManager.isStreamMute(mMasterStreamType)) return; + if (mAudioManager.isStreamMute(mUiSoundsStreamType)) return; mLockSoundStreamId = mLockSounds.play(soundId, mLockSoundVolume, mLockSoundVolume, 1/*priortiy*/, 0/*loop*/, 1.0f/*rate*/); @@ -1328,6 +1303,8 @@ public class KeyguardViewMediator extends SystemUI { @Override public void run() { try { + mStatusBarKeyguardViewManager.keyguardGoingAway(); + // Don't actually hide the Keyguard at the moment, wait for window // manager until it tells us it's safe to do so with // startKeyguardExitAnimation. diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java index c23f45d..88d0997 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java @@ -18,8 +18,6 @@ package com.android.systemui.media; import android.app.Activity; import android.app.AlertDialog; -import android.app.PendingIntent; -import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.ApplicationInfo; @@ -32,16 +30,10 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; import android.util.Log; -import android.view.LayoutInflater; import android.view.WindowManager; import android.widget.CheckBox; import android.widget.CompoundButton; -import android.widget.TextView; - -import com.android.internal.app.AlertActivity; -import com.android.internal.app.AlertController; import com.android.systemui.R; -import com.android.systemui.statusbar.phone.SystemUIDialog; public class MediaProjectionPermissionActivity extends Activity implements DialogInterface.OnClickListener, CheckBox.OnCheckedChangeListener, diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java index 4391bfc..af1f795 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java @@ -50,7 +50,6 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { private static final boolean DEBUG = PowerUI.DEBUG; private static final String TAG_NOTIFICATION = "low_battery"; - private static final int ID_NOTIFICATION = 100; private static final int SHOWING_NOTHING = 0; private static final int SHOWING_WARNING = 1; @@ -145,7 +144,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { showSaverNotification(); mShowing = SHOWING_SAVER; } else { - mNoMan.cancelAsUser(TAG_NOTIFICATION, ID_NOTIFICATION, UserHandle.ALL); + mNoMan.cancelAsUser(TAG_NOTIFICATION, R.id.notification_power, UserHandle.ALL); mShowing = SHOWING_NOTHING; } } @@ -160,13 +159,13 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { .setContentText(mContext.getString(R.string.invalid_charger_text)) .setPriority(Notification.PRIORITY_MAX) .setVisibility(Notification.VISIBILITY_PUBLIC) - .setColor(mContext.getResources().getColor( + .setColor(mContext.getColor( com.android.internal.R.color.system_notification_accent_color)); final Notification n = nb.build(); if (n.headsUpContentView != null) { n.headsUpContentView.setViewVisibility(com.android.internal.R.id.right_icon, View.GONE); } - mNoMan.notifyAsUser(TAG_NOTIFICATION, ID_NOTIFICATION, n, UserHandle.ALL); + mNoMan.notifyAsUser(TAG_NOTIFICATION, R.id.notification_power, n, UserHandle.ALL); } private void showWarningNotification() { @@ -184,7 +183,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { .setDeleteIntent(pendingBroadcast(ACTION_DISMISSED_WARNING)) .setPriority(Notification.PRIORITY_MAX) .setVisibility(Notification.VISIBILITY_PUBLIC) - .setColor(mContext.getResources().getColor( + .setColor(mContext.getColor( com.android.internal.R.color.battery_saver_mode_color)); if (hasBatterySettings()) { nb.setContentIntent(pendingBroadcast(ACTION_SHOW_BATTERY_SETTINGS)); @@ -204,7 +203,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { if (n.headsUpContentView != null) { n.headsUpContentView.setViewVisibility(com.android.internal.R.id.right_icon, View.GONE); } - mNoMan.notifyAsUser(TAG_NOTIFICATION, ID_NOTIFICATION, n, UserHandle.ALL); + mNoMan.notifyAsUser(TAG_NOTIFICATION, R.id.notification_power, n, UserHandle.ALL); } private void showSaverNotification() { @@ -215,13 +214,13 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { .setOngoing(true) .setShowWhen(false) .setVisibility(Notification.VISIBILITY_PUBLIC) - .setColor(mContext.getResources().getColor( + .setColor(mContext.getColor( com.android.internal.R.color.battery_saver_mode_color)); addStopSaverAction(nb); if (hasSaverSettings()) { nb.setContentIntent(pendingActivity(mOpenSaverSettings)); } - mNoMan.notifyAsUser(TAG_NOTIFICATION, ID_NOTIFICATION, nb.build(), UserHandle.ALL); + mNoMan.notifyAsUser(TAG_NOTIFICATION, R.id.notification_power, nb.build(), UserHandle.ALL); } private void addStopSaverAction(Notification.Builder nb) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/DataUsageGraph.java b/packages/SystemUI/src/com/android/systemui/qs/DataUsageGraph.java index d55ceaa..aff5d2b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/DataUsageGraph.java +++ b/packages/SystemUI/src/com/android/systemui/qs/DataUsageGraph.java @@ -44,10 +44,10 @@ public class DataUsageGraph extends View { public DataUsageGraph(Context context, AttributeSet attrs) { super(context, attrs); final Resources res = context.getResources(); - mTrackColor = res.getColor(R.color.data_usage_graph_track); - mUsageColor = res.getColor(R.color.system_accent_color); - mOverlimitColor = res.getColor(R.color.system_warning_color); - mWarningColor = res.getColor(R.color.data_usage_graph_warning); + mTrackColor = context.getColor(R.color.data_usage_graph_track); + mUsageColor = context.getColor(R.color.system_accent_color); + mOverlimitColor = context.getColor(R.color.system_warning_color); + mWarningColor = context.getColor(R.color.data_usage_graph_warning); mMarkerWidth = res.getDimensionPixelSize(R.dimen.data_usage_graph_marker_width); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java index a0b6e82..0ab644a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java @@ -18,12 +18,10 @@ package com.android.systemui.qs; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; -import android.content.res.Configuration; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.Log; -import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 4dacacf..f4fd6a2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -26,7 +26,6 @@ import android.content.res.Resources; import android.os.Handler; import android.os.Message; import android.util.AttributeSet; -import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -253,12 +252,6 @@ public class QSPanel extends ViewGroup { @Override public void onStateChanged(QSTile.State state) { int visibility = state.visible ? VISIBLE : GONE; - if (state.visible && !mGridContentVisible) { - - // We don't want to show it if the content is hidden, - // then we just set it to invisible, to ensure that it gets visible again - visibility = INVISIBLE; - } setTileVisibility(r.tileView, visibility); r.tileView.onStateChanged(state); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java index 16ae6b4..ec83ca7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java @@ -88,7 +88,7 @@ public class QSTileView extends ViewGroup { addView(mIcon); mDivider = new View(mContext); - mDivider.setBackgroundColor(res.getColor(R.color.qs_tile_divider)); + mDivider.setBackgroundColor(context.getColor(R.color.qs_tile_divider)); final int dh = res.getDimensionPixelSize(R.dimen.qs_tile_divider_height); mDivider.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, dh)); addView(mDivider); @@ -138,8 +138,8 @@ public class QSTileView extends ViewGroup { mDualLabel = new QSDualTileLabel(mContext); mDualLabel.setId(android.R.id.title); mDualLabel.setBackgroundResource(R.drawable.btn_borderless_rect); - mDualLabel.setFirstLineCaret(res.getDrawable(R.drawable.qs_dual_tile_caret)); - mDualLabel.setTextColor(res.getColor(R.color.qs_tile_text)); + mDualLabel.setFirstLineCaret(mContext.getDrawable(R.drawable.qs_dual_tile_caret)); + mDualLabel.setTextColor(mContext.getColor(R.color.qs_tile_text)); mDualLabel.setPadding(0, mDualTileVerticalPaddingPx, 0, mDualTileVerticalPaddingPx); mDualLabel.setTypeface(CONDENSED); mDualLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, @@ -157,7 +157,7 @@ public class QSTileView extends ViewGroup { } else { mLabel = new TextView(mContext); mLabel.setId(android.R.id.title); - mLabel.setTextColor(res.getColor(R.color.qs_tile_text)); + mLabel.setTextColor(mContext.getColor(R.color.qs_tile_text)); mLabel.setGravity(Gravity.CENTER_HORIZONTAL); mLabel.setMinLines(2); mLabel.setPadding(0, 0, 0, 0); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java index 2dd02a5..2bc31fc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java @@ -72,7 +72,7 @@ public class AirplaneModeTile extends QSTile<QSTile.BooleanState> { final boolean airplaneMode = value != 0; state.value = airplaneMode; state.visible = true; - state.label = mContext.getString(R.string.quick_settings_airplane_mode_label); + state.label = mContext.getString(R.string.airplane_mode); if (airplaneMode) { state.icon = mEnable; state.contentDescription = mContext.getString( diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java index c15566f..b42b5f6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java @@ -16,6 +16,8 @@ package com.android.systemui.qs.tiles; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; import android.content.Context; import android.content.Intent; import android.provider.Settings; @@ -23,13 +25,14 @@ import android.text.TextUtils; import android.view.View; import android.view.ViewGroup; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.systemui.R; import com.android.systemui.qs.QSDetailItems; import com.android.systemui.qs.QSDetailItems.Item; import com.android.systemui.qs.QSTile; import com.android.systemui.statusbar.policy.BluetoothController; -import com.android.systemui.statusbar.policy.BluetoothController.PairedDevice; +import java.util.Collection; import java.util.Set; /** Quick settings tile: Bluetooth **/ @@ -143,7 +146,7 @@ public class BluetoothTile extends QSTile<QSTile.BooleanState> { refreshState(); } @Override - public void onBluetoothPairedDevicesChanged() { + public void onBluetoothDevicesChanged() { mUiHandler.post(new Runnable() { @Override public void run() { @@ -199,19 +202,21 @@ public class BluetoothTile extends QSTile<QSTile.BooleanState> { private void updateItems() { if (mItems == null) return; Item[] items = null; - final Set<PairedDevice> devices = mController.getPairedDevices(); + final Collection<CachedBluetoothDevice> devices = mController.getDevices(); if (devices != null) { - items = new Item[devices.size()]; + items = new Item[getBondedCount(devices)]; int i = 0; - for (PairedDevice device : devices) { + for (CachedBluetoothDevice device : devices) { + if (device.getBondState() == BluetoothDevice.BOND_NONE) continue; final Item item = new Item(); item.icon = R.drawable.ic_qs_bluetooth_on; - item.line1 = device.name; - if (device.state == PairedDevice.STATE_CONNECTED) { + item.line1 = device.getName(); + int state = device.getMaxConnectionState(); + if (state == BluetoothProfile.STATE_CONNECTED) { item.icon = R.drawable.ic_qs_bluetooth_connected; item.line2 = mContext.getString(R.string.quick_settings_connected); item.canDisconnect = true; - } else if (device.state == PairedDevice.STATE_CONNECTING) { + } else if (state == BluetoothProfile.STATE_CONNECTING) { item.icon = R.drawable.ic_qs_bluetooth_connecting; item.line2 = mContext.getString(R.string.quick_settings_connecting); } @@ -222,11 +227,22 @@ public class BluetoothTile extends QSTile<QSTile.BooleanState> { mItems.setItems(items); } + private int getBondedCount(Collection<CachedBluetoothDevice> devices) { + int ct = 0; + for (CachedBluetoothDevice device : devices) { + if (device.getBondState() != BluetoothDevice.BOND_NONE) { + ct++; + } + } + return ct; + } + @Override public void onDetailItemClick(Item item) { if (item == null || item.tag == null) return; - final PairedDevice device = (PairedDevice) item.tag; - if (device != null && device.state == PairedDevice.STATE_DISCONNECTED) { + final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag; + if (device != null && device.getMaxConnectionState() + == BluetoothProfile.STATE_DISCONNECTED) { mController.connect(device); } } @@ -234,7 +250,7 @@ public class BluetoothTile extends QSTile<QSTile.BooleanState> { @Override public void onDetailItemDisconnect(Item item) { if (item == null || item.tag == null) return; - final PairedDevice device = (PairedDevice) item.tag; + final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag; if (device != null) { mController.disconnect(device); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataUsageDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataUsageDetailView.java index eb816b7..d0ae383 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataUsageDetailView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataUsageDetailView.java @@ -96,7 +96,7 @@ public class DataUsageDetailView extends LinearLayout { title.setText(titleId); final TextView usage = (TextView) findViewById(R.id.usage_text); usage.setText(formatBytes(bytes)); - usage.setTextColor(res.getColor(usageColor)); + usage.setTextColor(mContext.getColor(usageColor)); final DataUsageGraph graph = (DataUsageGraph) findViewById(R.id.usage_graph); graph.setLevels(info.limitLevel, info.warningLevel, info.usageLevel); final TextView carrier = (TextView) findViewById(R.id.usage_carrier_text); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java new file mode 100644 index 0000000..ff6a45a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.provider.Settings; +import android.provider.Settings.Global; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnAttachStateChangeListener; +import android.view.ViewGroup; + +import com.android.systemui.R; +import com.android.systemui.qs.QSTile; +import com.android.systemui.statusbar.policy.ZenModeController; +import com.android.systemui.volume.ZenModePanel; + +/** Quick settings tile: Do not disturb **/ +public class DndTile extends QSTile<QSTile.BooleanState> { + private static final Intent ZEN_SETTINGS = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS); + + private static final String ACTION_SET_VISIBLE = "com.android.systemui.dndtile.SET_VISIBLE"; + private static final String EXTRA_VISIBLE = "visible"; + private static final String PREF_KEY_VISIBLE = "DndTileVisible"; + private static final String PREF_KEY_COMBINED_ICON = "DndTileCombinedIcon"; + + private final ZenModeController mController; + private final DndDetailAdapter mDetailAdapter; + + private boolean mListening; + private boolean mVisible; + private boolean mShowingDetail; + + public DndTile(Host host) { + super(host); + mController = host.getZenModeController(); + mDetailAdapter = new DndDetailAdapter(); + mVisible = isVisible(host.getContext()); + mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_SET_VISIBLE)); + } + + public static void setVisible(Context context, boolean visible) { + context.sendBroadcast(new Intent(DndTile.ACTION_SET_VISIBLE) + .putExtra(DndTile.EXTRA_VISIBLE, visible)); + } + + public static boolean isVisible(Context context) { + return getSharedPrefs(context).getBoolean(PREF_KEY_VISIBLE, false); + } + + public static void setCombinedIcon(Context context, boolean combined) { + getSharedPrefs(context).edit().putBoolean(PREF_KEY_COMBINED_ICON, combined).commit(); + } + + public static boolean isCombinedIcon(Context context) { + return getSharedPrefs(context).getBoolean(PREF_KEY_COMBINED_ICON, false); + } + + @Override + public DetailAdapter getDetailAdapter() { + return mDetailAdapter; + } + + @Override + protected BooleanState newTileState() { + return new BooleanState(); + } + + @Override + public void handleClick() { + if (mState.value) { + mController.setZen(Global.ZEN_MODE_OFF); + } else { + mController.setZen(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS); + showDetail(true); + } + } + + @Override + protected void handleUpdateState(BooleanState state, Object arg) { + final int zen = arg instanceof Integer ? (Integer) arg : mController.getZen(); + state.value = zen != Global.ZEN_MODE_OFF; + state.visible = mVisible; + switch (zen) { + case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: + state.icon = ResourceIcon.get(R.drawable.ic_qs_dnd_on); + state.label = mContext.getString(R.string.quick_settings_dnd_priority_label); + state.contentDescription = mContext.getString( + R.string.accessibility_quick_settings_dnd_priority_on); + break; + case Global.ZEN_MODE_NO_INTERRUPTIONS: + state.icon = ResourceIcon.get(R.drawable.ic_qs_dnd_on); + state.label = mContext.getString(R.string.quick_settings_dnd_none_label); + state.contentDescription = mContext.getString( + R.string.accessibility_quick_settings_dnd_none_on); + break; + case Global.ZEN_MODE_ALARMS: + state.icon = ResourceIcon.get(R.drawable.ic_qs_dnd_on); + state.label = mContext.getString(R.string.quick_settings_dnd_alarms_label); + state.contentDescription = mContext.getString( + R.string.accessibility_quick_settings_dnd_alarms_on); + break; + default: + state.icon = ResourceIcon.get(R.drawable.ic_qs_dnd_off); + state.label = mContext.getString(R.string.quick_settings_dnd_label); + state.contentDescription = mContext.getString( + R.string.accessibility_quick_settings_dnd_off); + break; + } + if (mShowingDetail && !state.value) { + showDetail(false); + } + } + + @Override + protected String composeChangeAnnouncement() { + if (mState.value) { + return mContext.getString(R.string.accessibility_quick_settings_dnd_changed_on); + } else { + return mContext.getString(R.string.accessibility_quick_settings_dnd_changed_off); + } + } + + @Override + public void setListening(boolean listening) { + if (mListening == listening) return; + mListening = listening; + if (mListening) { + mController.addCallback(mZenCallback); + } else { + mController.removeCallback(mZenCallback); + } + } + + private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() { + public void onZenChanged(int zen) { + refreshState(zen); + } + }; + + private static SharedPreferences getSharedPrefs(Context context) { + return context.getSharedPreferences(context.getPackageName(), 0); + } + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + mVisible = intent.getBooleanExtra(EXTRA_VISIBLE, false); + getSharedPrefs(mContext).edit().putBoolean(PREF_KEY_VISIBLE, mVisible).commit(); + refreshState(); + } + }; + + private final class DndDetailAdapter implements DetailAdapter, OnAttachStateChangeListener { + + @Override + public int getTitle() { + return R.string.quick_settings_dnd_label; + } + + @Override + public Boolean getToggleState() { + return mState.value; + } + + @Override + public Intent getSettingsIntent() { + return ZEN_SETTINGS; + } + + @Override + public void setToggleState(boolean state) { + if (!state) { + mController.setZen(Global.ZEN_MODE_OFF); + showDetail(false); + } + } + + @Override + public View createDetailView(Context context, View convertView, ViewGroup parent) { + final ZenModePanel zmp = convertView != null ? (ZenModePanel) convertView + : (ZenModePanel) LayoutInflater.from(context).inflate( + R.layout.zen_mode_panel, parent, false); + if (convertView == null) { + zmp.init(mController); + zmp.setEmbedded(true); + zmp.addOnAttachStateChangeListener(this); + } + return zmp; + } + + @Override + public void onViewAttachedToWindow(View v) { + mShowingDetail = true; + } + + @Override + public void onViewDetachedFromWindow(View v) { + mShowingDetail = false; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java index 5c1a317..cb78deb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java @@ -17,7 +17,6 @@ package com.android.systemui.qs.tiles; import android.app.ActivityManager; -import android.os.SystemClock; import com.android.systemui.R; import com.android.systemui.qs.QSTile; @@ -27,16 +26,11 @@ import com.android.systemui.statusbar.policy.FlashlightController; public class FlashlightTile extends QSTile<QSTile.BooleanState> implements FlashlightController.FlashlightListener { - /** Grace period for which we consider the flashlight - * still available because it was recently on. */ - private static final long RECENTLY_ON_DURATION_MILLIS = 500; - private final AnimationIcon mEnable = new AnimationIcon(R.drawable.ic_signal_flashlight_enable_animation); private final AnimationIcon mDisable = new AnimationIcon(R.drawable.ic_signal_flashlight_disable_animation); private final FlashlightController mFlashlightController; - private long mWasLastOn; public FlashlightTile(Host host) { super(host); @@ -69,33 +63,17 @@ public class FlashlightTile extends QSTile<QSTile.BooleanState> implements return; } boolean newState = !mState.value; - mFlashlightController.setFlashlight(newState); refreshState(newState ? UserBoolean.USER_TRUE : UserBoolean.USER_FALSE); + mFlashlightController.setFlashlight(newState); } @Override protected void handleUpdateState(BooleanState state, Object arg) { - if (state.value) { - mWasLastOn = SystemClock.uptimeMillis(); - } - + state.visible = mFlashlightController.isAvailable(); + state.label = mHost.getContext().getString(R.string.quick_settings_flashlight_label); if (arg instanceof UserBoolean) { state.value = ((UserBoolean) arg).value; } - - if (!state.value && mWasLastOn != 0) { - if (SystemClock.uptimeMillis() > mWasLastOn + RECENTLY_ON_DURATION_MILLIS) { - mWasLastOn = 0; - } else { - mHandler.removeCallbacks(mRecentlyOnTimeout); - mHandler.postAtTime(mRecentlyOnTimeout, mWasLastOn + RECENTLY_ON_DURATION_MILLIS); - } - } - - // Always show the tile when the flashlight is or was recently on. This is needed because - // the camera is not available while it is being used for the flashlight. - state.visible = mWasLastOn != 0 || mFlashlightController.isAvailable(); - state.label = mHost.getContext().getString(R.string.quick_settings_flashlight_label); final AnimationIcon icon = state.value ? mEnable : mDisable; icon.setAllowAnimation(arg instanceof UserBoolean && ((UserBoolean) arg).userInitiated); state.icon = icon; @@ -115,8 +93,8 @@ public class FlashlightTile extends QSTile<QSTile.BooleanState> implements } @Override - public void onFlashlightOff() { - refreshState(UserBoolean.BACKGROUND_FALSE); + public void onFlashlightChanged(boolean enabled) { + refreshState(enabled ? UserBoolean.BACKGROUND_TRUE : UserBoolean.BACKGROUND_FALSE); } @Override @@ -128,11 +106,4 @@ public class FlashlightTile extends QSTile<QSTile.BooleanState> implements public void onFlashlightAvailabilityChanged(boolean available) { refreshState(); } - - private Runnable mRecentlyOnTimeout = new Runnable() { - @Override - public void run() { - refreshState(); - } - }; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java index c55cbcc..21c5c96 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java @@ -28,7 +28,6 @@ import android.graphics.Bitmap; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.util.AttributeSet; -import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java index e09024b..70746c7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java @@ -16,6 +16,8 @@ package com.android.systemui.qs.tiles; +import java.util.List; + import android.content.Context; import android.content.Intent; import android.content.res.Resources; @@ -24,16 +26,15 @@ import android.util.Log; import android.view.View; import android.view.ViewGroup; +import com.android.settingslib.wifi.AccessPoint; import com.android.systemui.R; import com.android.systemui.qs.QSDetailItems; import com.android.systemui.qs.QSDetailItems.Item; import com.android.systemui.qs.QSTile; import com.android.systemui.qs.QSTileView; import com.android.systemui.qs.SignalTileView; -import com.android.systemui.statusbar.phone.QSTileHost; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.NetworkController.AccessPointController; -import com.android.systemui.statusbar.policy.NetworkController.AccessPointController.AccessPoint; import com.android.systemui.statusbar.policy.NetworkController.NetworkSignalChangedCallback; /** Quick settings tile: Wifi **/ @@ -282,10 +283,10 @@ public class WifiTile extends QSTile<QSTile.SignalState> { } @Override - public void onAccessPointsChanged(final AccessPoint[] accessPoints) { - mAccessPoints = accessPoints; + public void onAccessPointsChanged(final List<AccessPoint> accessPoints) { + mAccessPoints = accessPoints.toArray(new AccessPoint[accessPoints.size()]); updateItems(); - if (accessPoints != null && accessPoints.length > 0) { + if (accessPoints != null && accessPoints.size() > 0) { fireScanStateChanged(false); } } @@ -299,7 +300,7 @@ public class WifiTile extends QSTile<QSTile.SignalState> { public void onDetailItemClick(Item item) { if (item == null || item.tag == null) return; final AccessPoint ap = (AccessPoint) item.tag; - if (!ap.isConnected) { + if (!ap.isActive()) { if (mWifiController.connect(ap)) { mHost.collapsePanels(); } @@ -326,16 +327,10 @@ public class WifiTile extends QSTile<QSTile.SignalState> { final AccessPoint ap = mAccessPoints[i]; final Item item = new Item(); item.tag = ap; - item.icon = ap.iconId; - item.line1 = ap.ssid; - if (ap.isConnected) { - item.line2 = mContext.getString(ap.isConfigured ? - R.string.quick_settings_connected : - R.string.quick_settings_connected_via_wfa); - } else if (ap.networkId >= 0) { - // TODO: Set line 2 to wifi saved string here. - } - item.overlay = ap.hasSecurity + item.icon = mWifiController.getIcon(ap); + item.line1 = ap.getSsid(); + item.line2 = ap.getSummary(); + item.overlay = ap.getSecurity() != AccessPoint.SECURITY_NONE ? mContext.getDrawable(R.drawable.qs_ic_wifi_lock) : null; items[i] = item; diff --git a/packages/SystemUI/src/com/android/systemui/recent/ColorDrawableWithDimensions.java b/packages/SystemUI/src/com/android/systemui/recent/ColorDrawableWithDimensions.java deleted file mode 100644 index b4d3edd..0000000 --- a/packages/SystemUI/src/com/android/systemui/recent/ColorDrawableWithDimensions.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.recent; - -import android.graphics.drawable.ColorDrawable; - -public class ColorDrawableWithDimensions extends ColorDrawable { - private int mWidth; - private int mHeight; - - public ColorDrawableWithDimensions(int color, int width, int height) { - super(color); - mWidth = width; - mHeight = height; - } - - @Override - public int getIntrinsicWidth() { - return mWidth; - } - - @Override - public int getIntrinsicHeight() { - return mHeight; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recent/Constants.java b/packages/SystemUI/src/com/android/systemui/recent/Constants.java deleted file mode 100644 index 8252a9f..0000000 --- a/packages/SystemUI/src/com/android/systemui/recent/Constants.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.recent; - -public class Constants { - static final int MAX_ESCAPE_ANIMATION_DURATION = 500; // in ms - static final int SNAP_BACK_DURATION = 250; // in ms - static final int ESCAPE_VELOCITY = 100; // speed of item required to "curate" it in dp/s - public static float ALPHA_FADE_START = 0.8f; // fraction of thumbnail width where fade starts - static final float ALPHA_FADE_END = 0.5f; // fraction of thumbnail width beyond which alpha->0 -} diff --git a/packages/SystemUI/src/com/android/systemui/recent/FadedEdgeDrawHelper.java b/packages/SystemUI/src/com/android/systemui/recent/FadedEdgeDrawHelper.java deleted file mode 100644 index 59f7a80..0000000 --- a/packages/SystemUI/src/com/android/systemui/recent/FadedEdgeDrawHelper.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.recent; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.LinearGradient; -import android.graphics.Matrix; -import android.graphics.Paint; -import android.graphics.Shader; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewConfiguration; -import android.widget.LinearLayout; - -import com.android.systemui.R; - -public class FadedEdgeDrawHelper { - public static final boolean OPTIMIZE_SW_RENDERED_RECENTS = true; - public static final boolean USE_DARK_FADE_IN_HW_ACCELERATED_MODE = true; - private View mScrollView; - - private int mFadingEdgeLength; - private boolean mIsVertical; - private boolean mSoftwareRendered = false; - private Paint mBlackPaint; - private Paint mFadePaint; - private Matrix mFadeMatrix; - private LinearGradient mFade; - - public static FadedEdgeDrawHelper create(Context context, - AttributeSet attrs, View scrollView, boolean isVertical) { - boolean isTablet = context.getResources(). - getBoolean(R.bool.config_recents_interface_for_tablets); - if (!isTablet && (OPTIMIZE_SW_RENDERED_RECENTS || USE_DARK_FADE_IN_HW_ACCELERATED_MODE)) { - return new FadedEdgeDrawHelper(context, attrs, scrollView, isVertical); - } else { - return null; - } - } - - public FadedEdgeDrawHelper(Context context, - AttributeSet attrs, View scrollView, boolean isVertical) { - mScrollView = scrollView; - TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View); - mFadingEdgeLength = a.getDimensionPixelSize(android.R.styleable.View_fadingEdgeLength, - ViewConfiguration.get(context).getScaledFadingEdgeLength()); - mIsVertical = isVertical; - } - - public void onAttachedToWindowCallback( - LinearLayout layout, boolean hardwareAccelerated) { - mSoftwareRendered = !hardwareAccelerated; - if ((mSoftwareRendered && OPTIMIZE_SW_RENDERED_RECENTS) - || USE_DARK_FADE_IN_HW_ACCELERATED_MODE) { - mScrollView.setVerticalFadingEdgeEnabled(false); - mScrollView.setHorizontalFadingEdgeEnabled(false); - } - } - - public void addViewCallback(View newLinearLayoutChild) { - if (mSoftwareRendered && OPTIMIZE_SW_RENDERED_RECENTS) { - final RecentsPanelView.ViewHolder holder = - (RecentsPanelView.ViewHolder) newLinearLayoutChild.getTag(); - holder.labelView.setDrawingCacheEnabled(true); - holder.labelView.buildDrawingCache(); - } - } - - public void drawCallback(Canvas canvas, - int left, int right, int top, int bottom, int scrollX, int scrollY, - float topFadingEdgeStrength, float bottomFadingEdgeStrength, - float leftFadingEdgeStrength, float rightFadingEdgeStrength, int mPaddingTop) { - - if ((mSoftwareRendered && OPTIMIZE_SW_RENDERED_RECENTS) - || USE_DARK_FADE_IN_HW_ACCELERATED_MODE) { - if (mFadePaint == null) { - mFadePaint = new Paint(); - mFadeMatrix = new Matrix(); - // use use a height of 1, and then wack the matrix each time we - // actually use it. - mFade = new LinearGradient(0, 0, 0, 1, 0xCC000000, 0, Shader.TileMode.CLAMP); - // PULL OUT THIS CONSTANT - mFadePaint.setShader(mFade); - } - - // draw the fade effect - boolean drawTop = false; - boolean drawBottom = false; - boolean drawLeft = false; - boolean drawRight = false; - - float topFadeStrength = 0.0f; - float bottomFadeStrength = 0.0f; - float leftFadeStrength = 0.0f; - float rightFadeStrength = 0.0f; - - final float fadeHeight = mFadingEdgeLength; - int length = (int) fadeHeight; - - // clip the fade length if top and bottom fades overlap - // overlapping fades produce odd-looking artifacts - if (mIsVertical && (top + length > bottom - length)) { - length = (bottom - top) / 2; - } - - // also clip horizontal fades if necessary - if (!mIsVertical && (left + length > right - length)) { - length = (right - left) / 2; - } - - if (mIsVertical) { - topFadeStrength = Math.max(0.0f, Math.min(1.0f, topFadingEdgeStrength)); - drawTop = topFadeStrength * fadeHeight > 1.0f; - bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, bottomFadingEdgeStrength)); - drawBottom = bottomFadeStrength * fadeHeight > 1.0f; - } - - if (!mIsVertical) { - leftFadeStrength = Math.max(0.0f, Math.min(1.0f, leftFadingEdgeStrength)); - drawLeft = leftFadeStrength * fadeHeight > 1.0f; - rightFadeStrength = Math.max(0.0f, Math.min(1.0f, rightFadingEdgeStrength)); - drawRight = rightFadeStrength * fadeHeight > 1.0f; - } - - if (drawTop) { - mFadeMatrix.setScale(1, fadeHeight * topFadeStrength); - mFadeMatrix.postTranslate(left, top); - mFade.setLocalMatrix(mFadeMatrix); - mFadePaint.setShader(mFade); - canvas.drawRect(left, top, right, top + length, mFadePaint); - - if (mBlackPaint == null) { - // Draw under the status bar at the top - mBlackPaint = new Paint(); - mBlackPaint.setColor(0xFF000000); - } - canvas.drawRect(left, top - mPaddingTop, right, top, mBlackPaint); - } - - if (drawBottom) { - mFadeMatrix.setScale(1, fadeHeight * bottomFadeStrength); - mFadeMatrix.postRotate(180); - mFadeMatrix.postTranslate(left, bottom); - mFade.setLocalMatrix(mFadeMatrix); - mFadePaint.setShader(mFade); - canvas.drawRect(left, bottom - length, right, bottom, mFadePaint); - } - - if (drawLeft) { - mFadeMatrix.setScale(1, fadeHeight * leftFadeStrength); - mFadeMatrix.postRotate(-90); - mFadeMatrix.postTranslate(left, top); - mFade.setLocalMatrix(mFadeMatrix); - mFadePaint.setShader(mFade); - canvas.drawRect(left, top, left + length, bottom, mFadePaint); - } - - if (drawRight) { - mFadeMatrix.setScale(1, fadeHeight * rightFadeStrength); - mFadeMatrix.postRotate(90); - mFadeMatrix.postTranslate(right, top); - mFade.setLocalMatrix(mFadeMatrix); - mFadePaint.setShader(mFade); - canvas.drawRect(right - length, top, right, bottom, mFadePaint); - } - } - } - - public int getVerticalFadingEdgeLength() { - return mFadingEdgeLength; - } - - public int getHorizontalFadingEdgeLength() { - return mFadingEdgeLength; - } - -} diff --git a/packages/SystemUI/src/com/android/systemui/recent/FirstFrameAnimatorHelper.java b/packages/SystemUI/src/com/android/systemui/recent/FirstFrameAnimatorHelper.java deleted file mode 100644 index 84d13cf..0000000 --- a/packages/SystemUI/src/com/android/systemui/recent/FirstFrameAnimatorHelper.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.recent; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; -import android.util.Log; -import android.view.View; -import android.view.ViewPropertyAnimator; -import android.view.ViewTreeObserver; - -/* - * This is a helper class that listens to updates from the corresponding animation. - * For the first two frames, it adjusts the current play time of the animation to - * prevent jank at the beginning of the animation - */ -public class FirstFrameAnimatorHelper extends AnimatorListenerAdapter - implements ValueAnimator.AnimatorUpdateListener { - private static final boolean DEBUG = false; - private static final int MAX_DELAY = 1000; - private static final int IDEAL_FRAME_DURATION = 16; - private View mTarget; - private long mStartFrame; - private long mStartTime = -1; - private boolean mHandlingOnAnimationUpdate; - private boolean mAdjustedSecondFrameTime; - - private static ViewTreeObserver.OnDrawListener sGlobalDrawListener; - private static long sGlobalFrameCounter; - - public FirstFrameAnimatorHelper(ValueAnimator animator, View target) { - mTarget = target; - animator.addUpdateListener(this); - } - - public FirstFrameAnimatorHelper(ViewPropertyAnimator vpa, View target) { - mTarget = target; - vpa.setListener(this); - } - - // only used for ViewPropertyAnimators - public void onAnimationStart(Animator animation) { - final ValueAnimator va = (ValueAnimator) animation; - va.addUpdateListener(FirstFrameAnimatorHelper.this); - onAnimationUpdate(va); - } - - public static void initializeDrawListener(View view) { - if (sGlobalDrawListener != null) { - view.getViewTreeObserver().removeOnDrawListener(sGlobalDrawListener); - } - sGlobalDrawListener = new ViewTreeObserver.OnDrawListener() { - private long mTime = System.currentTimeMillis(); - public void onDraw() { - sGlobalFrameCounter++; - if (DEBUG) { - long newTime = System.currentTimeMillis(); - Log.d("FirstFrameAnimatorHelper", "TICK " + (newTime - mTime)); - mTime = newTime; - } - } - }; - view.getViewTreeObserver().addOnDrawListener(sGlobalDrawListener); - } - - public void onAnimationUpdate(final ValueAnimator animation) { - final long currentTime = System.currentTimeMillis(); - if (mStartTime == -1) { - mStartFrame = sGlobalFrameCounter; - mStartTime = currentTime; - } - - if (!mHandlingOnAnimationUpdate && - // If the current play time exceeds the duration, the animation - // will get finished, even if we call setCurrentPlayTime -- therefore - // don't adjust the animation in that case - animation.getCurrentPlayTime() < animation.getDuration()) { - mHandlingOnAnimationUpdate = true; - long frameNum = sGlobalFrameCounter - mStartFrame; - // If we haven't drawn our first frame, reset the time to t = 0 - // (give up after MAX_DELAY ms of waiting though - might happen, for example, if we - // are no longer in the foreground and no frames are being rendered ever) - if (frameNum == 0 && currentTime < mStartTime + MAX_DELAY) { - // The first frame on animations doesn't always trigger an invalidate... - // force an invalidate here to make sure the animation continues to advance - mTarget.getRootView().invalidate(); - animation.setCurrentPlayTime(0); - - // For the second frame, if the first frame took more than 16ms, - // adjust the start time and pretend it took only 16ms anyway. This - // prevents a large jump in the animation due to an expensive first frame - } else if (frameNum == 1 && currentTime < mStartTime + MAX_DELAY && - !mAdjustedSecondFrameTime && - currentTime > mStartTime + IDEAL_FRAME_DURATION) { - animation.setCurrentPlayTime(IDEAL_FRAME_DURATION); - mAdjustedSecondFrameTime = true; - } else { - if (frameNum > 1) { - mTarget.post(new Runnable() { - public void run() { - animation.removeUpdateListener(FirstFrameAnimatorHelper.this); - } - }); - } - if (DEBUG) print(animation); - } - mHandlingOnAnimationUpdate = false; - } else { - if (DEBUG) print(animation); - } - } - - public void print(ValueAnimator animation) { - float flatFraction = animation.getCurrentPlayTime() / (float) animation.getDuration(); - Log.d("FirstFrameAnimatorHelper", sGlobalFrameCounter + - "(" + (sGlobalFrameCounter - mStartFrame) + ") " + mTarget + " dirty? " + - mTarget.isDirty() + " " + flatFraction + " " + this + " " + animation); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java b/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java deleted file mode 100644 index 34430d9..0000000 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java +++ /dev/null @@ -1,586 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.recent; - -import android.app.ActivityManager; -import android.app.AppGlobals; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.IPackageManager; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.os.AsyncTask; -import android.os.Handler; -import android.os.Process; -import android.os.RemoteException; -import android.os.UserHandle; -import android.util.Log; -import android.view.MotionEvent; -import android.view.View; - -import com.android.systemui.R; -import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.statusbar.phone.PhoneStatusBar; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; - -public class RecentTasksLoader implements View.OnTouchListener { - static final String TAG = "RecentTasksLoader"; - static final boolean DEBUG = PhoneStatusBar.DEBUG || false; - - private static final int DISPLAY_TASKS = 20; - private static final int MAX_TASKS = DISPLAY_TASKS + 1; // allow extra for non-apps - - private Context mContext; - private RecentsPanelView mRecentsPanel; - - private Object mFirstTaskLock = new Object(); - private TaskDescription mFirstTask; - private boolean mFirstTaskLoaded; - - private AsyncTask<Void, ArrayList<TaskDescription>, Void> mTaskLoader; - private AsyncTask<Void, TaskDescription, Void> mThumbnailLoader; - private Handler mHandler; - - private int mIconDpi; - private ColorDrawableWithDimensions mDefaultThumbnailBackground; - private ColorDrawableWithDimensions mDefaultIconBackground; - private int mNumTasksInFirstScreenful = Integer.MAX_VALUE; - - private boolean mFirstScreenful; - private ArrayList<TaskDescription> mLoadedTasks; - - private enum State { LOADING, LOADED, CANCELLED }; - private State mState = State.CANCELLED; - - - private static RecentTasksLoader sInstance; - public static RecentTasksLoader getInstance(Context context) { - if (sInstance == null) { - sInstance = new RecentTasksLoader(context); - } - return sInstance; - } - - private RecentTasksLoader(Context context) { - mContext = context; - mHandler = new Handler(); - - final Resources res = context.getResources(); - - // get the icon size we want -- on tablets, we use bigger icons - boolean isTablet = res.getBoolean(R.bool.config_recents_interface_for_tablets); - if (isTablet) { - ActivityManager activityManager = - (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - mIconDpi = activityManager.getLauncherLargeIconDensity(); - } else { - mIconDpi = res.getDisplayMetrics().densityDpi; - } - - // Render default icon (just a blank image) - int defaultIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.app_icon_size); - int iconSize = (int) (defaultIconSize * mIconDpi / res.getDisplayMetrics().densityDpi); - mDefaultIconBackground = new ColorDrawableWithDimensions(0x00000000, iconSize, iconSize); - - // Render the default thumbnail background - int thumbnailWidth = - (int) res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_width); - int thumbnailHeight = - (int) res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_height); - int color = res.getColor(R.drawable.status_bar_recents_app_thumbnail_background); - - mDefaultThumbnailBackground = - new ColorDrawableWithDimensions(color, thumbnailWidth, thumbnailHeight); - } - - public void setRecentsPanel(RecentsPanelView newRecentsPanel, RecentsPanelView caller) { - // Only allow clearing mRecentsPanel if the caller is the current recentsPanel - if (newRecentsPanel != null || mRecentsPanel == caller) { - mRecentsPanel = newRecentsPanel; - if (mRecentsPanel != null) { - mNumTasksInFirstScreenful = mRecentsPanel.numItemsInOneScreenful(); - } - } - } - - public Drawable getDefaultThumbnail() { - return mDefaultThumbnailBackground; - } - - public Drawable getDefaultIcon() { - return mDefaultIconBackground; - } - - public ArrayList<TaskDescription> getLoadedTasks() { - return mLoadedTasks; - } - - public void remove(TaskDescription td) { - mLoadedTasks.remove(td); - } - - public boolean isFirstScreenful() { - return mFirstScreenful; - } - - private boolean isCurrentHomeActivity(ComponentName component, ActivityInfo homeInfo) { - if (homeInfo == null) { - final PackageManager pm = mContext.getPackageManager(); - homeInfo = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME) - .resolveActivityInfo(pm, 0); - } - return homeInfo != null - && homeInfo.packageName.equals(component.getPackageName()) - && homeInfo.name.equals(component.getClassName()); - } - - // Create an TaskDescription, returning null if the title or icon is null - TaskDescription createTaskDescription(int taskId, int persistentTaskId, Intent baseIntent, - ComponentName origActivity, CharSequence description, int userId) { - Intent intent = new Intent(baseIntent); - if (origActivity != null) { - intent.setComponent(origActivity); - } - final PackageManager pm = mContext.getPackageManager(); - final IPackageManager ipm = AppGlobals.getPackageManager(); - intent.setFlags((intent.getFlags()&~Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) - | Intent.FLAG_ACTIVITY_NEW_TASK); - ResolveInfo resolveInfo = null; - try { - resolveInfo = ipm.resolveIntent(intent, null, 0, userId); - } catch (RemoteException re) { - } - if (resolveInfo != null) { - final ActivityInfo info = resolveInfo.activityInfo; - final String title = info.loadLabel(pm).toString(); - - if (title != null && title.length() > 0) { - if (DEBUG) Log.v(TAG, "creating activity desc for id=" - + persistentTaskId + ", label=" + title); - - TaskDescription item = new TaskDescription(taskId, - persistentTaskId, resolveInfo, baseIntent, info.packageName, - description, userId); - item.setLabel(title); - - return item; - } else { - if (DEBUG) Log.v(TAG, "SKIPPING item " + persistentTaskId); - } - } - return null; - } - - void loadThumbnailAndIcon(TaskDescription td) { - final ActivityManager am = (ActivityManager) - mContext.getSystemService(Context.ACTIVITY_SERVICE); - final PackageManager pm = mContext.getPackageManager(); - final Bitmap thumbnail = SystemServicesProxy.getThumbnail(am, td.persistentTaskId); - Drawable icon = getFullResIcon(td.resolveInfo, pm); - if (td.userId != UserHandle.myUserId()) { - // Need to badge the icon - icon = mContext.getPackageManager().getUserBadgedIcon(icon, new UserHandle(td.userId)); - } - if (DEBUG) Log.v(TAG, "Loaded bitmap for task " - + td + ": " + thumbnail); - synchronized (td) { - if (thumbnail != null) { - td.setThumbnail(new BitmapDrawable(mContext.getResources(), thumbnail)); - } else { - td.setThumbnail(mDefaultThumbnailBackground); - } - if (icon != null) { - td.setIcon(icon); - } - td.setLoaded(true); - } - } - - Drawable getFullResDefaultActivityIcon() { - return getFullResIcon(Resources.getSystem(), - com.android.internal.R.mipmap.sym_def_app_icon); - } - - Drawable getFullResIcon(Resources resources, int iconId) { - try { - return resources.getDrawableForDensity(iconId, mIconDpi); - } catch (Resources.NotFoundException e) { - return getFullResDefaultActivityIcon(); - } - } - - private Drawable getFullResIcon(ResolveInfo info, PackageManager packageManager) { - Resources resources; - try { - resources = packageManager.getResourcesForApplication( - info.activityInfo.applicationInfo); - } catch (PackageManager.NameNotFoundException e) { - resources = null; - } - if (resources != null) { - int iconId = info.activityInfo.getIconResource(); - if (iconId != 0) { - return getFullResIcon(resources, iconId); - } - } - return getFullResDefaultActivityIcon(); - } - - Runnable mPreloadTasksRunnable = new Runnable() { - public void run() { - loadTasksInBackground(); - } - }; - - // additional optimization when we have software system buttons - start loading the recent - // tasks on touch down - @Override - public boolean onTouch(View v, MotionEvent ev) { - int action = ev.getAction() & MotionEvent.ACTION_MASK; - if (action == MotionEvent.ACTION_DOWN) { - preloadRecentTasksList(); - } else if (action == MotionEvent.ACTION_CANCEL) { - cancelPreloadingRecentTasksList(); - } else if (action == MotionEvent.ACTION_UP) { - // Remove the preloader if we haven't called it yet - mHandler.removeCallbacks(mPreloadTasksRunnable); - if (!v.isPressed()) { - cancelLoadingThumbnailsAndIcons(); - } - - } - return false; - } - - public void preloadRecentTasksList() { - mHandler.post(mPreloadTasksRunnable); - } - - public void cancelPreloadingRecentTasksList() { - cancelLoadingThumbnailsAndIcons(); - mHandler.removeCallbacks(mPreloadTasksRunnable); - } - - public void cancelLoadingThumbnailsAndIcons(RecentsPanelView caller) { - // Only oblige this request if it comes from the current RecentsPanel - // (eg when you rotate, the old RecentsPanel request should be ignored) - if (mRecentsPanel == caller) { - cancelLoadingThumbnailsAndIcons(); - } - } - - - private void cancelLoadingThumbnailsAndIcons() { - if (mRecentsPanel != null && mRecentsPanel.isShowing()) { - return; - } - - if (mTaskLoader != null) { - mTaskLoader.cancel(false); - mTaskLoader = null; - } - if (mThumbnailLoader != null) { - mThumbnailLoader.cancel(false); - mThumbnailLoader = null; - } - mLoadedTasks = null; - if (mRecentsPanel != null) { - mRecentsPanel.onTaskLoadingCancelled(); - } - mFirstScreenful = false; - mState = State.CANCELLED; - } - - private void clearFirstTask() { - synchronized (mFirstTaskLock) { - mFirstTask = null; - mFirstTaskLoaded = false; - } - } - - public void preloadFirstTask() { - Thread bgLoad = new Thread() { - public void run() { - TaskDescription first = loadFirstTask(); - synchronized(mFirstTaskLock) { - if (mCancelPreloadingFirstTask) { - clearFirstTask(); - } else { - mFirstTask = first; - mFirstTaskLoaded = true; - } - mPreloadingFirstTask = false; - } - } - }; - synchronized(mFirstTaskLock) { - if (!mPreloadingFirstTask) { - clearFirstTask(); - mPreloadingFirstTask = true; - bgLoad.start(); - } - } - } - - public void cancelPreloadingFirstTask() { - synchronized(mFirstTaskLock) { - if (mPreloadingFirstTask) { - mCancelPreloadingFirstTask = true; - } else { - clearFirstTask(); - } - } - } - - boolean mPreloadingFirstTask; - boolean mCancelPreloadingFirstTask; - public TaskDescription getFirstTask() { - while(true) { - synchronized(mFirstTaskLock) { - if (mFirstTaskLoaded) { - return mFirstTask; - } else if (!mFirstTaskLoaded && !mPreloadingFirstTask) { - mFirstTask = loadFirstTask(); - mFirstTaskLoaded = true; - return mFirstTask; - } - } - try { - Thread.sleep(3); - } catch (InterruptedException e) { - } - } - } - - public TaskDescription loadFirstTask() { - final ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); - - final List<ActivityManager.RecentTaskInfo> recentTasks = am.getRecentTasksForUser(1, - ActivityManager.RECENT_IGNORE_UNAVAILABLE | ActivityManager.RECENT_INCLUDE_PROFILES, - UserHandle.CURRENT.getIdentifier()); - TaskDescription item = null; - if (recentTasks.size() > 0) { - ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(0); - - Intent intent = new Intent(recentInfo.baseIntent); - if (recentInfo.origActivity != null) { - intent.setComponent(recentInfo.origActivity); - } - - // Don't load the current home activity. - if (isCurrentHomeActivity(intent.getComponent(), null)) { - return null; - } - - // Don't load ourselves - if (intent.getComponent().getPackageName().equals(mContext.getPackageName())) { - return null; - } - - item = createTaskDescription(recentInfo.id, - recentInfo.persistentId, recentInfo.baseIntent, - recentInfo.origActivity, recentInfo.description, - recentInfo.userId); - if (item != null) { - loadThumbnailAndIcon(item); - } - return item; - } - return null; - } - - public void loadTasksInBackground() { - loadTasksInBackground(false); - } - public void loadTasksInBackground(final boolean zeroeth) { - if (mState != State.CANCELLED) { - return; - } - mState = State.LOADING; - mFirstScreenful = true; - - final LinkedBlockingQueue<TaskDescription> tasksWaitingForThumbnails = - new LinkedBlockingQueue<TaskDescription>(); - mTaskLoader = new AsyncTask<Void, ArrayList<TaskDescription>, Void>() { - @Override - protected void onProgressUpdate(ArrayList<TaskDescription>... values) { - if (!isCancelled()) { - ArrayList<TaskDescription> newTasks = values[0]; - // do a callback to RecentsPanelView to let it know we have more values - // how do we let it know we're all done? just always call back twice - if (mRecentsPanel != null) { - mRecentsPanel.onTasksLoaded(newTasks, mFirstScreenful); - } - if (mLoadedTasks == null) { - mLoadedTasks = new ArrayList<TaskDescription>(); - } - mLoadedTasks.addAll(newTasks); - mFirstScreenful = false; - } - } - @Override - protected Void doInBackground(Void... params) { - // We load in two stages: first, we update progress with just the first screenful - // of items. Then, we update with the rest of the items - final int origPri = Process.getThreadPriority(Process.myTid()); - Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - final PackageManager pm = mContext.getPackageManager(); - final ActivityManager am = (ActivityManager) - mContext.getSystemService(Context.ACTIVITY_SERVICE); - - final List<ActivityManager.RecentTaskInfo> recentTasks = - am.getRecentTasks(MAX_TASKS, ActivityManager.RECENT_IGNORE_UNAVAILABLE - | ActivityManager.RECENT_INCLUDE_PROFILES); - int numTasks = recentTasks.size(); - ActivityInfo homeInfo = new Intent(Intent.ACTION_MAIN) - .addCategory(Intent.CATEGORY_HOME).resolveActivityInfo(pm, 0); - - boolean firstScreenful = true; - ArrayList<TaskDescription> tasks = new ArrayList<TaskDescription>(); - - // skip the first task - assume it's either the home screen or the current activity. - final int first = 0; - for (int i = first, index = 0; i < numTasks && (index < MAX_TASKS); ++i) { - if (isCancelled()) { - break; - } - final ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(i); - - Intent intent = new Intent(recentInfo.baseIntent); - if (recentInfo.origActivity != null) { - intent.setComponent(recentInfo.origActivity); - } - - // Don't load the current home activity. - if (isCurrentHomeActivity(intent.getComponent(), homeInfo)) { - continue; - } - - // Don't load ourselves - if (intent.getComponent().getPackageName().equals(mContext.getPackageName())) { - continue; - } - - TaskDescription item = createTaskDescription(recentInfo.id, - recentInfo.persistentId, recentInfo.baseIntent, - recentInfo.origActivity, recentInfo.description, - recentInfo.userId); - - if (item != null) { - while (true) { - try { - tasksWaitingForThumbnails.put(item); - break; - } catch (InterruptedException e) { - } - } - tasks.add(item); - if (firstScreenful && tasks.size() == mNumTasksInFirstScreenful) { - publishProgress(tasks); - tasks = new ArrayList<TaskDescription>(); - firstScreenful = false; - //break; - } - ++index; - } - } - - if (!isCancelled()) { - publishProgress(tasks); - if (firstScreenful) { - // always should publish two updates - publishProgress(new ArrayList<TaskDescription>()); - } - } - - while (true) { - try { - tasksWaitingForThumbnails.put(new TaskDescription()); - break; - } catch (InterruptedException e) { - } - } - - Process.setThreadPriority(origPri); - return null; - } - }; - mTaskLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - loadThumbnailsAndIconsInBackground(tasksWaitingForThumbnails); - } - - private void loadThumbnailsAndIconsInBackground( - final BlockingQueue<TaskDescription> tasksWaitingForThumbnails) { - // continually read items from tasksWaitingForThumbnails and load - // thumbnails and icons for them. finish thread when cancelled or there - // is a null item in tasksWaitingForThumbnails - mThumbnailLoader = new AsyncTask<Void, TaskDescription, Void>() { - @Override - protected void onProgressUpdate(TaskDescription... values) { - if (!isCancelled()) { - TaskDescription td = values[0]; - if (td.isNull()) { // end sentinel - mState = State.LOADED; - } else { - if (mRecentsPanel != null) { - mRecentsPanel.onTaskThumbnailLoaded(td); - } - } - } - } - @Override - protected Void doInBackground(Void... params) { - final int origPri = Process.getThreadPriority(Process.myTid()); - Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - - while (true) { - if (isCancelled()) { - break; - } - TaskDescription td = null; - while (td == null) { - try { - td = tasksWaitingForThumbnails.take(); - } catch (InterruptedException e) { - } - } - if (td.isNull()) { // end sentinel - publishProgress(td); - break; - } - loadThumbnailAndIcon(td); - - publishProgress(td); - } - - Process.setThreadPriority(origPri); - return null; - } - }; - mThumbnailLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recent/Recents.java b/packages/SystemUI/src/com/android/systemui/recent/Recents.java deleted file mode 100644 index e9f3cf9..0000000 --- a/packages/SystemUI/src/com/android/systemui/recent/Recents.java +++ /dev/null @@ -1,326 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.recent; - -import android.app.ActivityOptions; -import android.content.ActivityNotFoundException; -import android.content.Context; -import android.content.Intent; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.os.UserHandle; -import android.util.DisplayMetrics; -import android.util.Log; -import android.view.Display; -import android.view.View; -import com.android.systemui.R; -import com.android.systemui.RecentsComponent; -import com.android.systemui.SystemUI; -import com.android.systemui.recents.AlternateRecentsComponent; - - -public class Recents extends SystemUI implements RecentsComponent { - private static final String TAG = "Recents"; - private static final boolean DEBUG = true; - - // Which recents to use - boolean mUseAlternateRecents = true; - boolean mBootCompleted = false; - static AlternateRecentsComponent sAlternateRecents; - - /** Returns the Recents component, creating a new one in-process if necessary. */ - public static AlternateRecentsComponent getRecentsComponent(Context context, - boolean forceInitialize) { - if (sAlternateRecents == null) { - sAlternateRecents = new AlternateRecentsComponent(context); - if (forceInitialize) { - sAlternateRecents.onStart(); - sAlternateRecents.onBootCompleted(); - } - } - return sAlternateRecents; - } - - @Override - public void start() { - if (mUseAlternateRecents) { - if (sAlternateRecents == null) { - sAlternateRecents = getRecentsComponent(mContext, false); - } - sAlternateRecents.onStart(); - } - - putComponent(RecentsComponent.class, this); - } - - @Override - protected void onBootCompleted() { - if (mUseAlternateRecents) { - if (sAlternateRecents != null) { - sAlternateRecents.onBootCompleted(); - } - } - mBootCompleted = true; - } - - @Override - public void showRecents(boolean triggeredFromAltTab, View statusBarView) { - if (mUseAlternateRecents) { - sAlternateRecents.onShowRecents(triggeredFromAltTab); - } - } - - @Override - public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) { - if (mUseAlternateRecents) { - sAlternateRecents.onHideRecents(triggeredFromAltTab, triggeredFromHomeKey); - } else { - Intent intent = new Intent(RecentsActivity.CLOSE_RECENTS_INTENT); - intent.setPackage("com.android.systemui"); - sendBroadcastSafely(intent); - - RecentTasksLoader.getInstance(mContext).cancelPreloadingFirstTask(); - } - } - - @Override - public void toggleRecents(Display display, int layoutDirection, View statusBarView) { - if (mUseAlternateRecents) { - // Launch the alternate recents if required - sAlternateRecents.onToggleRecents(); - return; - } - - if (DEBUG) Log.d(TAG, "toggle recents panel"); - try { - TaskDescription firstTask = RecentTasksLoader.getInstance(mContext).getFirstTask(); - - Intent intent = new Intent(RecentsActivity.TOGGLE_RECENTS_INTENT); - intent.setClassName("com.android.systemui", - "com.android.systemui.recent.RecentsActivity"); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - - if (firstTask == null) { - if (RecentsActivity.forceOpaqueBackground(mContext)) { - ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, - R.anim.recents_launch_from_launcher_enter, - R.anim.recents_launch_from_launcher_exit); - mContext.startActivityAsUser(intent, opts.toBundle(), new UserHandle( - UserHandle.USER_CURRENT)); - } else { - // The correct window animation will be applied via the activity's style - mContext.startActivityAsUser(intent, new UserHandle( - UserHandle.USER_CURRENT)); - } - - } else { - Bitmap first = null; - if (firstTask.getThumbnail() instanceof BitmapDrawable) { - first = ((BitmapDrawable) firstTask.getThumbnail()).getBitmap(); - } else { - first = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); - Drawable d = RecentTasksLoader.getInstance(mContext).getDefaultThumbnail(); - d.draw(new Canvas(first)); - } - final Resources res = mContext.getResources(); - - float thumbWidth = res - .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_width); - float thumbHeight = res - .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_height); - if (first == null) { - throw new RuntimeException("Recents thumbnail is null"); - } - if (first.getWidth() != thumbWidth || first.getHeight() != thumbHeight) { - first = Bitmap.createScaledBitmap(first, (int) thumbWidth, (int) thumbHeight, - true); - if (first == null) { - throw new RuntimeException("Recents thumbnail is null"); - } - } - - - DisplayMetrics dm = new DisplayMetrics(); - display.getMetrics(dm); - // calculate it here, but consider moving it elsewhere - // first, determine which orientation you're in. - final Configuration config = res.getConfiguration(); - int x, y; - - if (config.orientation == Configuration.ORIENTATION_PORTRAIT) { - float appLabelLeftMargin = res.getDimensionPixelSize( - R.dimen.status_bar_recents_app_label_left_margin); - float appLabelWidth = res.getDimensionPixelSize( - R.dimen.status_bar_recents_app_label_width); - float thumbLeftMargin = res.getDimensionPixelSize( - R.dimen.status_bar_recents_thumbnail_left_margin); - float thumbBgPadding = res.getDimensionPixelSize( - R.dimen.status_bar_recents_thumbnail_bg_padding); - - float width = appLabelLeftMargin + - +appLabelWidth - + thumbLeftMargin - + thumbWidth - + 2 * thumbBgPadding; - - x = (int) ((dm.widthPixels - width) / 2f + appLabelLeftMargin + appLabelWidth - + thumbBgPadding + thumbLeftMargin); - y = (int) (dm.heightPixels - - res.getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_height) - - thumbBgPadding); - if (layoutDirection == View.LAYOUT_DIRECTION_RTL) { - x = dm.widthPixels - x - res.getDimensionPixelSize( - R.dimen.status_bar_recents_thumbnail_width); - } - - } else { // if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) { - float thumbTopMargin = res.getDimensionPixelSize( - R.dimen.status_bar_recents_thumbnail_top_margin); - float thumbBgPadding = res.getDimensionPixelSize( - R.dimen.status_bar_recents_thumbnail_bg_padding); - float textPadding = res.getDimensionPixelSize( - R.dimen.status_bar_recents_text_description_padding); - float labelTextSize = res.getDimensionPixelSize( - R.dimen.status_bar_recents_app_label_text_size); - Paint p = new Paint(); - p.setTextSize(labelTextSize); - float labelTextHeight = p.getFontMetricsInt().bottom - - p.getFontMetricsInt().top; - float descriptionTextSize = res.getDimensionPixelSize( - R.dimen.status_bar_recents_app_description_text_size); - p.setTextSize(descriptionTextSize); - float descriptionTextHeight = p.getFontMetricsInt().bottom - - p.getFontMetricsInt().top; - - float statusBarHeight = res.getDimensionPixelSize( - com.android.internal.R.dimen.status_bar_height); - float recentsItemTopPadding = statusBarHeight; - - float height = thumbTopMargin - + thumbHeight - + 2 * thumbBgPadding + textPadding + labelTextHeight - + recentsItemTopPadding + textPadding + descriptionTextHeight; - float recentsItemRightPadding = res - .getDimensionPixelSize(R.dimen.status_bar_recents_item_padding); - float recentsScrollViewRightPadding = res - .getDimensionPixelSize(R.dimen.status_bar_recents_right_glow_margin); - x = (int) (dm.widthPixels - res - .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_width) - - thumbBgPadding - recentsItemRightPadding - - recentsScrollViewRightPadding); - y = (int) ((dm.heightPixels - statusBarHeight - height) / 2f + thumbTopMargin - + recentsItemTopPadding + thumbBgPadding + statusBarHeight); - } - - ActivityOptions opts = ActivityOptions.makeThumbnailScaleDownAnimation( - statusBarView, - first, x, y, - new ActivityOptions.OnAnimationStartedListener() { - public void onAnimationStarted() { - Intent intent = - new Intent(RecentsActivity.WINDOW_ANIMATION_START_INTENT); - intent.setPackage("com.android.systemui"); - sendBroadcastSafely(intent); - } - }); - intent.putExtra(RecentsActivity.WAITING_FOR_WINDOW_ANIMATION_PARAM, true); - startActivitySafely(intent, opts.toBundle()); - } - } catch (ActivityNotFoundException e) { - Log.e(TAG, "Failed to launch RecentAppsIntent", e); - } - } - - @Override - protected void onConfigurationChanged(Configuration newConfig) { - if (mUseAlternateRecents) { - sAlternateRecents.onConfigurationChanged(newConfig); - } - } - - @Override - public void preloadRecents() { - if (mUseAlternateRecents) { - sAlternateRecents.onPreloadRecents(); - } else { - Intent intent = new Intent(RecentsActivity.PRELOAD_INTENT); - intent.setClassName("com.android.systemui", - "com.android.systemui.recent.RecentsPreloadReceiver"); - sendBroadcastSafely(intent); - - RecentTasksLoader.getInstance(mContext).preloadFirstTask(); - } - } - - @Override - public void cancelPreloadingRecents() { - if (mUseAlternateRecents) { - sAlternateRecents.onCancelPreloadingRecents(); - } else { - Intent intent = new Intent(RecentsActivity.CANCEL_PRELOAD_INTENT); - intent.setClassName("com.android.systemui", - "com.android.systemui.recent.RecentsPreloadReceiver"); - sendBroadcastSafely(intent); - - RecentTasksLoader.getInstance(mContext).cancelPreloadingFirstTask(); - } - } - - @Override - public void showNextAffiliatedTask() { - if (mUseAlternateRecents) { - sAlternateRecents.onShowNextAffiliatedTask(); - } - } - - @Override - public void showPrevAffiliatedTask() { - if (mUseAlternateRecents) { - sAlternateRecents.onShowPrevAffiliatedTask(); - } - } - - @Override - public void setCallback(Callbacks cb) { - if (mUseAlternateRecents) { - sAlternateRecents.setRecentsComponentCallback(cb); - } - } - - /** - * Send broadcast only if BOOT_COMPLETED - */ - private void sendBroadcastSafely(Intent intent) { - if (!mBootCompleted) return; - mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT)); - } - - /** - * Start activity only if BOOT_COMPLETED - */ - private void startActivitySafely(Intent intent, Bundle opts) { - if (!mBootCompleted) return; - mContext.startActivityAsUser(intent, opts, new UserHandle(UserHandle.USER_CURRENT)); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsActivity.java deleted file mode 100644 index 7ab40b0..0000000 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentsActivity.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.recent; - -import android.app.Activity; -import android.app.ActivityManager; -import android.app.WallpaperManager; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.Bundle; -import android.os.UserHandle; -import android.view.MotionEvent; -import android.view.View; -import android.view.WindowManager; - -import com.android.systemui.R; -import com.android.systemui.statusbar.StatusBarPanel; - -import java.util.List; - -public class RecentsActivity extends Activity { - public static final String TOGGLE_RECENTS_INTENT = "com.android.systemui.recent.action.TOGGLE_RECENTS"; - public static final String PRELOAD_INTENT = "com.android.systemui.recent.action.PRELOAD"; - public static final String CANCEL_PRELOAD_INTENT = "com.android.systemui.recent.CANCEL_PRELOAD"; - public static final String CLOSE_RECENTS_INTENT = "com.android.systemui.recent.action.CLOSE"; - public static final String WINDOW_ANIMATION_START_INTENT = "com.android.systemui.recent.action.WINDOW_ANIMATION_START"; - public static final String PRELOAD_PERMISSION = "com.android.systemui.recent.permission.PRELOAD"; - public static final String WAITING_FOR_WINDOW_ANIMATION_PARAM = "com.android.systemui.recent.WAITING_FOR_WINDOW_ANIMATION"; - private static final String WAS_SHOWING = "was_showing"; - - private RecentsPanelView mRecentsPanel; - private IntentFilter mIntentFilter; - private boolean mShowing; - private boolean mForeground; - - private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (CLOSE_RECENTS_INTENT.equals(intent.getAction())) { - if (mRecentsPanel != null && mRecentsPanel.isShowing()) { - if (mShowing && !mForeground) { - // Captures the case right before we transition to another activity - mRecentsPanel.show(false); - } - } - } else if (WINDOW_ANIMATION_START_INTENT.equals(intent.getAction())) { - if (mRecentsPanel != null) { - mRecentsPanel.onWindowAnimationStart(); - } - } - } - }; - - public class TouchOutsideListener implements View.OnTouchListener { - private StatusBarPanel mPanel; - - public TouchOutsideListener(StatusBarPanel panel) { - mPanel = panel; - } - - public boolean onTouch(View v, MotionEvent ev) { - final int action = ev.getAction(); - if (action == MotionEvent.ACTION_OUTSIDE - || (action == MotionEvent.ACTION_DOWN - && !mPanel.isInContentArea((int) ev.getX(), (int) ev.getY()))) { - dismissAndGoHome(); - return true; - } - return false; - } - } - - @Override - public void onPause() { - overridePendingTransition( - R.anim.recents_return_to_launcher_enter, - R.anim.recents_return_to_launcher_exit); - mForeground = false; - super.onPause(); - } - - @Override - public void onStop() { - mShowing = false; - if (mRecentsPanel != null) { - mRecentsPanel.onUiHidden(); - } - super.onStop(); - } - - private void updateWallpaperVisibility(boolean visible) { - int wpflags = visible ? WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER : 0; - int curflags = getWindow().getAttributes().flags - & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; - if (wpflags != curflags) { - getWindow().setFlags(wpflags, WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER); - } - } - - public static boolean forceOpaqueBackground(Context context) { - return WallpaperManager.getInstance(context).getWallpaperInfo() != null; - } - - @Override - public void onStart() { - // Hide wallpaper if it's not a static image - if (forceOpaqueBackground(this)) { - updateWallpaperVisibility(false); - } else { - updateWallpaperVisibility(true); - } - mShowing = true; - if (mRecentsPanel != null) { - // Call and refresh the recent tasks list in case we didn't preload tasks - // or in case we don't get an onNewIntent - mRecentsPanel.refreshRecentTasksList(); - mRecentsPanel.refreshViews(); - } - super.onStart(); - } - - @Override - public void onResume() { - mForeground = true; - super.onResume(); - } - - @Override - public void onBackPressed() { - dismissAndGoBack(); - } - - public void dismissAndGoHome() { - if (mRecentsPanel != null) { - Intent homeIntent = new Intent(Intent.ACTION_MAIN, null); - homeIntent.addCategory(Intent.CATEGORY_HOME); - homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); - startActivityAsUser(homeIntent, new UserHandle(UserHandle.USER_CURRENT)); - mRecentsPanel.show(false); - } - } - - public void dismissAndGoBack() { - if (mRecentsPanel != null) { - final ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); - - final List<ActivityManager.RecentTaskInfo> recentTasks = - am.getRecentTasks(2, - ActivityManager.RECENT_WITH_EXCLUDED | - ActivityManager.RECENT_IGNORE_UNAVAILABLE | - ActivityManager.RECENT_INCLUDE_PROFILES); - if (recentTasks.size() > 1 && - mRecentsPanel.simulateClick(recentTasks.get(1).persistentId)) { - // recents panel will take care of calling show(false) through simulateClick - return; - } - mRecentsPanel.show(false); - } - finish(); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - getWindow().addPrivateFlags( - WindowManager.LayoutParams.PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR); - setContentView(R.layout.status_bar_recent_panel); - mRecentsPanel = (RecentsPanelView) findViewById(R.id.recents_root); - mRecentsPanel.setOnTouchListener(new TouchOutsideListener(mRecentsPanel)); - mRecentsPanel.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); - - final RecentTasksLoader recentTasksLoader = RecentTasksLoader.getInstance(this); - recentTasksLoader.setRecentsPanel(mRecentsPanel, mRecentsPanel); - mRecentsPanel.setMinSwipeAlpha( - getResources().getInteger(R.integer.config_recent_item_min_alpha) / 100f); - - if (savedInstanceState == null || - savedInstanceState.getBoolean(WAS_SHOWING)) { - handleIntent(getIntent(), (savedInstanceState == null)); - } - mIntentFilter = new IntentFilter(); - mIntentFilter.addAction(CLOSE_RECENTS_INTENT); - mIntentFilter.addAction(WINDOW_ANIMATION_START_INTENT); - registerReceiver(mIntentReceiver, mIntentFilter); - super.onCreate(savedInstanceState); - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - outState.putBoolean(WAS_SHOWING, mRecentsPanel.isShowing()); - } - - @Override - protected void onDestroy() { - RecentTasksLoader.getInstance(this).setRecentsPanel(null, mRecentsPanel); - unregisterReceiver(mIntentReceiver); - super.onDestroy(); - } - - @Override - protected void onNewIntent(Intent intent) { - handleIntent(intent, true); - } - - private void handleIntent(Intent intent, boolean checkWaitingForAnimationParam) { - super.onNewIntent(intent); - - if (TOGGLE_RECENTS_INTENT.equals(intent.getAction())) { - if (mRecentsPanel != null) { - if (mRecentsPanel.isShowing()) { - dismissAndGoBack(); - } else { - final RecentTasksLoader recentTasksLoader = RecentTasksLoader.getInstance(this); - boolean waitingForWindowAnimation = checkWaitingForAnimationParam && - intent.getBooleanExtra(WAITING_FOR_WINDOW_ANIMATION_PARAM, false); - mRecentsPanel.show(true, recentTasksLoader.getLoadedTasks(), - recentTasksLoader.isFirstScreenful(), waitingForWindowAnimation); - } - } - } - } - - boolean isForeground() { - return mForeground; - } - - boolean isActivityShowing() { - return mShowing; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsCallback.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsCallback.java deleted file mode 100644 index deb5670..0000000 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentsCallback.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.recent; - -import android.view.View; - -public interface RecentsCallback { - static final int SWIPE_LEFT = 0; - static final int SWIPE_RIGHT = 1; - static final int SWIPE_UP = 2; - static final int SWIPE_DOWN = 3; - - void handleOnClick(View selectedView); - void handleSwipe(View selectedView); - void handleLongPress(View selectedView, View anchorView, View thumbnailView); - void dismiss(); -} diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java deleted file mode 100644 index cf5d3a6..0000000 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java +++ /dev/null @@ -1,391 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.recent; - -import android.animation.LayoutTransition; -import android.content.Context; -import android.content.res.Configuration; -import android.database.DataSetObserver; -import android.graphics.Canvas; -import android.util.AttributeSet; -import android.util.DisplayMetrics; -import android.util.Log; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.ViewTreeObserver; -import android.view.ViewTreeObserver.OnGlobalLayoutListener; -import android.widget.HorizontalScrollView; -import android.widget.LinearLayout; - -import com.android.systemui.R; -import com.android.systemui.SwipeHelper; -import com.android.systemui.recent.RecentsPanelView.TaskDescriptionAdapter; - -import java.util.HashSet; -import java.util.Iterator; - -public class RecentsHorizontalScrollView extends HorizontalScrollView - implements SwipeHelper.Callback, RecentsPanelView.RecentsScrollView { - private static final String TAG = RecentsPanelView.TAG; - private static final boolean DEBUG = RecentsPanelView.DEBUG; - private LinearLayout mLinearLayout; - private TaskDescriptionAdapter mAdapter; - private RecentsCallback mCallback; - protected int mLastScrollPosition; - private SwipeHelper mSwipeHelper; - private FadedEdgeDrawHelper mFadedEdgeDrawHelper; - private HashSet<View> mRecycledViews; - private int mNumItemsInOneScreenful; - private Runnable mOnScrollListener; - - public RecentsHorizontalScrollView(Context context, AttributeSet attrs) { - super(context, attrs, 0); - mSwipeHelper = new SwipeHelper(SwipeHelper.Y, this, context); - mFadedEdgeDrawHelper = FadedEdgeDrawHelper.create(context, attrs, this, false); - mRecycledViews = new HashSet<View>(); - } - - public void setMinSwipeAlpha(float minAlpha) { - mSwipeHelper.setMinSwipeProgress(minAlpha); - } - - private int scrollPositionOfMostRecent() { - return mLinearLayout.getWidth() - getWidth(); - } - - private void addToRecycledViews(View v) { - if (mRecycledViews.size() < mNumItemsInOneScreenful) { - mRecycledViews.add(v); - } - } - - public View findViewForTask(int persistentTaskId) { - for (int i = 0; i < mLinearLayout.getChildCount(); i++) { - View v = mLinearLayout.getChildAt(i); - RecentsPanelView.ViewHolder holder = (RecentsPanelView.ViewHolder) v.getTag(); - if (holder.taskDescription.persistentTaskId == persistentTaskId) { - return v; - } - } - return null; - } - - private void update() { - for (int i = 0; i < mLinearLayout.getChildCount(); i++) { - View v = mLinearLayout.getChildAt(i); - addToRecycledViews(v); - mAdapter.recycleView(v); - } - LayoutTransition transitioner = getLayoutTransition(); - setLayoutTransition(null); - - mLinearLayout.removeAllViews(); - Iterator<View> recycledViews = mRecycledViews.iterator(); - for (int i = 0; i < mAdapter.getCount(); i++) { - View old = null; - if (recycledViews.hasNext()) { - old = recycledViews.next(); - recycledViews.remove(); - old.setVisibility(VISIBLE); - } - - final View view = mAdapter.getView(i, old, mLinearLayout); - - if (mFadedEdgeDrawHelper != null) { - mFadedEdgeDrawHelper.addViewCallback(view); - } - - OnTouchListener noOpListener = new OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - return true; - } - }; - - view.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - mCallback.dismiss(); - } - }); - // We don't want a click sound when we dimiss recents - view.setSoundEffectsEnabled(false); - - OnClickListener launchAppListener = new OnClickListener() { - public void onClick(View v) { - mCallback.handleOnClick(view); - } - }; - - RecentsPanelView.ViewHolder holder = (RecentsPanelView.ViewHolder) view.getTag(); - final View thumbnailView = holder.thumbnailView; - OnLongClickListener longClickListener = new OnLongClickListener() { - public boolean onLongClick(View v) { - final View anchorView = view.findViewById(R.id.app_description); - mCallback.handleLongPress(view, anchorView, thumbnailView); - return true; - } - }; - thumbnailView.setClickable(true); - thumbnailView.setOnClickListener(launchAppListener); - thumbnailView.setOnLongClickListener(longClickListener); - - // We don't want to dismiss recents if a user clicks on the app title - // (we also don't want to launch the app either, though, because the - // app title is a small target and doesn't have great click feedback) - final View appTitle = view.findViewById(R.id.app_label); - appTitle.setContentDescription(" "); - appTitle.setOnTouchListener(noOpListener); - mLinearLayout.addView(view); - } - setLayoutTransition(transitioner); - - // Scroll to end after initial layout. - - final OnGlobalLayoutListener updateScroll = new OnGlobalLayoutListener() { - public void onGlobalLayout() { - mLastScrollPosition = scrollPositionOfMostRecent(); - scrollTo(mLastScrollPosition, 0); - final ViewTreeObserver observer = getViewTreeObserver(); - if (observer.isAlive()) { - observer.removeOnGlobalLayoutListener(this); - } - } - }; - getViewTreeObserver().addOnGlobalLayoutListener(updateScroll); - } - - @Override - public void removeViewInLayout(final View view) { - dismissChild(view); - } - - public boolean onInterceptTouchEvent(MotionEvent ev) { - if (DEBUG) Log.v(TAG, "onInterceptTouchEvent()"); - return mSwipeHelper.onInterceptTouchEvent(ev) || - super.onInterceptTouchEvent(ev); - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - return mSwipeHelper.onTouchEvent(ev) || - super.onTouchEvent(ev); - } - - public boolean canChildBeDismissed(View v) { - return true; - } - - @Override - public boolean isAntiFalsingNeeded() { - return false; - } - - @Override - public float getFalsingThresholdFactor() { - return 1.0f; - } - - public void dismissChild(View v) { - mSwipeHelper.dismissChild(v, 0); - } - - public void onChildDismissed(View v) { - addToRecycledViews(v); - mLinearLayout.removeView(v); - mCallback.handleSwipe(v); - // Restore the alpha/translation parameters to what they were before swiping - // (for when these items are recycled) - View contentView = getChildContentView(v); - contentView.setAlpha(1f); - contentView.setTranslationY(0); - } - - public void onBeginDrag(View v) { - // We do this so the underlying ScrollView knows that it won't get - // the chance to intercept events anymore - requestDisallowInterceptTouchEvent(true); - } - - public void onDragCancelled(View v) { - } - - @Override - public void onChildSnappedBack(View animView) { - } - - @Override - public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) { - return false; - } - - public View getChildAtPosition(MotionEvent ev) { - final float x = ev.getX() + getScrollX(); - final float y = ev.getY() + getScrollY(); - for (int i = 0; i < mLinearLayout.getChildCount(); i++) { - View item = mLinearLayout.getChildAt(i); - if (x >= item.getLeft() && x < item.getRight() - && y >= item.getTop() && y < item.getBottom()) { - return item; - } - } - return null; - } - - public View getChildContentView(View v) { - return v.findViewById(R.id.recent_item); - } - - @Override - public void drawFadedEdges(Canvas canvas, int left, int right, int top, int bottom) { - if (mFadedEdgeDrawHelper != null) { - - mFadedEdgeDrawHelper.drawCallback(canvas, - left, right, top, bottom, getScrollX(), getScrollY(), - 0, 0, - getLeftFadingEdgeStrength(), getRightFadingEdgeStrength(), getPaddingTop()); - } - } - - @Override - protected void onScrollChanged(int l, int t, int oldl, int oldt) { - super.onScrollChanged(l, t, oldl, oldt); - if (mOnScrollListener != null) { - mOnScrollListener.run(); - } - } - - public void setOnScrollListener(Runnable listener) { - mOnScrollListener = listener; - } - - @Override - public int getVerticalFadingEdgeLength() { - if (mFadedEdgeDrawHelper != null) { - return mFadedEdgeDrawHelper.getVerticalFadingEdgeLength(); - } else { - return super.getVerticalFadingEdgeLength(); - } - } - - @Override - public int getHorizontalFadingEdgeLength() { - if (mFadedEdgeDrawHelper != null) { - return mFadedEdgeDrawHelper.getHorizontalFadingEdgeLength(); - } else { - return super.getHorizontalFadingEdgeLength(); - } - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - setScrollbarFadingEnabled(true); - mLinearLayout = (LinearLayout) findViewById(R.id.recents_linear_layout); - final int leftPadding = getContext().getResources() - .getDimensionPixelOffset(R.dimen.status_bar_recents_thumbnail_left_margin); - setOverScrollEffectPadding(leftPadding, 0); - } - - @Override - public void onAttachedToWindow() { - if (mFadedEdgeDrawHelper != null) { - mFadedEdgeDrawHelper.onAttachedToWindowCallback(mLinearLayout, isHardwareAccelerated()); - } - } - - @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - float densityScale = getResources().getDisplayMetrics().density; - mSwipeHelper.setDensityScale(densityScale); - float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop(); - mSwipeHelper.setPagingTouchSlop(pagingTouchSlop); - } - - private void setOverScrollEffectPadding(int leftPadding, int i) { - // TODO Add to (Vertical)ScrollView - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - - // Skip this work if a transition is running; it sets the scroll values independently - // and should not have those animated values clobbered by this logic - LayoutTransition transition = mLinearLayout.getLayoutTransition(); - if (transition != null && transition.isRunning()) { - return; - } - // Keep track of the last visible item in the list so we can restore it - // to the bottom when the orientation changes. - mLastScrollPosition = scrollPositionOfMostRecent(); - - // This has to happen post-layout, so run it "in the future" - post(new Runnable() { - public void run() { - // Make sure we're still not clobbering the transition-set values, since this - // runnable launches asynchronously - LayoutTransition transition = mLinearLayout.getLayoutTransition(); - if (transition == null || !transition.isRunning()) { - scrollTo(mLastScrollPosition, 0); - } - } - }); - } - - public void setAdapter(TaskDescriptionAdapter adapter) { - mAdapter = adapter; - mAdapter.registerDataSetObserver(new DataSetObserver() { - public void onChanged() { - update(); - } - - public void onInvalidated() { - update(); - } - }); - DisplayMetrics dm = getResources().getDisplayMetrics(); - int childWidthMeasureSpec = - MeasureSpec.makeMeasureSpec(dm.widthPixels, MeasureSpec.AT_MOST); - int childheightMeasureSpec = - MeasureSpec.makeMeasureSpec(dm.heightPixels, MeasureSpec.AT_MOST); - View child = mAdapter.createView(mLinearLayout); - child.measure(childWidthMeasureSpec, childheightMeasureSpec); - mNumItemsInOneScreenful = - (int) Math.ceil(dm.widthPixels / (double) child.getMeasuredWidth()); - addToRecycledViews(child); - - for (int i = 0; i < mNumItemsInOneScreenful - 1; i++) { - addToRecycledViews(mAdapter.createView(mLinearLayout)); - } - } - - public int numItemsInOneScreenful() { - return mNumItemsInOneScreenful; - } - - @Override - public void setLayoutTransition(LayoutTransition transition) { - // The layout transition applies to our embedded LinearLayout - mLinearLayout.setLayoutTransition(transition); - } - - public void setCallback(RecentsCallback callback) { - mCallback = callback; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java deleted file mode 100644 index 4c3460e..0000000 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java +++ /dev/null @@ -1,813 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.recent; - -import android.animation.Animator; -import android.animation.LayoutTransition; -import android.animation.TimeInterpolator; -import android.app.ActivityManager; -import android.app.ActivityManagerNative; -import android.app.ActivityOptions; -import android.app.TaskStackBuilder; -import android.content.ActivityNotFoundException; -import android.content.Context; -import android.content.Intent; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Matrix; -import android.graphics.Shader.TileMode; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.Bundle; -import android.os.RemoteException; -import android.os.UserHandle; -import android.provider.Settings; -import android.util.AttributeSet; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewPropertyAnimator; -import android.view.ViewRootImpl; -import android.view.accessibility.AccessibilityEvent; -import android.view.animation.AnimationUtils; -import android.view.animation.DecelerateInterpolator; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.BaseAdapter; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.ImageView.ScaleType; -import android.widget.PopupMenu; -import android.widget.TextView; - -import com.android.systemui.R; -import com.android.systemui.statusbar.BaseStatusBar; -import com.android.systemui.statusbar.StatusBarPanel; -import com.android.systemui.statusbar.phone.PhoneStatusBar; - -import java.util.ArrayList; - -public class RecentsPanelView extends FrameLayout implements OnItemClickListener, RecentsCallback, - StatusBarPanel, Animator.AnimatorListener { - static final String TAG = "RecentsPanelView"; - static final boolean DEBUG = PhoneStatusBar.DEBUG || false; - private PopupMenu mPopup; - private View mRecentsScrim; - private View mRecentsNoApps; - private RecentsScrollView mRecentsContainer; - - private boolean mShowing; - private boolean mWaitingToShow; - private ViewHolder mItemToAnimateInWhenWindowAnimationIsFinished; - private boolean mAnimateIconOfFirstTask; - private boolean mWaitingForWindowAnimation; - private long mWindowAnimationStartTime; - private boolean mCallUiHiddenBeforeNextReload; - - private RecentTasksLoader mRecentTasksLoader; - private ArrayList<TaskDescription> mRecentTaskDescriptions; - private TaskDescriptionAdapter mListAdapter; - private int mThumbnailWidth; - private boolean mFitThumbnailToXY; - private int mRecentItemLayoutId; - private boolean mHighEndGfx; - - public static interface RecentsScrollView { - public int numItemsInOneScreenful(); - public void setAdapter(TaskDescriptionAdapter adapter); - public void setCallback(RecentsCallback callback); - public void setMinSwipeAlpha(float minAlpha); - public View findViewForTask(int persistentTaskId); - public void drawFadedEdges(Canvas c, int left, int right, int top, int bottom); - public void setOnScrollListener(Runnable listener); - } - - private final class OnLongClickDelegate implements View.OnLongClickListener { - View mOtherView; - OnLongClickDelegate(View other) { - mOtherView = other; - } - public boolean onLongClick(View v) { - return mOtherView.performLongClick(); - } - } - - /* package */ final static class ViewHolder { - View thumbnailView; - ImageView thumbnailViewImage; - Drawable thumbnailViewDrawable; - ImageView iconView; - TextView labelView; - TextView descriptionView; - View calloutLine; - TaskDescription taskDescription; - boolean loadedThumbnailAndIcon; - } - - /* package */ final class TaskDescriptionAdapter extends BaseAdapter { - private LayoutInflater mInflater; - - public TaskDescriptionAdapter(Context context) { - mInflater = LayoutInflater.from(context); - } - - public int getCount() { - return mRecentTaskDescriptions != null ? mRecentTaskDescriptions.size() : 0; - } - - public Object getItem(int position) { - return position; // we only need the index - } - - public long getItemId(int position) { - return position; // we just need something unique for this position - } - - public View createView(ViewGroup parent) { - View convertView = mInflater.inflate(mRecentItemLayoutId, parent, false); - ViewHolder holder = new ViewHolder(); - holder.thumbnailView = convertView.findViewById(R.id.app_thumbnail); - holder.thumbnailViewImage = - (ImageView) convertView.findViewById(R.id.app_thumbnail_image); - // If we set the default thumbnail now, we avoid an onLayout when we update - // the thumbnail later (if they both have the same dimensions) - updateThumbnail(holder, mRecentTasksLoader.getDefaultThumbnail(), false, false); - holder.iconView = (ImageView) convertView.findViewById(R.id.app_icon); - holder.iconView.setImageDrawable(mRecentTasksLoader.getDefaultIcon()); - holder.labelView = (TextView) convertView.findViewById(R.id.app_label); - holder.calloutLine = convertView.findViewById(R.id.recents_callout_line); - holder.descriptionView = (TextView) convertView.findViewById(R.id.app_description); - - convertView.setTag(holder); - return convertView; - } - - public View getView(int position, View convertView, ViewGroup parent) { - if (convertView == null) { - convertView = createView(parent); - } - final ViewHolder holder = (ViewHolder) convertView.getTag(); - - // index is reverse since most recent appears at the bottom... - final int index = mRecentTaskDescriptions.size() - position - 1; - - final TaskDescription td = mRecentTaskDescriptions.get(index); - - holder.labelView.setText(td.getLabel()); - holder.thumbnailView.setContentDescription(td.getLabel()); - holder.loadedThumbnailAndIcon = td.isLoaded(); - if (td.isLoaded()) { - updateThumbnail(holder, td.getThumbnail(), true, false); - updateIcon(holder, td.getIcon(), true, false); - } - if (index == 0) { - if (mAnimateIconOfFirstTask) { - ViewHolder oldHolder = mItemToAnimateInWhenWindowAnimationIsFinished; - if (oldHolder != null) { - oldHolder.iconView.setAlpha(1f); - oldHolder.iconView.setTranslationX(0f); - oldHolder.iconView.setTranslationY(0f); - oldHolder.labelView.setAlpha(1f); - oldHolder.labelView.setTranslationX(0f); - oldHolder.labelView.setTranslationY(0f); - if (oldHolder.calloutLine != null) { - oldHolder.calloutLine.setAlpha(1f); - oldHolder.calloutLine.setTranslationX(0f); - oldHolder.calloutLine.setTranslationY(0f); - } - } - mItemToAnimateInWhenWindowAnimationIsFinished = holder; - int translation = -getResources().getDimensionPixelSize( - R.dimen.status_bar_recents_app_icon_translate_distance); - final Configuration config = getResources().getConfiguration(); - if (config.orientation == Configuration.ORIENTATION_PORTRAIT) { - if (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { - translation = -translation; - } - holder.iconView.setAlpha(0f); - holder.iconView.setTranslationX(translation); - holder.labelView.setAlpha(0f); - holder.labelView.setTranslationX(translation); - holder.calloutLine.setAlpha(0f); - holder.calloutLine.setTranslationX(translation); - } else { - holder.iconView.setAlpha(0f); - holder.iconView.setTranslationY(translation); - } - if (!mWaitingForWindowAnimation) { - animateInIconOfFirstTask(); - } - } - } - - holder.thumbnailView.setTag(td); - holder.thumbnailView.setOnLongClickListener(new OnLongClickDelegate(convertView)); - holder.taskDescription = td; - return convertView; - } - - public void recycleView(View v) { - ViewHolder holder = (ViewHolder) v.getTag(); - updateThumbnail(holder, mRecentTasksLoader.getDefaultThumbnail(), false, false); - holder.iconView.setImageDrawable(mRecentTasksLoader.getDefaultIcon()); - holder.iconView.setVisibility(INVISIBLE); - holder.iconView.animate().cancel(); - holder.labelView.setText(null); - holder.labelView.animate().cancel(); - holder.thumbnailView.setContentDescription(null); - holder.thumbnailView.setTag(null); - holder.thumbnailView.setOnLongClickListener(null); - holder.thumbnailView.setVisibility(INVISIBLE); - holder.iconView.setAlpha(1f); - holder.iconView.setTranslationX(0f); - holder.iconView.setTranslationY(0f); - holder.labelView.setAlpha(1f); - holder.labelView.setTranslationX(0f); - holder.labelView.setTranslationY(0f); - if (holder.calloutLine != null) { - holder.calloutLine.setAlpha(1f); - holder.calloutLine.setTranslationX(0f); - holder.calloutLine.setTranslationY(0f); - holder.calloutLine.animate().cancel(); - } - holder.taskDescription = null; - holder.loadedThumbnailAndIcon = false; - } - } - - public RecentsPanelView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public RecentsPanelView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - updateValuesFromResources(); - - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecentsPanelView, - defStyle, 0); - - mRecentItemLayoutId = a.getResourceId(R.styleable.RecentsPanelView_recentItemLayout, 0); - mRecentTasksLoader = RecentTasksLoader.getInstance(context); - a.recycle(); - } - - public int numItemsInOneScreenful() { - return mRecentsContainer.numItemsInOneScreenful(); - } - - private boolean pointInside(int x, int y, View v) { - final int l = v.getLeft(); - final int r = v.getRight(); - final int t = v.getTop(); - final int b = v.getBottom(); - return x >= l && x < r && y >= t && y < b; - } - - public boolean isInContentArea(int x, int y) { - return pointInside(x, y, (View) mRecentsContainer); - } - - public void show(boolean show) { - show(show, null, false, false); - } - - public void show(boolean show, ArrayList<TaskDescription> recentTaskDescriptions, - boolean firstScreenful, boolean animateIconOfFirstTask) { - if (show && mCallUiHiddenBeforeNextReload) { - onUiHidden(); - recentTaskDescriptions = null; - mAnimateIconOfFirstTask = false; - mWaitingForWindowAnimation = false; - } else { - mAnimateIconOfFirstTask = animateIconOfFirstTask; - mWaitingForWindowAnimation = animateIconOfFirstTask; - } - if (show) { - mWaitingToShow = true; - refreshRecentTasksList(recentTaskDescriptions, firstScreenful); - showIfReady(); - } else { - showImpl(false); - } - } - - private void showIfReady() { - // mWaitingToShow => there was a touch up on the recents button - // mRecentTaskDescriptions != null => we've created views for the first screenful of items - if (mWaitingToShow && mRecentTaskDescriptions != null) { - showImpl(true); - } - } - - static void sendCloseSystemWindows(Context context, String reason) { - if (ActivityManagerNative.isSystemReady()) { - try { - ActivityManagerNative.getDefault().closeSystemDialogs(reason); - } catch (RemoteException e) { - } - } - } - - private void showImpl(boolean show) { - sendCloseSystemWindows(getContext(), BaseStatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS); - - mShowing = show; - - if (show) { - // if there are no apps, bring up a "No recent apps" message - boolean noApps = mRecentTaskDescriptions != null - && (mRecentTaskDescriptions.size() == 0); - mRecentsNoApps.setAlpha(1f); - mRecentsNoApps.setVisibility(noApps ? View.VISIBLE : View.INVISIBLE); - - onAnimationEnd(null); - setFocusable(true); - setFocusableInTouchMode(true); - requestFocus(); - } else { - mWaitingToShow = false; - // call onAnimationEnd() and clearRecentTasksList() in onUiHidden() - mCallUiHiddenBeforeNextReload = true; - if (mPopup != null) { - mPopup.dismiss(); - } - } - } - - protected void onAttachedToWindow () { - super.onAttachedToWindow(); - final ViewRootImpl root = getViewRootImpl(); - if (root != null) { - root.setDrawDuringWindowsAnimating(true); - } - } - - public void onUiHidden() { - mCallUiHiddenBeforeNextReload = false; - if (!mShowing && mRecentTaskDescriptions != null) { - onAnimationEnd(null); - clearRecentTasksList(); - } - } - - public void dismiss() { - ((RecentsActivity) getContext()).dismissAndGoHome(); - } - - public void dismissAndGoBack() { - ((RecentsActivity) getContext()).dismissAndGoBack(); - } - - public void onAnimationCancel(Animator animation) { - } - - public void onAnimationEnd(Animator animation) { - if (mShowing) { - final LayoutTransition transitioner = new LayoutTransition(); - ((ViewGroup)mRecentsContainer).setLayoutTransition(transitioner); - createCustomAnimations(transitioner); - } else { - ((ViewGroup)mRecentsContainer).setLayoutTransition(null); - } - } - - public void onAnimationRepeat(Animator animation) { - } - - public void onAnimationStart(Animator animation) { - } - - @Override - public boolean dispatchHoverEvent(MotionEvent event) { - // Ignore hover events outside of this panel bounds since such events - // generate spurious accessibility events with the panel content when - // tapping outside of it, thus confusing the user. - final int x = (int) event.getX(); - final int y = (int) event.getY(); - if (x >= 0 && x < getWidth() && y >= 0 && y < getHeight()) { - return super.dispatchHoverEvent(event); - } - return true; - } - - /** - * Whether the panel is showing, or, if it's animating, whether it will be - * when the animation is done. - */ - public boolean isShowing() { - return mShowing; - } - - public void setRecentTasksLoader(RecentTasksLoader loader) { - mRecentTasksLoader = loader; - } - - public void updateValuesFromResources() { - final Resources res = getContext().getResources(); - mThumbnailWidth = Math.round(res.getDimension(R.dimen.status_bar_recents_thumbnail_width)); - mFitThumbnailToXY = res.getBoolean(R.bool.config_recents_thumbnail_image_fits_to_xy); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - - mRecentsContainer = (RecentsScrollView) findViewById(R.id.recents_container); - mRecentsContainer.setOnScrollListener(new Runnable() { - public void run() { - // need to redraw the faded edges - invalidate(); - } - }); - mListAdapter = new TaskDescriptionAdapter(getContext()); - mRecentsContainer.setAdapter(mListAdapter); - mRecentsContainer.setCallback(this); - - mRecentsScrim = findViewById(R.id.recents_bg_protect); - mRecentsNoApps = findViewById(R.id.recents_no_apps); - - if (mRecentsScrim != null) { - mHighEndGfx = ActivityManager.isHighEndGfx(); - if (!mHighEndGfx) { - mRecentsScrim.setBackground(null); - } else if (mRecentsScrim.getBackground() instanceof BitmapDrawable) { - // In order to save space, we make the background texture repeat in the Y direction - ((BitmapDrawable) mRecentsScrim.getBackground()).setTileModeY(TileMode.REPEAT); - } - } - } - - public void setMinSwipeAlpha(float minAlpha) { - mRecentsContainer.setMinSwipeAlpha(minAlpha); - } - - private void createCustomAnimations(LayoutTransition transitioner) { - transitioner.setDuration(200); - transitioner.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 0); - transitioner.setAnimator(LayoutTransition.DISAPPEARING, null); - } - - private void updateIcon(ViewHolder h, Drawable icon, boolean show, boolean anim) { - if (icon != null) { - h.iconView.setImageDrawable(icon); - if (show && h.iconView.getVisibility() != View.VISIBLE) { - if (anim) { - h.iconView.setAnimation( - AnimationUtils.loadAnimation(getContext(), R.anim.recent_appear)); - } - h.iconView.setVisibility(View.VISIBLE); - } - } - } - - private void updateThumbnail(ViewHolder h, Drawable thumbnail, boolean show, boolean anim) { - if (thumbnail != null) { - // Should remove the default image in the frame - // that this now covers, to improve scrolling speed. - // That can't be done until the anim is complete though. - h.thumbnailViewImage.setImageDrawable(thumbnail); - - // scale the image to fill the full width of the ImageView. do this only if - // we haven't set a bitmap before, or if the bitmap size has changed - if (h.thumbnailViewDrawable == null || - h.thumbnailViewDrawable.getIntrinsicWidth() != thumbnail.getIntrinsicWidth() || - h.thumbnailViewDrawable.getIntrinsicHeight() != thumbnail.getIntrinsicHeight()) { - if (mFitThumbnailToXY) { - h.thumbnailViewImage.setScaleType(ScaleType.FIT_XY); - } else { - Matrix scaleMatrix = new Matrix(); - float scale = mThumbnailWidth / (float) thumbnail.getIntrinsicWidth(); - scaleMatrix.setScale(scale, scale); - h.thumbnailViewImage.setScaleType(ScaleType.MATRIX); - h.thumbnailViewImage.setImageMatrix(scaleMatrix); - } - } - if (show && h.thumbnailView.getVisibility() != View.VISIBLE) { - if (anim) { - h.thumbnailView.setAnimation( - AnimationUtils.loadAnimation(getContext(), R.anim.recent_appear)); - } - h.thumbnailView.setVisibility(View.VISIBLE); - } - h.thumbnailViewDrawable = thumbnail; - } - } - - void onTaskThumbnailLoaded(TaskDescription td) { - synchronized (td) { - if (mRecentsContainer != null) { - ViewGroup container = (ViewGroup) mRecentsContainer; - if (container instanceof RecentsScrollView) { - container = (ViewGroup) container.findViewById( - R.id.recents_linear_layout); - } - // Look for a view showing this thumbnail, to update. - for (int i=0; i < container.getChildCount(); i++) { - View v = container.getChildAt(i); - if (v.getTag() instanceof ViewHolder) { - ViewHolder h = (ViewHolder)v.getTag(); - if (!h.loadedThumbnailAndIcon && h.taskDescription == td) { - // only fade in the thumbnail if recents is already visible-- we - // show it immediately otherwise - //boolean animateShow = mShowing && - // mRecentsContainer.getAlpha() > ViewConfiguration.ALPHA_THRESHOLD; - boolean animateShow = false; - updateIcon(h, td.getIcon(), true, animateShow); - updateThumbnail(h, td.getThumbnail(), true, animateShow); - h.loadedThumbnailAndIcon = true; - } - } - } - } - } - showIfReady(); - } - - private void animateInIconOfFirstTask() { - if (mItemToAnimateInWhenWindowAnimationIsFinished != null && - !mRecentTasksLoader.isFirstScreenful()) { - int timeSinceWindowAnimation = - (int) (System.currentTimeMillis() - mWindowAnimationStartTime); - final int minStartDelay = 150; - final int startDelay = Math.max(0, Math.min( - minStartDelay - timeSinceWindowAnimation, minStartDelay)); - final int duration = 250; - final ViewHolder holder = mItemToAnimateInWhenWindowAnimationIsFinished; - final TimeInterpolator cubic = new DecelerateInterpolator(1.5f); - FirstFrameAnimatorHelper.initializeDrawListener(holder.iconView); - for (View v : - new View[] { holder.iconView, holder.labelView, holder.calloutLine }) { - if (v != null) { - ViewPropertyAnimator vpa = v.animate().translationX(0).translationY(0) - .alpha(1f).setStartDelay(startDelay) - .setDuration(duration).setInterpolator(cubic); - FirstFrameAnimatorHelper h = new FirstFrameAnimatorHelper(vpa, v); - } - } - mItemToAnimateInWhenWindowAnimationIsFinished = null; - mAnimateIconOfFirstTask = false; - } - } - - public void onWindowAnimationStart() { - mWaitingForWindowAnimation = false; - mWindowAnimationStartTime = System.currentTimeMillis(); - animateInIconOfFirstTask(); - } - - public void clearRecentTasksList() { - // Clear memory used by screenshots - if (mRecentTaskDescriptions != null) { - mRecentTasksLoader.cancelLoadingThumbnailsAndIcons(this); - onTaskLoadingCancelled(); - } - } - - public void onTaskLoadingCancelled() { - // Gets called by RecentTasksLoader when it's cancelled - if (mRecentTaskDescriptions != null) { - mRecentTaskDescriptions = null; - mListAdapter.notifyDataSetInvalidated(); - } - } - - public void refreshViews() { - mListAdapter.notifyDataSetInvalidated(); - updateUiElements(); - showIfReady(); - } - - public void refreshRecentTasksList() { - refreshRecentTasksList(null, false); - } - - private void refreshRecentTasksList( - ArrayList<TaskDescription> recentTasksList, boolean firstScreenful) { - if (mRecentTaskDescriptions == null && recentTasksList != null) { - onTasksLoaded(recentTasksList, firstScreenful); - } else { - mRecentTasksLoader.loadTasksInBackground(); - } - } - - public void onTasksLoaded(ArrayList<TaskDescription> tasks, boolean firstScreenful) { - if (mRecentTaskDescriptions == null) { - mRecentTaskDescriptions = new ArrayList<TaskDescription>(tasks); - } else { - mRecentTaskDescriptions.addAll(tasks); - } - if (((RecentsActivity) getContext()).isActivityShowing()) { - refreshViews(); - } - } - - private void updateUiElements() { - final int items = mRecentTaskDescriptions != null - ? mRecentTaskDescriptions.size() : 0; - - ((View) mRecentsContainer).setVisibility(items > 0 ? View.VISIBLE : View.GONE); - - // Set description for accessibility - int numRecentApps = mRecentTaskDescriptions != null - ? mRecentTaskDescriptions.size() : 0; - String recentAppsAccessibilityDescription; - if (numRecentApps == 0) { - recentAppsAccessibilityDescription = - getResources().getString(R.string.status_bar_no_recent_apps); - } else { - recentAppsAccessibilityDescription = getResources().getQuantityString( - R.plurals.status_bar_accessibility_recent_apps, numRecentApps, numRecentApps); - } - setContentDescription(recentAppsAccessibilityDescription); - } - - public boolean simulateClick(int persistentTaskId) { - View v = mRecentsContainer.findViewForTask(persistentTaskId); - if (v != null) { - handleOnClick(v); - return true; - } - return false; - } - - public void handleOnClick(View view) { - ViewHolder holder = (ViewHolder) view.getTag(); - TaskDescription ad = holder.taskDescription; - final Context context = view.getContext(); - final ActivityManager am = (ActivityManager) - context.getSystemService(Context.ACTIVITY_SERVICE); - - Bitmap bm = null; - boolean usingDrawingCache = true; - if (holder.thumbnailViewDrawable instanceof BitmapDrawable) { - bm = ((BitmapDrawable) holder.thumbnailViewDrawable).getBitmap(); - if (bm.getWidth() == holder.thumbnailViewImage.getWidth() && - bm.getHeight() == holder.thumbnailViewImage.getHeight()) { - usingDrawingCache = false; - } - } - if (usingDrawingCache) { - holder.thumbnailViewImage.setDrawingCacheEnabled(true); - bm = holder.thumbnailViewImage.getDrawingCache(); - } - Bundle opts = (bm == null) ? - null : - ActivityOptions.makeThumbnailScaleUpAnimation( - holder.thumbnailViewImage, bm, 0, 0, null).toBundle(); - - show(false); - if (ad.taskId >= 0) { - // This is an active task; it should just go to the foreground. - am.moveTaskToFront(ad.taskId, ActivityManager.MOVE_TASK_WITH_HOME, - opts); - } else { - Intent intent = ad.intent; - intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY - | Intent.FLAG_ACTIVITY_TASK_ON_HOME - | Intent.FLAG_ACTIVITY_NEW_TASK); - if (DEBUG) Log.v(TAG, "Starting activity " + intent); - try { - context.startActivityAsUser(intent, opts, - new UserHandle(ad.userId)); - } catch (SecurityException e) { - Log.e(TAG, "Recents does not have the permission to launch " + intent, e); - } catch (ActivityNotFoundException e) { - Log.e(TAG, "Error launching activity " + intent, e); - } - } - if (usingDrawingCache) { - holder.thumbnailViewImage.setDrawingCacheEnabled(false); - } - } - - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - handleOnClick(view); - } - - public void handleSwipe(View view) { - TaskDescription ad = ((ViewHolder) view.getTag()).taskDescription; - if (ad == null) { - Log.v(TAG, "Not able to find activity description for swiped task; view=" + view + - " tag=" + view.getTag()); - return; - } - if (DEBUG) Log.v(TAG, "Jettison " + ad.getLabel()); - mRecentTaskDescriptions.remove(ad); - mRecentTasksLoader.remove(ad); - - // Handled by widget containers to enable LayoutTransitions properly - // mListAdapter.notifyDataSetChanged(); - - if (mRecentTaskDescriptions.size() == 0) { - dismissAndGoBack(); - } - - // Currently, either direction means the same thing, so ignore direction and remove - // the task. - final ActivityManager am = (ActivityManager) - getContext().getSystemService(Context.ACTIVITY_SERVICE); - if (am != null) { - am.removeTask(ad.persistentTaskId); - - // Accessibility feedback - setContentDescription( - getContext().getString(R.string.accessibility_recents_item_dismissed, ad.getLabel())); - sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); - setContentDescription(null); - } - } - - private void startApplicationDetailsActivity(String packageName, int userId) { - Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, - Uri.fromParts("package", packageName, null)); - intent.setComponent(intent.resolveActivity(getContext().getPackageManager())); - TaskStackBuilder.create(getContext()) - .addNextIntentWithParentStack(intent).startActivities(null, new UserHandle(userId)); - } - - public boolean onInterceptTouchEvent(MotionEvent ev) { - if (mPopup != null) { - return true; - } else { - return super.onInterceptTouchEvent(ev); - } - } - - public void handleLongPress( - final View selectedView, final View anchorView, final View thumbnailView) { - thumbnailView.setSelected(true); - final PopupMenu popup = - new PopupMenu(getContext(), anchorView == null ? selectedView : anchorView); - mPopup = popup; - popup.getMenuInflater().inflate(R.menu.recent_popup_menu, popup.getMenu()); - popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - if (item.getItemId() == R.id.recent_remove_item) { - ((ViewGroup) mRecentsContainer).removeViewInLayout(selectedView); - } else if (item.getItemId() == R.id.recent_inspect_item) { - ViewHolder viewHolder = (ViewHolder) selectedView.getTag(); - if (viewHolder != null) { - final TaskDescription ad = viewHolder.taskDescription; - startApplicationDetailsActivity(ad.packageName, ad.userId); - show(false); - } else { - throw new IllegalStateException("Oops, no tag on view " + selectedView); - } - } else { - return false; - } - return true; - } - }); - popup.setOnDismissListener(new PopupMenu.OnDismissListener() { - public void onDismiss(PopupMenu menu) { - thumbnailView.setSelected(false); - mPopup = null; - } - }); - popup.show(); - } - - @Override - protected void dispatchDraw(Canvas canvas) { - super.dispatchDraw(canvas); - - int paddingLeft = getPaddingLeft(); - final boolean offsetRequired = isPaddingOffsetRequired(); - if (offsetRequired) { - paddingLeft += getLeftPaddingOffset(); - } - - int left = getScrollX() + paddingLeft; - int right = left + getRight() - getLeft() - getPaddingRight() - paddingLeft; - int top = getScrollY() + getFadeTop(offsetRequired); - int bottom = top + getFadeHeight(offsetRequired); - - if (offsetRequired) { - right += getRightPaddingOffset(); - bottom += getBottomPaddingOffset(); - } - mRecentsContainer.drawFadedEdges(canvas, left, right, top, bottom); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsPreloadReceiver.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsPreloadReceiver.java deleted file mode 100644 index eb58920..0000000 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentsPreloadReceiver.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.recent; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - -public class RecentsPreloadReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - if (RecentsActivity.PRELOAD_INTENT.equals(intent.getAction())) { - RecentTasksLoader.getInstance(context).preloadRecentTasksList(); - } else if (RecentsActivity.CANCEL_PRELOAD_INTENT.equals(intent.getAction())){ - RecentTasksLoader.getInstance(context).cancelPreloadingRecentTasksList(); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java deleted file mode 100644 index d518f74..0000000 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java +++ /dev/null @@ -1,401 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.recent; - -import android.animation.LayoutTransition; -import android.content.Context; -import android.content.res.Configuration; -import android.database.DataSetObserver; -import android.graphics.Canvas; -import android.util.AttributeSet; -import android.util.DisplayMetrics; -import android.util.Log; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.ViewTreeObserver; -import android.view.ViewTreeObserver.OnGlobalLayoutListener; -import android.widget.LinearLayout; -import android.widget.ScrollView; - -import com.android.systemui.R; -import com.android.systemui.SwipeHelper; -import com.android.systemui.recent.RecentsPanelView.TaskDescriptionAdapter; - -import java.util.HashSet; -import java.util.Iterator; - -public class RecentsVerticalScrollView extends ScrollView - implements SwipeHelper.Callback, RecentsPanelView.RecentsScrollView { - private static final String TAG = RecentsPanelView.TAG; - private static final boolean DEBUG = RecentsPanelView.DEBUG; - private LinearLayout mLinearLayout; - private TaskDescriptionAdapter mAdapter; - private RecentsCallback mCallback; - protected int mLastScrollPosition; - private SwipeHelper mSwipeHelper; - private FadedEdgeDrawHelper mFadedEdgeDrawHelper; - private HashSet<View> mRecycledViews; - private int mNumItemsInOneScreenful; - private Runnable mOnScrollListener; - - public RecentsVerticalScrollView(Context context, AttributeSet attrs) { - super(context, attrs, 0); - mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, context); - - mFadedEdgeDrawHelper = FadedEdgeDrawHelper.create(context, attrs, this, true); - mRecycledViews = new HashSet<View>(); - } - - public void setMinSwipeAlpha(float minAlpha) { - mSwipeHelper.setMinSwipeProgress(minAlpha); - } - - private int scrollPositionOfMostRecent() { - return mLinearLayout.getHeight() - getHeight() + getPaddingTop(); - } - - private void addToRecycledViews(View v) { - if (mRecycledViews.size() < mNumItemsInOneScreenful) { - mRecycledViews.add(v); - } - } - - public View findViewForTask(int persistentTaskId) { - for (int i = 0; i < mLinearLayout.getChildCount(); i++) { - View v = mLinearLayout.getChildAt(i); - RecentsPanelView.ViewHolder holder = (RecentsPanelView.ViewHolder) v.getTag(); - if (holder.taskDescription.persistentTaskId == persistentTaskId) { - return v; - } - } - return null; - } - - private void update() { - for (int i = 0; i < mLinearLayout.getChildCount(); i++) { - View v = mLinearLayout.getChildAt(i); - addToRecycledViews(v); - mAdapter.recycleView(v); - } - LayoutTransition transitioner = getLayoutTransition(); - setLayoutTransition(null); - - mLinearLayout.removeAllViews(); - - // Once we can clear the data associated with individual item views, - // we can get rid of the removeAllViews() and the code below will - // recycle them. - Iterator<View> recycledViews = mRecycledViews.iterator(); - for (int i = 0; i < mAdapter.getCount(); i++) { - View old = null; - if (recycledViews.hasNext()) { - old = recycledViews.next(); - recycledViews.remove(); - old.setVisibility(VISIBLE); - } - final View view = mAdapter.getView(i, old, mLinearLayout); - - if (mFadedEdgeDrawHelper != null) { - mFadedEdgeDrawHelper.addViewCallback(view); - } - - OnTouchListener noOpListener = new OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - return true; - } - }; - - view.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - mCallback.dismiss(); - } - }); - // We don't want a click sound when we dimiss recents - view.setSoundEffectsEnabled(false); - - OnClickListener launchAppListener = new OnClickListener() { - public void onClick(View v) { - mCallback.handleOnClick(view); - } - }; - - RecentsPanelView.ViewHolder holder = (RecentsPanelView.ViewHolder) view.getTag(); - final View thumbnailView = holder.thumbnailView; - OnLongClickListener longClickListener = new OnLongClickListener() { - public boolean onLongClick(View v) { - final View anchorView = view.findViewById(R.id.app_description); - mCallback.handleLongPress(view, anchorView, thumbnailView); - return true; - } - }; - thumbnailView.setClickable(true); - thumbnailView.setOnClickListener(launchAppListener); - thumbnailView.setOnLongClickListener(longClickListener); - - // We don't want to dismiss recents if a user clicks on the app title - // (we also don't want to launch the app either, though, because the - // app title is a small target and doesn't have great click feedback) - final View appTitle = view.findViewById(R.id.app_label); - appTitle.setContentDescription(" "); - appTitle.setOnTouchListener(noOpListener); - final View calloutLine = view.findViewById(R.id.recents_callout_line); - if (calloutLine != null) { - calloutLine.setOnTouchListener(noOpListener); - } - - mLinearLayout.addView(view); - } - setLayoutTransition(transitioner); - - // Scroll to end after initial layout. - final OnGlobalLayoutListener updateScroll = new OnGlobalLayoutListener() { - public void onGlobalLayout() { - mLastScrollPosition = scrollPositionOfMostRecent(); - scrollTo(0, mLastScrollPosition); - final ViewTreeObserver observer = getViewTreeObserver(); - if (observer.isAlive()) { - observer.removeOnGlobalLayoutListener(this); - } - } - }; - getViewTreeObserver().addOnGlobalLayoutListener(updateScroll); - } - - @Override - public void removeViewInLayout(final View view) { - dismissChild(view); - } - - public boolean onInterceptTouchEvent(MotionEvent ev) { - if (DEBUG) Log.v(TAG, "onInterceptTouchEvent()"); - return mSwipeHelper.onInterceptTouchEvent(ev) || - super.onInterceptTouchEvent(ev); - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - return mSwipeHelper.onTouchEvent(ev) || - super.onTouchEvent(ev); - } - - public boolean canChildBeDismissed(View v) { - return true; - } - - @Override - public boolean isAntiFalsingNeeded() { - return false; - } - - @Override - public float getFalsingThresholdFactor() { - return 1.0f; - } - - public void dismissChild(View v) { - mSwipeHelper.dismissChild(v, 0); - } - - public void onChildDismissed(View v) { - addToRecycledViews(v); - mLinearLayout.removeView(v); - mCallback.handleSwipe(v); - // Restore the alpha/translation parameters to what they were before swiping - // (for when these items are recycled) - View contentView = getChildContentView(v); - contentView.setAlpha(1f); - contentView.setTranslationX(0); - } - - public void onBeginDrag(View v) { - // We do this so the underlying ScrollView knows that it won't get - // the chance to intercept events anymore - requestDisallowInterceptTouchEvent(true); - } - - public void onDragCancelled(View v) { - } - - @Override - public void onChildSnappedBack(View animView) { - } - - @Override - public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) { - return false; - } - - public View getChildAtPosition(MotionEvent ev) { - final float x = ev.getX() + getScrollX(); - final float y = ev.getY() + getScrollY(); - for (int i = 0; i < mLinearLayout.getChildCount(); i++) { - View item = mLinearLayout.getChildAt(i); - if (item.getVisibility() == View.VISIBLE - && x >= item.getLeft() && x < item.getRight() - && y >= item.getTop() && y < item.getBottom()) { - return item; - } - } - return null; - } - - public View getChildContentView(View v) { - return v.findViewById(R.id.recent_item); - } - - @Override - public void drawFadedEdges(Canvas canvas, int left, int right, int top, int bottom) { - if (mFadedEdgeDrawHelper != null) { - final boolean offsetRequired = isPaddingOffsetRequired(); - mFadedEdgeDrawHelper.drawCallback(canvas, - left, right, top + getFadeTop(offsetRequired), bottom, getScrollX(), getScrollY(), - getTopFadingEdgeStrength(), getBottomFadingEdgeStrength(), - 0, 0, getPaddingTop()); - } - } - - @Override - protected void onScrollChanged(int l, int t, int oldl, int oldt) { - super.onScrollChanged(l, t, oldl, oldt); - if (mOnScrollListener != null) { - mOnScrollListener.run(); - } - } - - public void setOnScrollListener(Runnable listener) { - mOnScrollListener = listener; - } - - @Override - public int getVerticalFadingEdgeLength() { - if (mFadedEdgeDrawHelper != null) { - return mFadedEdgeDrawHelper.getVerticalFadingEdgeLength(); - } else { - return super.getVerticalFadingEdgeLength(); - } - } - - @Override - public int getHorizontalFadingEdgeLength() { - if (mFadedEdgeDrawHelper != null) { - return mFadedEdgeDrawHelper.getHorizontalFadingEdgeLength(); - } else { - return super.getHorizontalFadingEdgeLength(); - } - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - setScrollbarFadingEnabled(true); - mLinearLayout = (LinearLayout) findViewById(R.id.recents_linear_layout); - final int leftPadding = getContext().getResources() - .getDimensionPixelOffset(R.dimen.status_bar_recents_thumbnail_left_margin); - setOverScrollEffectPadding(leftPadding, 0); - } - - @Override - public void onAttachedToWindow() { - if (mFadedEdgeDrawHelper != null) { - mFadedEdgeDrawHelper.onAttachedToWindowCallback(mLinearLayout, isHardwareAccelerated()); - } - } - - @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - float densityScale = getResources().getDisplayMetrics().density; - mSwipeHelper.setDensityScale(densityScale); - float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop(); - mSwipeHelper.setPagingTouchSlop(pagingTouchSlop); - } - - private void setOverScrollEffectPadding(int leftPadding, int i) { - // TODO Add to (Vertical)ScrollView - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - - // Skip this work if a transition is running; it sets the scroll values independently - // and should not have those animated values clobbered by this logic - LayoutTransition transition = mLinearLayout.getLayoutTransition(); - if (transition != null && transition.isRunning()) { - return; - } - // Keep track of the last visible item in the list so we can restore it - // to the bottom when the orientation changes. - mLastScrollPosition = scrollPositionOfMostRecent(); - - // This has to happen post-layout, so run it "in the future" - post(new Runnable() { - public void run() { - // Make sure we're still not clobbering the transition-set values, since this - // runnable launches asynchronously - LayoutTransition transition = mLinearLayout.getLayoutTransition(); - if (transition == null || !transition.isRunning()) { - scrollTo(0, mLastScrollPosition); - } - } - }); - } - - public void setAdapter(TaskDescriptionAdapter adapter) { - mAdapter = adapter; - mAdapter.registerDataSetObserver(new DataSetObserver() { - public void onChanged() { - update(); - } - - public void onInvalidated() { - update(); - } - }); - - DisplayMetrics dm = getResources().getDisplayMetrics(); - int childWidthMeasureSpec = - MeasureSpec.makeMeasureSpec(dm.widthPixels, MeasureSpec.AT_MOST); - int childheightMeasureSpec = - MeasureSpec.makeMeasureSpec(dm.heightPixels, MeasureSpec.AT_MOST); - View child = mAdapter.createView(mLinearLayout); - child.measure(childWidthMeasureSpec, childheightMeasureSpec); - mNumItemsInOneScreenful = - (int) Math.ceil(dm.heightPixels / (double) child.getMeasuredHeight()); - addToRecycledViews(child); - - for (int i = 0; i < mNumItemsInOneScreenful - 1; i++) { - addToRecycledViews(mAdapter.createView(mLinearLayout)); - } - } - - public int numItemsInOneScreenful() { - return mNumItemsInOneScreenful; - } - - @Override - public void setLayoutTransition(LayoutTransition transition) { - // The layout transition applies to our embedded LinearLayout - mLinearLayout.setLayoutTransition(transition); - } - - public void setCallback(RecentsCallback callback) { - mCallback = callback; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recent/TaskDescription.java b/packages/SystemUI/src/com/android/systemui/recent/TaskDescription.java deleted file mode 100644 index 5ad965f..0000000 --- a/packages/SystemUI/src/com/android/systemui/recent/TaskDescription.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.recent; - -import android.os.UserHandle; -import android.content.Intent; -import android.content.pm.ResolveInfo; -import android.graphics.drawable.Drawable; - -public final class TaskDescription { - final ResolveInfo resolveInfo; - final int taskId; // application task id for curating apps - final int persistentTaskId; // persistent id - final Intent intent; // launch intent for application - final String packageName; // used to override animations (see onClick()) - final CharSequence description; - final int userId; - - private Drawable mThumbnail; // generated by Activity.onCreateThumbnail() - private Drawable mIcon; // application package icon - private CharSequence mLabel; // application package label - private boolean mLoaded; - - public TaskDescription(int _taskId, int _persistentTaskId, - ResolveInfo _resolveInfo, Intent _intent, - String _packageName, CharSequence _description, int _userId) { - resolveInfo = _resolveInfo; - intent = _intent; - taskId = _taskId; - persistentTaskId = _persistentTaskId; - - description = _description; - packageName = _packageName; - userId = _userId; - } - - public TaskDescription() { - resolveInfo = null; - intent = null; - taskId = -1; - persistentTaskId = -1; - - description = null; - packageName = null; - userId = UserHandle.USER_NULL; - } - - public void setLoaded(boolean loaded) { - mLoaded = loaded; - } - - public boolean isLoaded() { - return mLoaded; - } - - public boolean isNull() { - return resolveInfo == null; - } - - // mark all these as locked? - public CharSequence getLabel() { - return mLabel; - } - - public void setLabel(CharSequence label) { - mLabel = label; - } - - public Drawable getIcon() { - return mIcon; - } - - public void setIcon(Drawable icon) { - mIcon = icon; - } - - public void setThumbnail(Drawable thumbnail) { - mThumbnail = thumbnail; - } - - public Drawable getThumbnail() { - return mThumbnail; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java index 0a1718d..192acc6 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java @@ -29,18 +29,18 @@ public class Constants { public static final boolean EnableTransitionThumbnailDebugMode = false; // Enables the filtering of tasks according to their grouping public static final boolean EnableTaskFiltering = false; - // Enables clipping of tasks against each other - public static final boolean EnableTaskStackClipping = true; - // Enables tapping on the TaskBar to launch the task - public static final boolean EnableTaskBarTouchEvents = true; // Enables app-info pane on long-pressing the icon public static final boolean EnableDevAppInfoOnLongPress = true; + // Enables dismiss-all + public static final boolean EnableDismissAll = false; // Enables debug mode public static final boolean EnableDebugMode = false; // Enables the search bar layout public static final boolean EnableSearchLayout = true; // Enables the thumbnail alpha on the front-most task public static final boolean EnableThumbnailAlphaOnFrontmost = false; + // Enables all system stacks to show up in the same recents stack + public static final boolean EnableMultiStackToSingleStack = true; // This disables the bitmap and icon caches public static final boolean DisableBackgroundCache = false; // Enables the simulated task affiliations diff --git a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java index 2ddab48..2d1fab0 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java @@ -24,7 +24,6 @@ import android.appwidget.AppWidgetHost; import android.appwidget.AppWidgetProviderInfo; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -37,10 +36,13 @@ import android.os.Handler; import android.os.SystemClock; import android.os.UserHandle; import android.util.Pair; +import android.view.Display; import android.view.LayoutInflater; import android.view.View; import com.android.systemui.R; import com.android.systemui.RecentsComponent; +import com.android.systemui.SystemUI; +import com.android.systemui.SystemUIApplication; import com.android.systemui.recents.misc.Console; import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.recents.model.RecentsTaskLoadPlan; @@ -52,9 +54,9 @@ import com.android.systemui.recents.views.TaskStackView; import com.android.systemui.recents.views.TaskStackViewLayoutAlgorithm; import com.android.systemui.recents.views.TaskViewHeader; import com.android.systemui.recents.views.TaskViewTransform; +import com.android.systemui.statusbar.phone.PhoneStatusBar; import java.util.ArrayList; -import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -69,7 +71,8 @@ import java.util.concurrent.atomic.AtomicBoolean; @interface ProxyFromAnyToPrimaryUser {} /** A proxy implementation for the recents component */ -public class AlternateRecentsComponent implements ActivityOptions.OnAnimationStartedListener { +public class Recents extends SystemUI + implements ActivityOptions.OnAnimationStartedListener, RecentsComponent { final public static String EXTRA_TRIGGERED_FROM_ALT_TAB = "triggeredFromAltTab"; final public static String EXTRA_TRIGGERED_FROM_HOME_KEY = "triggeredFromHomeKey"; @@ -78,6 +81,8 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta // Owner proxy events final public static String ACTION_PROXY_NOTIFY_RECENTS_VISIBLITY_TO_OWNER = "action_notify_recents_visibility_change"; + final public static String ACTION_PROXY_SCREEN_PINNING_REQUEST_TO_OWNER = + "action_screen_pinning_request"; final public static String ACTION_START_ENTER_ANIMATION = "action_start_enter_animation"; final public static String ACTION_TOGGLE_RECENTS_ACTIVITY = "action_toggle_recents_activity"; @@ -85,7 +90,7 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta final static int sMinToggleDelay = 350; - final static String sToggleRecentsAction = "com.android.systemui.recents.SHOW_RECENTS"; + public final static String sToggleRecentsAction = "com.android.systemui.recents.SHOW_RECENTS"; public final static String sRecentsPackage = "com.android.systemui"; public final static String sRecentsActivity = "com.android.systemui.recents.RecentsActivity"; @@ -109,6 +114,9 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta /** Preloads the next task */ public void run() { + // Temporarily skip this if multi stack is enabled + if (mConfig.multiStackEnabled) return; + RecentsConfiguration config = RecentsConfiguration.getInstance(); if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) { RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); @@ -144,14 +152,17 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta case ACTION_PROXY_NOTIFY_RECENTS_VISIBLITY_TO_OWNER: visibilityChanged(intent.getBooleanExtra(EXTRA_RECENTS_VISIBILITY, false)); break; + case ACTION_PROXY_SCREEN_PINNING_REQUEST_TO_OWNER: + onStartScreenPinning(context); + break; } } } static RecentsComponent.Callbacks sRecentsComponentCallbacks; static RecentsTaskLoadPlan sInstanceLoadPlan; + static Recents sInstance; - Context mContext; LayoutInflater mInflater; SystemServicesProxy mSystemServicesProxy; Handler mHandler; @@ -179,11 +190,43 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta boolean mTriggeredFromAltTab; long mLastToggleTime; - public AlternateRecentsComponent(Context context) { - RecentsTaskLoader.initialize(context); - mInflater = LayoutInflater.from(context); - mContext = context; - mSystemServicesProxy = new SystemServicesProxy(context); + public Recents() { + } + + /** + * Gets the singleton instance and starts it if needed. On the primary user on the device, this + * component gets started as a normal {@link SystemUI} component. On a secondary user, this + * lifecycle doesn't exist, so we need to start it manually here if needed. + */ + public static Recents getInstanceAndStartIfNeeded(Context ctx) { + if (sInstance == null) { + sInstance = new Recents(); + sInstance.mContext = ctx; + sInstance.start(); + sInstance.onBootCompleted(); + } + return sInstance; + } + + /** Creates a new broadcast intent */ + static Intent createLocalBroadcastIntent(Context context, String action) { + Intent intent = new Intent(action); + intent.setPackage(context.getPackageName()); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | + Intent.FLAG_RECEIVER_FOREGROUND); + return intent; + } + + /** Initializes the Recents. */ + @ProxyFromPrimaryToCurrentUser + @Override + public void start() { + if (sInstance == null) { + sInstance = this; + } + RecentsTaskLoader.initialize(mContext); + mInflater = LayoutInflater.from(mContext); + mSystemServicesProxy = new SystemServicesProxy(mContext); mHandler = new Handler(); mTaskStackBounds = new Rect(); @@ -197,24 +240,12 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta if (mSystemServicesProxy.isForegroundUserOwner()) { mProxyBroadcastReceiver = new RecentsOwnerEventProxyReceiver(); IntentFilter filter = new IntentFilter(); - filter.addAction(AlternateRecentsComponent.ACTION_PROXY_NOTIFY_RECENTS_VISIBLITY_TO_OWNER); + filter.addAction(Recents.ACTION_PROXY_NOTIFY_RECENTS_VISIBLITY_TO_OWNER); + filter.addAction(Recents.ACTION_PROXY_SCREEN_PINNING_REQUEST_TO_OWNER); mContext.registerReceiverAsUser(mProxyBroadcastReceiver, UserHandle.CURRENT, filter, null, mHandler); } - } - /** Creates a new broadcast intent */ - static Intent createLocalBroadcastIntent(Context context, String action) { - Intent intent = new Intent(action); - intent.setPackage(context.getPackageName()); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | - Intent.FLAG_RECEIVER_FOREGROUND); - return intent; - } - - /** Initializes the Recents. */ - @ProxyFromPrimaryToCurrentUser - public void onStart() { // Initialize some static datastructures TaskStackViewLayoutAlgorithm.initializeCurve(); // Load the header bar layout @@ -230,17 +261,20 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize(); launchOpts.onlyLoadForCache = true; loader.loadTasks(mContext, plan, launchOpts); + putComponent(Recents.class, this); } + @Override public void onBootCompleted() { mBootCompleted = true; } /** Shows the Recents. */ @ProxyFromPrimaryToCurrentUser - public void onShowRecents(boolean triggeredFromAltTab) { + @Override + public void showRecents(boolean triggeredFromAltTab, View statusBarView) { if (mSystemServicesProxy.isForegroundUserOwner()) { - showRecents(triggeredFromAltTab); + showRecentsInternal(triggeredFromAltTab); } else { Intent intent = createLocalBroadcastIntent(mContext, RecentsUserEventProxyReceiver.ACTION_PROXY_SHOW_RECENTS_TO_USER); @@ -248,7 +282,8 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT); } } - void showRecents(boolean triggeredFromAltTab) { + + void showRecentsInternal(boolean triggeredFromAltTab) { mTriggeredFromAltTab = triggeredFromAltTab; try { @@ -260,9 +295,10 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta /** Hides the Recents. */ @ProxyFromPrimaryToCurrentUser - public void onHideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) { + @Override + public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) { if (mSystemServicesProxy.isForegroundUserOwner()) { - hideRecents(triggeredFromAltTab, triggeredFromHomeKey); + hideRecentsInternal(triggeredFromAltTab, triggeredFromHomeKey); } else { Intent intent = createLocalBroadcastIntent(mContext, RecentsUserEventProxyReceiver.ACTION_PROXY_HIDE_RECENTS_TO_USER); @@ -271,7 +307,8 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT); } } - void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) { + + void hideRecentsInternal(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) { if (mBootCompleted) { ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask(); if (topTask != null && mSystemServicesProxy.isRecentsTopMost(topTask, null)) { @@ -286,16 +323,18 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta /** Toggles the Recents activity. */ @ProxyFromPrimaryToCurrentUser - public void onToggleRecents() { + @Override + public void toggleRecents(Display display, int layoutDirection, View statusBarView) { if (mSystemServicesProxy.isForegroundUserOwner()) { - toggleRecents(); + toggleRecentsInternal(); } else { Intent intent = createLocalBroadcastIntent(mContext, RecentsUserEventProxyReceiver.ACTION_PROXY_TOGGLE_RECENTS_TO_USER); mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT); } } - void toggleRecents() { + + void toggleRecentsInternal() { mTriggeredFromAltTab = false; try { @@ -307,16 +346,18 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta /** Preloads info for the Recents activity. */ @ProxyFromPrimaryToCurrentUser - public void onPreloadRecents() { + @Override + public void preloadRecents() { if (mSystemServicesProxy.isForegroundUserOwner()) { - preloadRecents(); + preloadRecentsInternal(); } else { Intent intent = createLocalBroadcastIntent(mContext, RecentsUserEventProxyReceiver.ACTION_PROXY_PRELOAD_RECENTS_TO_USER); mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT); } } - void preloadRecents() { + + void preloadRecentsInternal() { // Preload only the raw task list into a new load plan (which will be consumed by the // RecentsActivity) RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); @@ -324,18 +365,27 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta sInstanceLoadPlan.preloadRawTasks(true); } - public void onCancelPreloadingRecents() { + @Override + public void cancelPreloadingRecents() { // Do nothing } void showRelativeAffiliatedTask(boolean showNextTask) { + // Return early if there is no focused stack + int focusedStackId = mSystemServicesProxy.getFocusedStack(); + TaskStack focusedStack = null; RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); loader.preloadTasks(plan, true /* isTopTaskHome */); - TaskStack stack = plan.getTaskStack(); + if (mConfig.multiStackEnabled) { + if (focusedStackId < 0) return; + focusedStack = plan.getTaskStack(focusedStackId); + } else { + focusedStack = plan.getAllTaskStacks().get(0); + } - // Return early if there are no tasks - if (stack.getTaskCount() == 0) return; + // Return early if there are no tasks in the focused stack + if (focusedStack == null || focusedStack.getTaskCount() == 0) return; ActivityManager.RunningTaskInfo runningTask = mSystemServicesProxy.getTopMostTask(); // Return early if there is no running task (can't determine affiliated tasks in this case) @@ -344,7 +394,7 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta if (mSystemServicesProxy.isInHomeStack(runningTask.id)) return; // Find the task in the recents list - ArrayList<Task> tasks = stack.getTasks(); + ArrayList<Task> tasks = focusedStack.getTasks(); Task toTask = null; ActivityOptions launchOpts = null; int taskCount = tasks.size(); @@ -366,7 +416,7 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta R.anim.recents_launch_prev_affiliated_task_source); } if (toTaskKey != null) { - toTask = stack.findTaskWithId(toTaskKey.id); + toTask = focusedStack.findTaskWithId(toTaskKey.id); } numAffiliatedTasks = group.getTaskCount(); break; @@ -399,11 +449,13 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta } } - public void onShowNextAffiliatedTask() { + @Override + public void showNextAffiliatedTask() { showRelativeAffiliatedTask(true); } - public void onShowPrevAffiliatedTask() { + @Override + public void showPrevAffiliatedTask() { showRelativeAffiliatedTask(false); } @@ -438,8 +490,9 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta // Reload the widget id before we get the task stack bounds reloadSearchBarAppWidget(mContext, mSystemServicesProxy); } - mConfig.getTaskStackBounds(mWindowRect.width(), mWindowRect.height(), mStatusBarHeight, - (mConfig.hasTransposedNavBar ? mNavBarWidth : 0), mTaskStackBounds); + mConfig.getAvailableTaskStackBounds(mWindowRect.width(), mWindowRect.height(), + mStatusBarHeight, (mConfig.hasTransposedNavBar ? mNavBarWidth : 0), + mTaskStackBounds); if (mConfig.isLandscape && mConfig.hasTransposedNavBar) { mSystemInsets.set(0, mStatusBarHeight, mNavBarWidth, 0); } else { @@ -611,14 +664,32 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta /** Starts the recents activity */ void startRecentsActivity(ActivityManager.RunningTaskInfo topTask, boolean isTopTaskHome) { + RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); + RecentsConfiguration.reinitialize(mContext, mSystemServicesProxy); + if (sInstanceLoadPlan == null) { // Create a new load plan if onPreloadRecents() was never triggered - RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); sInstanceLoadPlan = loader.createLoadPlan(mContext); } - RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); + + // Temporarily skip the transition (use a dummy fade) if multi stack is enabled. + // For multi-stack we need to figure out where each of the tasks are going. + if (mConfig.multiStackEnabled) { + loader.preloadTasks(sInstanceLoadPlan, true); + ArrayList<TaskStack> stacks = sInstanceLoadPlan.getAllTaskStacks(); + TaskStack stack = stacks.get(0); + mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, true); + TaskStackViewLayoutAlgorithm.VisibilityReport stackVr = + mDummyStackView.computeStackVisibilityReport(); + ActivityOptions opts = getUnknownTransitionActivityOptions(); + startAlternateRecentsActivity(topTask, opts, true /* fromHome */, + false /* fromSearchHome */, false /* fromThumbnail */, stackVr); + return; + } + loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome); - TaskStack stack = sInstanceLoadPlan.getTaskStack(); + ArrayList<TaskStack> stacks = sInstanceLoadPlan.getAllTaskStacks(); + TaskStack stack = stacks.get(0); // Prepare the dummy stack for the transition mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, isTopTaskHome); @@ -714,7 +785,8 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta } /** Sets the RecentsComponent callbacks. */ - public void setRecentsComponentCallback(RecentsComponent.Callbacks cb) { + @Override + public void setCallback(RecentsComponent.Callbacks cb) { sRecentsComponentCallbacks = cb; } @@ -737,6 +809,27 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta } } + /** Notifies the status bar to trigger screen pinning. */ + @ProxyFromAnyToPrimaryUser + public static void startScreenPinning(Context context, SystemServicesProxy ssp) { + if (ssp.isForegroundUserOwner()) { + onStartScreenPinning(context); + } else { + Intent intent = createLocalBroadcastIntent(context, + ACTION_PROXY_SCREEN_PINNING_REQUEST_TO_OWNER); + context.sendBroadcastAsUser(intent, UserHandle.OWNER); + } + } + static void onStartScreenPinning(Context context) { + // For the primary user, the context for the SystemUI component is the SystemUIApplication + SystemUIApplication app = (SystemUIApplication) + getInstanceAndStartIfNeeded(context).mContext; + PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class); + if (statusBar != null) { + statusBar.showScreenPinningRequest(false); + } + } + /** * Returns the preloaded load plan and invalidates it. */ diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java index 7422e36..130d3bc 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java @@ -19,7 +19,6 @@ package com.android.systemui.recents; import android.app.Activity; import android.app.ActivityOptions; import android.app.SearchManager; -import android.appwidget.AppWidgetHostView; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProviderInfo; import android.content.BroadcastReceiver; @@ -35,7 +34,6 @@ import android.view.KeyEvent; import android.view.View; import android.view.ViewStub; import android.widget.Toast; - import com.android.systemui.R; import com.android.systemui.recents.misc.DebugTrigger; import com.android.systemui.recents.misc.ReferenceCountedTrigger; @@ -43,15 +41,12 @@ import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.recents.misc.Utilities; import com.android.systemui.recents.model.RecentsTaskLoadPlan; import com.android.systemui.recents.model.RecentsTaskLoader; -import com.android.systemui.recents.model.SpaceNode; import com.android.systemui.recents.model.Task; import com.android.systemui.recents.model.TaskStack; import com.android.systemui.recents.views.DebugOverlayView; import com.android.systemui.recents.views.RecentsView; import com.android.systemui.recents.views.SystemBarScrimViews; import com.android.systemui.recents.views.ViewAnimation; -import com.android.systemui.statusbar.phone.PhoneStatusBar; -import com.android.systemui.SystemUIApplication; import java.lang.ref.WeakReference; import java.lang.reflect.InvocationTargetException; @@ -75,16 +70,17 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView View mEmptyView; DebugOverlayView mDebugOverlay; + // Resize task debug + RecentsResizeTaskDialog mResizeTaskDebugDialog; + // Search AppWidget - RecentsAppWidgetHost mAppWidgetHost; AppWidgetProviderInfo mSearchAppWidgetInfo; - AppWidgetHostView mSearchAppWidgetHostView; + RecentsAppWidgetHost mAppWidgetHost; + RecentsAppWidgetHostView mSearchAppWidgetHostView; // Runnables to finish the Recents activity FinishRecentsRunnable mFinishLaunchHomeRunnable; - private PhoneStatusBar mStatusBar; - /** * A common Runnable to finish Recents either by calling finish() (with a custom animation) or * launching Home with some ActivityOptions. Generally we always launch home when we exit @@ -129,20 +125,20 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - if (action.equals(AlternateRecentsComponent.ACTION_HIDE_RECENTS_ACTIVITY)) { - if (intent.getBooleanExtra(AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_ALT_TAB, false)) { + if (action.equals(Recents.ACTION_HIDE_RECENTS_ACTIVITY)) { + if (intent.getBooleanExtra(Recents.EXTRA_TRIGGERED_FROM_ALT_TAB, false)) { // If we are hiding from releasing Alt-Tab, dismiss Recents to the focused app dismissRecentsToFocusedTaskOrHome(false); - } else if (intent.getBooleanExtra(AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_HOME_KEY, false)) { + } else if (intent.getBooleanExtra(Recents.EXTRA_TRIGGERED_FROM_HOME_KEY, false)) { // Otherwise, dismiss Recents to Home dismissRecentsToHome(true); } else { // Do nothing, another activity is being launched on top of Recents } - } else if (action.equals(AlternateRecentsComponent.ACTION_TOGGLE_RECENTS_ACTIVITY)) { + } else if (action.equals(Recents.ACTION_TOGGLE_RECENTS_ACTIVITY)) { // If we are toggling Recents, then first unfilter any filtered stacks first dismissRecentsToFocusedTaskOrHome(true); - } else if (action.equals(AlternateRecentsComponent.ACTION_START_ENTER_ANIMATION)) { + } else if (action.equals(Recents.ACTION_START_ENTER_ANIMATION)) { // Trigger the enter animation onEnterAnimationTriggered(); // Notify the fallback receiver that we have successfully got the broadcast @@ -180,17 +176,17 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView }); /** Updates the set of recent tasks */ - void updateRecentsTasks(Intent launchIntent) { + void updateRecentsTasks() { // If AlternateRecentsComponent has preloaded a load plan, then use that to prevent // reconstructing the task stack RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); - RecentsTaskLoadPlan plan = AlternateRecentsComponent.consumeInstanceLoadPlan(); + RecentsTaskLoadPlan plan = Recents.consumeInstanceLoadPlan(); if (plan == null) { plan = loader.createLoadPlan(this); } // Start loading tasks according to the load plan - if (plan.getTaskStack() == null) { + if (!plan.hasTasks()) { loader.preloadTasks(plan, mConfig.launchedFromHome); } RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options(); @@ -199,13 +195,11 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView loadOpts.numVisibleTaskThumbnails = mConfig.launchedNumVisibleThumbnails; loader.loadTasks(this, plan, loadOpts); - SpaceNode root = plan.getSpaceNode(); - ArrayList<TaskStack> stacks = root.getStacks(); - boolean hasTasks = root.hasTasks(); - if (hasTasks) { + ArrayList<TaskStack> stacks = plan.getAllTaskStacks(); + mConfig.launchedWithNoRecentTasks = !plan.hasTasks(); + if (!mConfig.launchedWithNoRecentTasks) { mRecentsView.setTaskStacks(stacks); } - mConfig.launchedWithNoRecentTasks = !hasTasks; // Create the home intent runnable Intent homeIntent = new Intent(Intent.ACTION_MAIN, null); @@ -247,7 +241,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView if (mEmptyView != null) { mEmptyView.setVisibility(View.GONE); } - if (mRecentsView.hasSearchBar()) { + if (mRecentsView.hasValidSearchBar()) { mRecentsView.setSearchBarVisibility(View.VISIBLE); } else { addSearchBarAppWidgetView(); @@ -297,8 +291,8 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView if (Constants.DebugFlags.App.EnableSearchLayout) { int appWidgetId = mConfig.searchBarAppWidgetId; if (appWidgetId >= 0) { - mSearchAppWidgetHostView = mAppWidgetHost.createView(this, appWidgetId, - mSearchAppWidgetInfo); + mSearchAppWidgetHostView = (RecentsAppWidgetHostView) mAppWidgetHost.createView( + this, appWidgetId, mSearchAppWidgetInfo); Bundle opts = new Bundle(); opts.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY, AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX); @@ -362,12 +356,11 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // For the non-primary user, ensure that the SystemSericesProxy is initialized + // For the non-primary user, ensure that the SystemServicesProxy and configuration is + // initialized RecentsTaskLoader.initialize(this); - - // Initialize the loader and the configuration - mConfig = RecentsConfiguration.reinitialize(this, - RecentsTaskLoader.getInstance().getSystemServicesProxy()); + SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy(); + mConfig = RecentsConfiguration.reinitialize(this, ssp); // Initialize the widget host (the host id is static and does not change) mAppWidgetHost = new RecentsAppWidgetHost(this, Constants.Values.App.AppWidgetHostId); @@ -382,8 +375,6 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView mEmptyViewStub = (ViewStub) findViewById(R.id.empty_view_stub); mDebugOverlayStub = (ViewStub) findViewById(R.id.debug_overlay_stub); mScrimViews = new SystemBarScrimViews(this, mConfig); - mStatusBar = ((SystemUIApplication) getApplication()) - .getComponent(PhoneStatusBar.class); inflateDebugOverlay(); // Bind the search app widget when we first start up @@ -422,9 +413,6 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView super.onNewIntent(intent); setIntent(intent); - // Reinitialize the configuration - RecentsConfiguration.reinitialize(this, RecentsTaskLoader.getInstance().getSystemServicesProxy()); - // Clear any debug rects if (mDebugOverlay != null) { mDebugOverlay.clear(); @@ -436,20 +424,26 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView super.onStart(); RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); SystemServicesProxy ssp = loader.getSystemServicesProxy(); - AlternateRecentsComponent.notifyVisibilityChanged(this, ssp, true); + Recents.notifyVisibilityChanged(this, ssp, true); // Register the broadcast receiver to handle messages from our service IntentFilter filter = new IntentFilter(); - filter.addAction(AlternateRecentsComponent.ACTION_HIDE_RECENTS_ACTIVITY); - filter.addAction(AlternateRecentsComponent.ACTION_TOGGLE_RECENTS_ACTIVITY); - filter.addAction(AlternateRecentsComponent.ACTION_START_ENTER_ANIMATION); + filter.addAction(Recents.ACTION_HIDE_RECENTS_ACTIVITY); + filter.addAction(Recents.ACTION_TOGGLE_RECENTS_ACTIVITY); + filter.addAction(Recents.ACTION_START_ENTER_ANIMATION); registerReceiver(mServiceBroadcastReceiver, filter); // Register any broadcast receivers for the task loader loader.registerReceivers(this, mRecentsView); // Update the recent tasks - updateRecentsTasks(getIntent()); + updateRecentsTasks(); + + // If this is a new instance from a configuration change, then we have to manually trigger + // the enter animation state + if (mConfig.launchedHasConfigurationChanged) { + onEnterAnimationTriggered(); + } } @Override @@ -457,7 +451,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView super.onStop(); RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); SystemServicesProxy ssp = loader.getSystemServicesProxy(); - AlternateRecentsComponent.notifyVisibilityChanged(this, ssp, false); + Recents.notifyVisibilityChanged(this, ssp, false); // Notify the views that we are no longer visible mRecentsView.onRecentsHidden(); @@ -589,6 +583,21 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView } } + + /**** RecentsResizeTaskDialog ****/ + + private RecentsResizeTaskDialog getResizeTaskDebugDialog() { + if (mResizeTaskDebugDialog == null) { + mResizeTaskDebugDialog = new RecentsResizeTaskDialog(getFragmentManager(), this); + } + return mResizeTaskDebugDialog; + } + + @Override + public void onTaskResize(Task t) { + getResizeTaskDebugDialog().showResizeTaskDialog(t, mRecentsView); + } + /**** RecentsView.RecentsViewCallbacks Implementation ****/ @Override @@ -614,9 +623,9 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView @Override public void onScreenPinningRequest() { - if (mStatusBar != null) { - mStatusBar.showScreenPinningRequest(false); - } + RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); + SystemServicesProxy ssp = loader.getSystemServicesProxy(); + Recents.startScreenPinning(this, ssp); } /**** RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks Implementation ****/ diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java index 5bae37a..02a7b94 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java @@ -17,6 +17,7 @@ package com.android.systemui.recents; import android.appwidget.AppWidgetHost; +import android.appwidget.AppWidgetHostView; import android.appwidget.AppWidgetProviderInfo; import android.content.Context; import com.android.systemui.recents.misc.SystemServicesProxy; @@ -61,6 +62,12 @@ public class RecentsAppWidgetHost extends AppWidgetHost { } @Override + protected AppWidgetHostView onCreateView(Context context, int appWidgetId, + AppWidgetProviderInfo appWidget) { + return new RecentsAppWidgetHostView(context); + } + + @Override protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidgetInfo) { if (mCb == null) return; diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHostView.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHostView.java new file mode 100644 index 0000000..1ed755a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHostView.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.recents; + +import android.appwidget.AppWidgetHostView; +import android.content.Context; +import android.widget.RemoteViews; + +public class RecentsAppWidgetHostView extends AppWidgetHostView { + + private Context mContext; + private int mPreviousOrientation; + + public RecentsAppWidgetHostView(Context context) { + super(context); + mContext = context; + } + + @Override + public void updateAppWidget(RemoteViews remoteViews) { + // Store the orientation in which the widget was inflated + updateLastInflationOrientation(); + super.updateAppWidget(remoteViews); + } + + /** + * Updates the last orientation that this widget was inflated. + */ + private void updateLastInflationOrientation() { + mPreviousOrientation = mContext.getResources().getConfiguration().orientation; + } + + /** + * @return whether the search widget was updated while Recents was in a different orientation + * in the background. + */ + public boolean isReinflateRequired() { + // Re-inflate is required if the orientation has changed since last inflated. + int orientation = mContext.getResources().getConfiguration().orientation; + if (mPreviousOrientation != orientation) { + return true; + } + return false; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java index 52e7e7f..abeb2b0 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java @@ -24,7 +24,6 @@ import android.content.res.Resources; import android.graphics.Rect; import android.provider.Settings; import android.util.DisplayMetrics; -import android.util.TypedValue; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import com.android.systemui.R; @@ -50,9 +49,6 @@ public class RecentsConfiguration { // Disable all thumbnail loading. public static final int SVELTE_DISABLE_LOADING = 3; - /** Animations */ - public float animationPxMovementPerSecond; - /** Interpolators */ public Interpolator fastOutSlowInInterpolator; public Interpolator fastOutLinearInInterpolator; @@ -83,6 +79,7 @@ public class RecentsConfiguration { public int taskStackScrollDuration; public int taskStackMaxDim; public int taskStackTopPaddingPx; + public int dismissAllButtonSizePx; public float taskStackWidthPaddingPct; public float taskStackOverscrollPct; @@ -137,6 +134,7 @@ public class RecentsConfiguration { public boolean fakeShadows; /** Dev options and global settings */ + public boolean multiStackEnabled; public boolean lockToAppEnabled; public boolean developerOptionsEnabled; public boolean debugModeEnabled; @@ -197,10 +195,6 @@ public class RecentsConfiguration { // Insets displayRect.set(0, 0, dm.widthPixels, dm.heightPixels); - // Animations - animationPxMovementPerSecond = - res.getDimensionPixelSize(R.dimen.recents_animation_movement_in_dps_per_second); - // Filtering filteringCurrentViewsAnimDuration = res.getInteger(R.integer.recents_filter_animate_current_views_duration); @@ -217,14 +211,11 @@ public class RecentsConfiguration { // Task stack taskStackScrollDuration = res.getInteger(R.integer.recents_animate_task_stack_scroll_duration); - TypedValue widthPaddingPctValue = new TypedValue(); - res.getValue(R.dimen.recents_stack_width_padding_percentage, widthPaddingPctValue, true); - taskStackWidthPaddingPct = widthPaddingPctValue.getFloat(); - TypedValue stackOverscrollPctValue = new TypedValue(); - res.getValue(R.dimen.recents_stack_overscroll_percentage, stackOverscrollPctValue, true); - taskStackOverscrollPct = stackOverscrollPctValue.getFloat(); + taskStackWidthPaddingPct = res.getFloat(R.dimen.recents_stack_width_padding_percentage); + taskStackOverscrollPct = res.getFloat(R.dimen.recents_stack_overscroll_percentage); taskStackMaxDim = res.getInteger(R.integer.recents_max_task_stack_view_dim); taskStackTopPaddingPx = res.getDimensionPixelSize(R.dimen.recents_stack_top_padding); + dismissAllButtonSizePx = res.getDimensionPixelSize(R.dimen.recents_dismiss_all_button_size); // Transition transitionEnterFromAppDelay = @@ -254,22 +245,16 @@ public class RecentsConfiguration { taskViewTranslationZMaxPx = res.getDimensionPixelSize(R.dimen.recents_task_view_z_max); taskViewAffiliateGroupEnterOffsetPx = res.getDimensionPixelSize(R.dimen.recents_task_view_affiliate_group_enter_offset); - TypedValue thumbnailAlphaValue = new TypedValue(); - res.getValue(R.dimen.recents_task_view_thumbnail_alpha, thumbnailAlphaValue, true); - taskViewThumbnailAlpha = thumbnailAlphaValue.getFloat(); + taskViewThumbnailAlpha = res.getFloat(R.dimen.recents_task_view_thumbnail_alpha); // Task bar colors - taskBarViewDefaultBackgroundColor = - res.getColor(R.color.recents_task_bar_default_background_color); - taskBarViewLightTextColor = - res.getColor(R.color.recents_task_bar_light_text_color); - taskBarViewDarkTextColor = - res.getColor(R.color.recents_task_bar_dark_text_color); - taskBarViewHighlightColor = - res.getColor(R.color.recents_task_bar_highlight_color); - TypedValue affMinAlphaPctValue = new TypedValue(); - res.getValue(R.dimen.recents_task_affiliation_color_min_alpha_percentage, affMinAlphaPctValue, true); - taskBarViewAffiliationColorMinAlpha = affMinAlphaPctValue.getFloat(); + taskBarViewDefaultBackgroundColor = context.getColor( + R.color.recents_task_bar_default_background_color); + taskBarViewLightTextColor = context.getColor(R.color.recents_task_bar_light_text_color); + taskBarViewDarkTextColor = context.getColor(R.color.recents_task_bar_dark_text_color); + taskBarViewHighlightColor = context.getColor(R.color.recents_task_bar_highlight_color); + taskBarViewAffiliationColorMinAlpha = res.getFloat( + R.dimen.recents_task_affiliation_color_min_alpha_percentage); // Task bar size & animations taskBarHeight = res.getDimensionPixelSize(R.dimen.recents_task_bar_height); @@ -307,6 +292,7 @@ public class RecentsConfiguration { Settings.Global.DEVELOPMENT_SETTINGS_ENABLED) != 0; lockToAppEnabled = ssp.getSystemSetting(context, Settings.System.LOCK_TO_APP_ENABLED) != 0; + multiStackEnabled = "true".equals(ssp.getSystemProperty("persist.sys.debug.multi_window")); } /** Called when the configuration has changed, and we want to reset any configuration specific @@ -344,17 +330,12 @@ public class RecentsConfiguration { return !launchedWithNoRecentTasks && (!hasTransposedNavBar || !isLandscape); } - /** Returns whether the current layout is horizontal. */ - public boolean hasHorizontalLayout() { - return isLandscape && hasTransposedSearchBar; - } - /** * Returns the task stack bounds in the current orientation. These bounds do not account for * the system insets. */ - public void getTaskStackBounds(int windowWidth, int windowHeight, int topInset, int rightInset, - Rect taskStackBounds) { + public void getAvailableTaskStackBounds(int windowWidth, int windowHeight, int topInset, + int rightInset, Rect taskStackBounds) { Rect searchBarBounds = new Rect(); getSearchBarBounds(windowWidth, windowHeight, topInset, searchBarBounds); if (isLandscape && hasTransposedSearchBar) { @@ -371,7 +352,7 @@ public class RecentsConfiguration { * the system insets. */ public void getSearchBarBounds(int windowWidth, int windowHeight, int topInset, - Rect searchBarSpaceBounds) { + Rect searchBarSpaceBounds) { // Return empty rects if search is not enabled int searchBarSize = searchBarSpaceHeightPx; if (!Constants.DebugFlags.App.EnableSearchLayout || !hasSearchBarAppWidget()) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java new file mode 100644 index 0000000..4cd577d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.recents; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.app.FragmentManager; +import android.content.Context; +import android.content.DialogInterface; +import android.graphics.Rect; +import android.os.Bundle; +import android.util.Pair; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.Button; +import com.android.systemui.R; +import com.android.systemui.recents.misc.SystemServicesProxy; +import com.android.systemui.recents.model.RecentsTaskLoader; +import com.android.systemui.recents.model.Task; +import com.android.systemui.recents.RecentsActivity; +import com.android.systemui.recents.views.RecentsView; + +import java.util.ArrayList; + +/** + * A helper for the dialogs that show when task debugging is on. + */ +public class RecentsResizeTaskDialog extends DialogFragment { + + static final String TAG = "RecentsResizeTaskDialog"; + + // The various window arrangements we can handle. + private static final int PLACE_LEFT = 1; + private static final int PLACE_RIGHT = 2; + private static final int PLACE_TOP = 3; + private static final int PLACE_BOTTOM = 4; + private static final int PLACE_TOP_LEFT = 5; + private static final int PLACE_TOP_RIGHT = 6; + private static final int PLACE_BOTTOM_LEFT = 7; + private static final int PLACE_BOTTOM_RIGHT = 8; + private static final int PLACE_FULL = 9; + + // The button resource ID combined with the arrangement command. + private static final int[][] BUTTON_DEFINITIONS = + {{R.id.place_left, PLACE_LEFT}, + {R.id.place_right, PLACE_RIGHT}, + {R.id.place_top, PLACE_TOP}, + {R.id.place_bottom, PLACE_BOTTOM}, + {R.id.place_top_left, PLACE_TOP_LEFT}, + {R.id.place_top_right, PLACE_TOP_RIGHT}, + {R.id.place_bottom_left, PLACE_BOTTOM_LEFT}, + {R.id.place_bottom_right, PLACE_BOTTOM_RIGHT}, + {R.id.place_full, PLACE_FULL}}; + + // The task we want to resize. + private FragmentManager mFragmentManager; + private View mResizeTaskDialogContent; + private RecentsActivity mRecentsActivity; + private RecentsView mRecentsView; + private SystemServicesProxy mSsp; + private Rect[] mBounds = {new Rect(), new Rect(), new Rect(), new Rect()}; + private Task[] mTasks = {null, null, null, null}; + + public RecentsResizeTaskDialog(FragmentManager mgr, RecentsActivity activity) { + mFragmentManager = mgr; + mRecentsActivity = activity; + mSsp = RecentsTaskLoader.getInstance().getSystemServicesProxy(); + } + + /** Shows the resize-task dialog. */ + void showResizeTaskDialog(Task mainTask, RecentsView rv) { + mTasks[0] = mainTask; + mRecentsView = rv; + + show(mFragmentManager, TAG); + } + + /** Creates a new resize-task dialog. */ + private void createResizeTaskDialog(final Context context, LayoutInflater inflater, + AlertDialog.Builder builder) { + builder.setTitle(R.string.recents_caption_resize); + mResizeTaskDialogContent = + inflater.inflate(R.layout.recents_task_resize_dialog, null, false); + + for (int i = 0; i < BUTTON_DEFINITIONS.length; i++) { + Button b = (Button)mResizeTaskDialogContent.findViewById(BUTTON_DEFINITIONS[i][0]); + if (b != null) { + final int action = BUTTON_DEFINITIONS[i][1]; + b.setOnClickListener( + new View.OnClickListener() { + public void onClick(View v) { + placeTasks(action); + } + }); + } + } + + builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dismiss(); + } + }); + + builder.setView(mResizeTaskDialogContent); + } + + /** Helper function to place window(s) on the display according to an arrangement request. */ + private void placeTasks(int arrangement) { + Rect rect = mSsp.getWindowRect(); + for (int i = 0; i < mBounds.length; ++i) { + mBounds[i].set(rect); + if (i != 0) { + mTasks[i] = null; + } + } + int additionalTasks = 0; + switch (arrangement) { + case PLACE_LEFT: + mBounds[0].right = mBounds[0].centerX(); + mBounds[1].left = mBounds[0].right; + additionalTasks = 1; + break; + case PLACE_RIGHT: + mBounds[1].right = mBounds[1].centerX(); + mBounds[0].left = mBounds[1].right; + additionalTasks = 1; + break; + case PLACE_TOP: + mBounds[0].bottom = mBounds[0].centerY(); + mBounds[1].top = mBounds[0].bottom; + additionalTasks = 1; + break; + case PLACE_BOTTOM: + mBounds[1].bottom = mBounds[1].centerY(); + mBounds[0].top = mBounds[1].bottom; + additionalTasks = 1; + break; + case PLACE_TOP_LEFT: // TL, TR, BL, BR + mBounds[0].right = mBounds[0].centerX(); + mBounds[0].bottom = mBounds[0].centerY(); + mBounds[1].left = mBounds[0].right; + mBounds[1].bottom = mBounds[0].bottom; + mBounds[2].right = mBounds[0].right; + mBounds[2].top = mBounds[0].bottom; + mBounds[3].left = mBounds[0].right; + mBounds[3].top = mBounds[0].bottom; + additionalTasks = 3; + break; + case PLACE_TOP_RIGHT: // TR, TL, BR, BL + mBounds[0].left = mBounds[0].centerX(); + mBounds[0].bottom = mBounds[0].centerY(); + mBounds[1].right = mBounds[0].left; + mBounds[1].bottom = mBounds[0].bottom; + mBounds[2].left = mBounds[0].left; + mBounds[2].top = mBounds[0].bottom; + mBounds[3].right = mBounds[0].left; + mBounds[3].top = mBounds[0].bottom; + additionalTasks = 3; + break; + case PLACE_BOTTOM_LEFT: // BL, BR, TL, TR + mBounds[0].right = mBounds[0].centerX(); + mBounds[0].top = mBounds[0].centerY(); + mBounds[1].left = mBounds[0].right; + mBounds[1].top = mBounds[0].top; + mBounds[2].right = mBounds[0].right; + mBounds[2].bottom = mBounds[0].top; + mBounds[3].left = mBounds[0].right; + mBounds[3].bottom = mBounds[0].top; + additionalTasks = 3; + break; + case PLACE_BOTTOM_RIGHT: // BR, BL, TR, TL + mBounds[0].left = mBounds[0].centerX(); + mBounds[0].top = mBounds[0].centerY(); + mBounds[1].right = mBounds[0].left; + mBounds[1].top = mBounds[0].top; + mBounds[2].left = mBounds[0].left; + mBounds[2].bottom = mBounds[0].top; + mBounds[3].right = mBounds[0].left; + mBounds[3].bottom = mBounds[0].top; + additionalTasks = 3; + break; + case PLACE_FULL: + // Nothing to change. + break; + } + + // Get the other tasks. + for (int i = 1; i <= additionalTasks && mTasks[i - 1] != null; ++i) { + mTasks[i] = mRecentsView.getNextTaskOrTopTask(mTasks[i - 1]); + // Do stop if we circled back to the first item. + if (mTasks[i] == mTasks[0]) { + mTasks[i] = null; + } + } + + // Resize all tasks beginning from the "oldest" one. + for (int i = additionalTasks; i >= 0; --i) { + if (mTasks[i] != null) { + mSsp.resizeTask(mTasks[i].key.id, mBounds[i]); + } + } + + // Get rid of the dialog. + dismiss(); + mRecentsActivity.dismissRecentsToHomeRaw(false); + + // Show tasks - beginning with the oldest so that the focus ends on the selected one. + // TODO: Remove this once issue b/19893373 is resolved. + for (int i = additionalTasks; i >= 0; --i) { + if (mTasks[i] != null) { + mRecentsView.launchTask(mTasks[i]); + } + } + } + + @Override + public Dialog onCreateDialog(Bundle args) { + final Context context = this.getActivity(); + LayoutInflater inflater = getActivity().getLayoutInflater(); + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + createResizeTaskDialog(context, inflater, builder); + return builder.create(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsUserEventProxyReceiver.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsUserEventProxyReceiver.java index 236da5d..5eefbc7 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsUserEventProxyReceiver.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsUserEventProxyReceiver.java @@ -19,8 +19,6 @@ package com.android.systemui.recents; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import com.android.systemui.recent.Recents; - /** * A proxy for Recents events which happens strictly for non-owner users. @@ -39,28 +37,27 @@ public class RecentsUserEventProxyReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - AlternateRecentsComponent recents = Recents.getRecentsComponent( - context.getApplicationContext(), true); + Recents recents = Recents.getInstanceAndStartIfNeeded(context); switch (intent.getAction()) { case ACTION_PROXY_SHOW_RECENTS_TO_USER: { boolean triggeredFromAltTab = intent.getBooleanExtra( - AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_ALT_TAB, false); - recents.showRecents(triggeredFromAltTab); + Recents.EXTRA_TRIGGERED_FROM_ALT_TAB, false); + recents.showRecentsInternal(triggeredFromAltTab); break; } case ACTION_PROXY_HIDE_RECENTS_TO_USER: { boolean triggeredFromAltTab = intent.getBooleanExtra( - AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_ALT_TAB, false); + Recents.EXTRA_TRIGGERED_FROM_ALT_TAB, false); boolean triggeredFromHome = intent.getBooleanExtra( - AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_HOME_KEY, false); - recents.hideRecents(triggeredFromAltTab, triggeredFromHome); + Recents.EXTRA_TRIGGERED_FROM_HOME_KEY, false); + recents.hideRecentsInternal(triggeredFromAltTab, triggeredFromHome); break; } case ACTION_PROXY_TOGGLE_RECENTS_TO_USER: - recents.toggleRecents(); + recents.toggleRecentsInternal(); break; case ACTION_PROXY_PRELOAD_RECENTS_TO_USER: - recents.preloadRecents(); + recents.preloadRecentsInternal(); break; case ACTION_PROXY_CONFIG_CHANGE_TO_USER: recents.configurationChanged(); diff --git a/packages/SystemUI/src/com/android/systemui/recent/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java index 2fa0b58..cbf5c05 100644 --- a/packages/SystemUI/src/com/android/systemui/recent/ScreenPinningRequest.java +++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.recent; +package com.android.systemui.recents; import android.animation.ArgbEvaluator; import android.animation.ValueAnimator; @@ -41,7 +41,6 @@ import android.widget.LinearLayout; import android.widget.TextView; import com.android.systemui.R; -import com.android.systemui.recents.model.RecentsTaskLoader; import java.util.ArrayList; @@ -145,7 +144,7 @@ public class ScreenPinningRequest implements View.OnClickListener { boolean isLandscape = isLandscapePhone(mContext); inflateView(isLandscape); - int bgColor = mContext.getResources().getColor( + int bgColor = mContext.getColor( R.color.screen_pinning_request_window_bg); if (ActivityManager.isHighEndGfx()) { mLayout.setAlpha(0f); diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java index 90b099c..d60df9c 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java @@ -20,6 +20,7 @@ import android.app.ActivityManager; import android.app.ActivityManagerNative; import android.app.ActivityOptions; import android.app.AppGlobals; +import android.app.IActivityContainer; import android.app.IActivityManager; import android.app.ITaskStackListener; import android.app.SearchManager; @@ -49,21 +50,25 @@ import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings; import android.util.Log; import android.util.Pair; +import android.util.SparseArray; import android.view.Display; import android.view.DisplayInfo; import android.view.SurfaceControl; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; import com.android.systemui.R; -import com.android.systemui.recents.AlternateRecentsComponent; import com.android.systemui.recents.Constants; +import com.android.systemui.recents.Recents; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Random; @@ -214,7 +219,7 @@ public class SystemServicesProxy { } /** Returns a list of the running tasks */ - public List<ActivityManager.RunningTaskInfo> getRunningTasks(int numTasks) { + private List<ActivityManager.RunningTaskInfo> getRunningTasks(int numTasks) { if (mAm == null) return null; return mAm.getRunningTasks(numTasks); } @@ -235,8 +240,8 @@ public class SystemServicesProxy { ComponentName topActivity = topTask.topActivity; // Check if the front most activity is recents - if (topActivity.getPackageName().equals(AlternateRecentsComponent.sRecentsPackage) && - topActivity.getClassName().equals(AlternateRecentsComponent.sRecentsActivity)) { + if (topActivity.getPackageName().equals(Recents.sRecentsPackage) && + topActivity.getClassName().equals(Recents.sRecentsActivity)) { if (isHomeTopMost != null) { isHomeTopMost.set(false); } @@ -250,6 +255,57 @@ public class SystemServicesProxy { return false; } + /** Get the bounds of a stack / task. */ + public Rect getTaskBounds(int stackId) { + ActivityManager.StackInfo info = getAllStackInfos().get(stackId); + if (info != null) + return info.bounds; + return new Rect(); + } + + /** Resize a given task. */ + public void resizeTask(int taskId, Rect bounds) { + if (mIam == null) return; + + try { + mIam.resizeTask(taskId, bounds); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + /** Returns the stack info for all stacks. */ + public SparseArray<ActivityManager.StackInfo> getAllStackInfos() { + if (mIam == null) return new SparseArray<ActivityManager.StackInfo>(); + + try { + SparseArray<ActivityManager.StackInfo> stacks = + new SparseArray<ActivityManager.StackInfo>(); + List<ActivityManager.StackInfo> infos = mIam.getAllStackInfos(); + int stackCount = infos.size(); + for (int i = 0; i < stackCount; i++) { + ActivityManager.StackInfo info = infos.get(i); + stacks.put(info.stackId, info); + } + return stacks; + } catch (RemoteException e) { + e.printStackTrace(); + return new SparseArray<ActivityManager.StackInfo>(); + } + } + + /** Returns the focused stack id. */ + public int getFocusedStack() { + if (mIam == null) return -1; + + try { + return mIam.getFocusedStackId(); + } catch (RemoteException e) { + e.printStackTrace(); + return -1; + } + } + /** Returns whether the specified task is in the home stack */ public boolean isInHomeStack(int taskId) { if (mAm == null) return false; @@ -313,7 +369,7 @@ public class SystemServicesProxy { return thumbnail; } - /** Moves a task to the front with the specified activity options */ + /** Moves a task to the front with the specified activity options. */ public void moveTaskToFront(int taskId, ActivityOptions opts) { if (mAm == null) return; if (Constants.DebugFlags.App.EnableSystemServicesProxy) return; @@ -524,6 +580,13 @@ public class SystemServicesProxy { } /** + * Returns a system property. + */ + public String getSystemProperty(String key) { + return SystemProperties.get(key); + } + + /** * Returns the window rect. */ public Rect getWindowRect() { diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java index e1179fa..84544ff 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java @@ -21,7 +21,6 @@ import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Rect; import android.view.View; -import com.android.systemui.recents.RecentsConfiguration; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -34,7 +33,7 @@ public class Utilities { private static Method sPropertyMethod; static { try { - Class<?> c = Class.forName("android.view.GLES20Canvas"); + Class<?> c = Class.forName("android.view.DisplayListCanvas"); sPropertyMethod = c.getDeclaredMethod("setProperty", String.class, String.class); if (!sPropertyMethod.isAccessible()) sPropertyMethod.setAccessible(true); } catch (ClassNotFoundException e) { @@ -44,19 +43,6 @@ public class Utilities { } } - /** - * Calculates a consistent animation duration (ms) for all animations depending on the movement - * of the object being animated. - */ - public static int calculateTranslationAnimationDuration(int distancePx) { - return calculateTranslationAnimationDuration(distancePx, 100); - } - public static int calculateTranslationAnimationDuration(int distancePx, int minDuration) { - RecentsConfiguration config = RecentsConfiguration.getInstance(); - return Math.max(minDuration, (int) (1000f /* ms/s */ * - (Math.abs(distancePx) / config.animationPxMovementPerSecond))); - } - /** Scales a rect about its centroid */ public static void scaleRectAboutCenter(Rect r, float scale) { if (scale != 1.0f) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java index 0e1c01a..5d98dda 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java @@ -20,9 +20,12 @@ import android.app.ActivityManager; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; +import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.UserHandle; import android.util.Log; +import android.util.SparseArray; +import com.android.systemui.recents.Constants; import com.android.systemui.recents.RecentsConfiguration; import com.android.systemui.recents.misc.SystemServicesProxy; @@ -34,11 +37,11 @@ import java.util.List; /** * This class stores the loading state as it goes through multiple stages of loading: - * - preloadRawTasks() will load the raw set of recents tasks from the system - * - preloadPlan() will construct a new task stack with all metadata and only icons and thumbnails - * that are currently in the cache - * - executePlan() will actually load and fill in the icons and thumbnails according to the load - * options specified, such that we can transition into the Recents activity seamlessly + * 1) preloadRawTasks() will load the raw set of recents tasks from the system + * 2) preloadPlan() will construct a new task stack with all metadata and only icons and + * thumbnails that are currently in the cache + * 3) executePlan() will actually load and fill in the icons and thumbnails according to the load + * options specified, such that we can transition into the Recents activity seamlessly */ public class RecentsTaskLoadPlan { static String TAG = "RecentsTaskLoadPlan"; @@ -60,7 +63,7 @@ public class RecentsTaskLoadPlan { SystemServicesProxy mSystemServicesProxy; List<ActivityManager.RecentTaskInfo> mRawTasks; - TaskStack mStack; + SparseArray<TaskStack> mStacks = new SparseArray<>(); HashMap<Task.ComponentNameKey, ActivityInfoHandle> mActivityInfoCache = new HashMap<Task.ComponentNameKey, ActivityInfoHandle>(); @@ -90,21 +93,28 @@ public class RecentsTaskLoadPlan { synchronized void preloadPlan(RecentsTaskLoader loader, boolean isTopTaskHome) { if (DEBUG) Log.d(TAG, "preloadPlan"); + // This activity info cache will be used for both preloadPlan() and executePlan() mActivityInfoCache.clear(); - mStack = new TaskStack(); + + // TODO (multi-display): Currently assume the primary display + Rect displayBounds = mSystemServicesProxy.getWindowRect(); Resources res = mContext.getResources(); - ArrayList<Task> loadedTasks = new ArrayList<Task>(); + SparseArray<ArrayList<Task>> stacksTasks = new SparseArray<>(); if (mRawTasks == null) { preloadRawTasks(isTopTaskHome); } + int firstStackId = -1; int taskCount = mRawTasks.size(); for (int i = 0; i < taskCount; i++) { ActivityManager.RecentTaskInfo t = mRawTasks.get(i); + if (firstStackId < 0) { + firstStackId = t.stackId; + } // Compose the task key - Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.baseIntent, t.userId, - t.firstActiveTime, t.lastActiveTime); + Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.stackId, t.baseIntent, + t.userId, t.firstActiveTime, t.lastActiveTime); // Get an existing activity info handle if possible Task.ComponentNameKey cnKey = taskKey.getComponentNameKey(); @@ -143,14 +153,43 @@ public class RecentsTaskLoadPlan { iconFilename); task.thumbnail = loader.getAndUpdateThumbnail(taskKey, mSystemServicesProxy, false); if (DEBUG) Log.d(TAG, "\tthumbnail: " + taskKey + ", " + task.thumbnail); - loadedTasks.add(task); + + if (!mConfig.multiStackEnabled || + Constants.DebugFlags.App.EnableMultiStackToSingleStack) { + firstStackId = 0; + ArrayList<Task> stackTasks = stacksTasks.get(firstStackId); + if (stackTasks == null) { + stackTasks = new ArrayList<Task>(); + stacksTasks.put(firstStackId, stackTasks); + } + stackTasks.add(task); + } else { + ArrayList<Task> stackTasks = stacksTasks.get(t.stackId); + if (stackTasks == null) { + stackTasks = new ArrayList<Task>(); + stacksTasks.put(t.stackId, stackTasks); + } + stackTasks.add(task); + } } - mStack.setTasks(loadedTasks); - mStack.createAffiliatedGroupings(mConfig); - // Assertion - if (mStack.getTaskCount() != mRawTasks.size()) { - throw new RuntimeException("Loading failed"); + // Initialize the stacks + SparseArray<ActivityManager.StackInfo> stackInfos = mSystemServicesProxy.getAllStackInfos(); + mStacks.clear(); + int stackCount = stacksTasks.size(); + for (int i = 0; i < stackCount; i++) { + int stackId = stacksTasks.keyAt(i); + ActivityManager.StackInfo info = stackInfos.get(stackId); + ArrayList<Task> stackTasks = stacksTasks.valueAt(i); + TaskStack stack = new TaskStack(stackId); + if (Constants.DebugFlags.App.EnableMultiStackToSingleStack) { + stack.setBounds(displayBounds, displayBounds); + } else { + stack.setBounds(info.bounds, displayBounds); + } + stack.setTasks(stackTasks); + stack.createAffiliatedGroupings(mConfig); + mStacks.put(stackId, stack); } } @@ -166,72 +205,93 @@ public class RecentsTaskLoadPlan { Resources res = mContext.getResources(); // Iterate through each of the tasks and load them according to the load conditions. - ArrayList<Task> tasks = mStack.getTasks(); - int taskCount = tasks.size(); - for (int i = 0; i < taskCount; i++) { - ActivityManager.RecentTaskInfo t = mRawTasks.get(i); - Task task = tasks.get(i); - Task.TaskKey taskKey = task.key; + int stackCount = mStacks.size(); + for (int j = 0; j < stackCount; j++) { + ArrayList<Task> tasks = mStacks.valueAt(j).getTasks(); + int taskCount = tasks.size(); + for (int i = 0; i < taskCount; i++) { + ActivityManager.RecentTaskInfo t = mRawTasks.get(i); + Task task = tasks.get(i); + Task.TaskKey taskKey = task.key; - // Get an existing activity info handle if possible - Task.ComponentNameKey cnKey = taskKey.getComponentNameKey(); - ActivityInfoHandle infoHandle; - boolean hadCachedActivityInfo = false; - if (mActivityInfoCache.containsKey(cnKey)) { - infoHandle = mActivityInfoCache.get(cnKey); - hadCachedActivityInfo = true; - } else { - infoHandle = new ActivityInfoHandle(); - } + // Get an existing activity info handle if possible + Task.ComponentNameKey cnKey = taskKey.getComponentNameKey(); + ActivityInfoHandle infoHandle; + boolean hadCachedActivityInfo = false; + if (mActivityInfoCache.containsKey(cnKey)) { + infoHandle = mActivityInfoCache.get(cnKey); + hadCachedActivityInfo = true; + } else { + infoHandle = new ActivityInfoHandle(); + } - boolean isRunningTask = (task.key.id == opts.runningTaskId); - boolean isVisibleTask = i >= (taskCount - opts.numVisibleTasks); - boolean isVisibleThumbnail = i >= (taskCount - opts.numVisibleTaskThumbnails); + boolean isRunningTask = (task.key.id == opts.runningTaskId); + boolean isVisibleTask = i >= (taskCount - opts.numVisibleTasks); + boolean isVisibleThumbnail = i >= (taskCount - opts.numVisibleTaskThumbnails); - // If requested, skip the running task - if (opts.onlyLoadPausedActivities && isRunningTask) { - continue; - } + // If requested, skip the running task + if (opts.onlyLoadPausedActivities && isRunningTask) { + continue; + } - if (opts.loadIcons && (isRunningTask || isVisibleTask)) { - if (task.activityIcon == null) { - if (DEBUG) Log.d(TAG, "\tLoading icon: " + taskKey); - task.activityIcon = loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, - mSystemServicesProxy, res, infoHandle, true); + if (opts.loadIcons && (isRunningTask || isVisibleTask)) { + if (task.activityIcon == null) { + if (DEBUG) Log.d(TAG, "\tLoading icon: " + taskKey); + task.activityIcon = loader.getAndUpdateActivityIcon(taskKey, + t.taskDescription, mSystemServicesProxy, res, infoHandle, true); + } } - } - if (opts.loadThumbnails && (isRunningTask || isVisibleThumbnail)) { - if (task.thumbnail == null || isRunningTask) { - if (DEBUG) Log.d(TAG, "\tLoading thumbnail: " + taskKey); - if (mConfig.svelteLevel <= RecentsConfiguration.SVELTE_LIMIT_CACHE) { - task.thumbnail = loader.getAndUpdateThumbnail(taskKey, mSystemServicesProxy, - true); - } else if (mConfig.svelteLevel == RecentsConfiguration.SVELTE_DISABLE_CACHE) { - loadQueue.addTask(task); + if (opts.loadThumbnails && (isRunningTask || isVisibleThumbnail)) { + if (task.thumbnail == null || isRunningTask) { + if (DEBUG) Log.d(TAG, "\tLoading thumbnail: " + taskKey); + if (mConfig.svelteLevel <= RecentsConfiguration.SVELTE_LIMIT_CACHE) { + task.thumbnail = loader.getAndUpdateThumbnail(taskKey, + mSystemServicesProxy, true); + } else if (mConfig.svelteLevel == RecentsConfiguration.SVELTE_DISABLE_CACHE) { + loadQueue.addTask(task); + } } } - } - // Update the activity info cache - if (!hadCachedActivityInfo && infoHandle.info != null) { - mActivityInfoCache.put(cnKey, infoHandle); + // Update the activity info cache + if (!hadCachedActivityInfo && infoHandle.info != null) { + mActivityInfoCache.put(cnKey, infoHandle); + } } } } /** - * Composes and returns a TaskStack from the preloaded list of recent tasks. + * Returns all TaskStacks from the preloaded list of recent tasks. */ - public TaskStack getTaskStack() { - return mStack; + public ArrayList<TaskStack> getAllTaskStacks() { + ArrayList<TaskStack> stacks = new ArrayList<TaskStack>(); + int stackCount = mStacks.size(); + for (int i = 0; i < stackCount; i++) { + stacks.add(mStacks.valueAt(i)); + } + // Ensure that we have at least one stack + if (stacks.isEmpty()) { + stacks.add(new TaskStack()); + } + return stacks; } /** - * Composes and returns a SpaceNode from the preloaded list of recent tasks. + * Returns a specific TaskStack from the preloaded list of recent tasks. */ - public SpaceNode getSpaceNode() { - SpaceNode node = new SpaceNode(); - node.setStack(mStack); - return node; + public TaskStack getTaskStack(int stackId) { + return mStacks.get(stackId); + } + + /** Returns whether there are any tasks in any stacks. */ + public boolean hasTasks() { + int stackCount = mStacks.size(); + for (int i = 0; i < stackCount; i++) { + if (mStacks.valueAt(i).getTaskCount() > 0) { + return true; + } + } + return false; } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java index ba2903a..3192fe6 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java @@ -33,7 +33,6 @@ import com.android.systemui.recents.Constants; import com.android.systemui.recents.RecentsConfiguration; import com.android.systemui.recents.misc.SystemServicesProxy; -import java.util.Collection; import java.util.concurrent.ConcurrentLinkedQueue; @@ -47,18 +46,6 @@ class TaskResourceLoadQueue { ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<Task>(); /** Adds a new task to the load queue */ - void addTasks(Collection<Task> tasks) { - for (Task t : tasks) { - if (!mQueue.contains(t)) { - mQueue.add(t); - } - } - synchronized(this) { - notifyAll(); - } - } - - /** Adds a new task to the load queue */ void addTask(Task t) { if (!mQueue.contains(t)) { mQueue.add(t); diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java b/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java deleted file mode 100644 index 831698a..0000000 --- a/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.recents.model; - -import android.graphics.Rect; - -import java.util.ArrayList; - - -/** - * The full recents space is partitioned using a BSP into various nodes that define where task - * stacks should be placed. - */ -public class SpaceNode { - /* BSP node callbacks */ - public interface SpaceNodeCallbacks { - /** Notifies when a node is added */ - public void onSpaceNodeAdded(SpaceNode node); - /** Notifies when a node is measured */ - public void onSpaceNodeMeasured(SpaceNode node, Rect rect); - } - - SpaceNode mStartNode; - SpaceNode mEndNode; - - TaskStack mStack; - - public SpaceNode() { - // Do nothing - } - - /** Sets the current stack for this space node */ - public void setStack(TaskStack stack) { - mStack = stack; - } - - /** Returns the task stack (not null if this is a leaf) */ - TaskStack getStack() { - return mStack; - } - - /** Returns whether there are any tasks in any stacks below this node. */ - public boolean hasTasks() { - return (mStack.getTaskCount() > 0) || - (mStartNode != null && mStartNode.hasTasks()) || - (mEndNode != null && mEndNode.hasTasks()); - } - - /** Returns whether this is a leaf node */ - boolean isLeafNode() { - return (mStartNode == null) && (mEndNode == null); - } - - /** Returns all the descendent task stacks */ - private void getStacksRec(ArrayList<TaskStack> stacks) { - if (isLeafNode()) { - stacks.add(mStack); - } else { - mStartNode.getStacksRec(stacks); - mEndNode.getStacksRec(stacks); - } - } - public ArrayList<TaskStack> getStacks() { - ArrayList<TaskStack> stacks = new ArrayList<TaskStack>(); - getStacksRec(stacks); - return stacks; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java index 55dfe45..0cd55d7 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java @@ -36,6 +36,9 @@ public class Task { public void onTaskDataLoaded(); /* Notifies when a task has been unbound */ public void onTaskDataUnloaded(); + + /* Notifies when a task's stack id has changed. */ + public void onMultiStackDebugTaskStackIdChanged(); } /** The ComponentNameKey represents the unique primary key for a component @@ -68,14 +71,17 @@ public class Task { public static class TaskKey { final ComponentNameKey mComponentNameKey; public final int id; + public int stackId; public final Intent baseIntent; public final int userId; public long firstActiveTime; public long lastActiveTime; - public TaskKey(int id, Intent intent, int userId, long firstActiveTime, long lastActiveTime) { + public TaskKey(int id, int stackId, Intent intent, int userId, long firstActiveTime, + long lastActiveTime) { mComponentNameKey = new ComponentNameKey(intent.getComponent(), userId); this.id = id; + this.stackId = stackId; this.baseIntent = intent; this.userId = userId; this.firstActiveTime = firstActiveTime; @@ -92,18 +98,19 @@ public class Task { if (!(o instanceof TaskKey)) { return false; } - return id == ((TaskKey) o).id - && userId == ((TaskKey) o).userId; + TaskKey otherKey = (TaskKey) o; + return id == otherKey.id && stackId == otherKey.stackId && userId == otherKey.userId; } @Override public int hashCode() { - return (id << 5) + userId; + return Objects.hash(id, stackId, userId); } @Override public String toString() { return "Task.Key: " + id + ", " + + "s: " + stackId + ", " + "u: " + userId + ", " + "lat: " + lastActiveTime + ", " + baseIntent.getComponent().getPackageName(); @@ -180,6 +187,14 @@ public class Task { this.group = group; } + /** Updates the stack id of this task. */ + public void setStackId(int stackId) { + key.stackId = stackId; + if (mCb != null) { + mCb.onMultiStackDebugTaskStackIdChanged(); + } + } + /** Notifies the callback listeners that this task has been loaded */ public void notifyTaskDataLoaded(Bitmap thumbnail, Drawable applicationIcon) { this.applicationIcon = applicationIcon; diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java index 255d642..5aaea15 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java @@ -17,6 +17,7 @@ package com.android.systemui.recents.model; import android.graphics.Color; +import android.graphics.Rect; import com.android.systemui.recents.Constants; import com.android.systemui.recents.RecentsConfiguration; import com.android.systemui.recents.misc.NamedCounter; @@ -165,39 +166,46 @@ public class TaskStack { public void onStackTaskAdded(TaskStack stack, Task t); /* Notifies when a task has been removed from the stack */ public void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask); + /* Notifies when all task has been removed from the stack */ + public void onStackAllTasksRemoved(TaskStack stack, ArrayList<Task> removedTasks); /** Notifies when the stack was filtered */ public void onStackFiltered(TaskStack newStack, ArrayList<Task> curTasks, Task t); /** Notifies when the stack was un-filtered */ public void onStackUnfiltered(TaskStack newStack, ArrayList<Task> curTasks); } - /** A pair of indices representing the group and task positions in the stack and group. */ - public static class GroupTaskIndex { - public int groupIndex; // Index in the stack - public int taskIndex; // Index in the group - - public GroupTaskIndex() {} - - public GroupTaskIndex(int gi, int ti) { - groupIndex = gi; - taskIndex = ti; - } - } - // The task offset to apply to a task id as a group affiliation static final int IndividualTaskIdOffset = 1 << 16; + public final int id; + public final Rect stackBounds = new Rect(); + public final Rect displayBounds = new Rect(); + FilteredTaskList mTaskList = new FilteredTaskList(); TaskStackCallbacks mCb; ArrayList<TaskGrouping> mGroups = new ArrayList<TaskGrouping>(); HashMap<Integer, TaskGrouping> mAffinitiesGroups = new HashMap<Integer, TaskGrouping>(); - /** Sets the callbacks for this task stack */ + public TaskStack() { + this(0); + } + + public TaskStack(int stackId) { + id = stackId; + } + + /** Sets the callbacks for this task stack. */ public void setCallbacks(TaskStackCallbacks cb) { mCb = cb; } + /** Sets the bounds of this stack. */ + public void setBounds(Rect stackBounds, Rect displayBounds) { + this.stackBounds.set(stackBounds); + this.displayBounds.set(displayBounds); + } + /** Resets this TaskStack. */ public void reset() { mCb = null; @@ -214,19 +222,24 @@ public class TaskStack { } } + /** Does the actual work associated with removing the task. */ + void removeTaskImpl(Task t) { + // Remove the task from the list + mTaskList.remove(t); + // Remove it from the group as well, and if it is empty, remove the group + TaskGrouping group = t.group; + group.removeTask(t); + if (group.getTaskCount() == 0) { + removeGroup(group); + } + // Update the lock-to-app state + t.lockToThisTask = false; + } + /** Removes a task */ public void removeTask(Task t) { if (mTaskList.contains(t)) { - // Remove the task from the list - mTaskList.remove(t); - // Remove it from the group as well, and if it is empty, remove the group - TaskGrouping group = t.group; - group.removeTask(t); - if (group.getTaskCount() == 0) { - removeGroup(group); - } - // Update the lock-to-app state - t.lockToThisTask = false; + removeTaskImpl(t); Task newFrontMostTask = getFrontMostTask(); if (newFrontMostTask != null && newFrontMostTask.lockToTaskEnabled) { newFrontMostTask.lockToThisTask = true; @@ -238,20 +251,27 @@ public class TaskStack { } } + /** Removes all tasks */ + public void removeAllTasks() { + ArrayList<Task> taskList = new ArrayList<Task>(mTaskList.getTasks()); + int taskCount = taskList.size(); + for (int i = taskCount - 1; i >= 0; i--) { + Task t = taskList.get(i); + removeTaskImpl(t); + } + if (mCb != null) { + // Notify that all tasks have been removed + mCb.onStackAllTasksRemoved(this, taskList); + } + } + /** Sets a few tasks in one go */ public void setTasks(List<Task> tasks) { ArrayList<Task> taskList = mTaskList.getTasks(); int taskCount = taskList.size(); - for (int i = 0; i < taskCount; i++) { + for (int i = taskCount - 1; i >= 0; i--) { Task t = taskList.get(i); - // Remove the task from the list - mTaskList.remove(t); - // Remove it from the group as well, and if it is empty, remove the group - TaskGrouping group = t.group; - group.removeTask(t); - if (group.getTaskCount() == 0) { - removeGroup(group); - } + removeTaskImpl(t); if (mCb != null) { // Notify that a task has been removed mCb.onStackTaskRemoved(this, t, null); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FakeShadowDrawable.java b/packages/SystemUI/src/com/android/systemui/recents/views/FakeShadowDrawable.java index 72f9001..509ad1b 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/FakeShadowDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/FakeShadowDrawable.java @@ -159,9 +159,9 @@ class FakeShadowDrawable extends Drawable { } @Override - public void setColorFilter(ColorFilter cf) { - mCornerShadowPaint.setColorFilter(cf); - mEdgeShadowPaint.setColorFilter(cf); + public void setColorFilter(ColorFilter colorFilter) { + mCornerShadowPaint.setColorFilter(colorFilter); + mEdgeShadowPaint.setColorFilter(colorFilter); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java index ee79242..1377975 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java @@ -29,10 +29,12 @@ import android.provider.Settings; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewStub; import android.view.WindowInsets; import android.widget.FrameLayout; - +import com.android.systemui.R; import com.android.systemui.recents.Constants; +import com.android.systemui.recents.RecentsAppWidgetHostView; import com.android.systemui.recents.RecentsConfiguration; import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.recents.model.RecentsPackageMonitor; @@ -41,6 +43,7 @@ import com.android.systemui.recents.model.Task; import com.android.systemui.recents.model.TaskStack; import java.util.ArrayList; +import java.util.List; /** * This view is the the top level layout that contains TaskStacks (which are laid out according @@ -56,14 +59,18 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV public void onAllTaskViewsDismissed(); public void onExitToHomeAnimationTriggered(); public void onScreenPinningRequest(); + + public void onTaskResize(Task t); } RecentsConfiguration mConfig; LayoutInflater mInflater; DebugOverlayView mDebugOverlay; + RecentsViewLayoutAlgorithm mLayoutAlgorithm; ArrayList<TaskStack> mStacks; - View mSearchBar; + List<TaskStackView> mTaskStackViews = new ArrayList<>(); + RecentsAppWidgetHostView mSearchBar; RecentsViewCallbacks mCb; public RecentsView(Context context) { @@ -82,6 +89,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV super(context, attrs, defStyleAttr, defStyleRes); mConfig = RecentsConfiguration.getInstance(); mInflater = LayoutInflater.from(context); + mLayoutAlgorithm = new RecentsViewLayoutAlgorithm(mConfig); } /** Sets the callbacks */ @@ -98,29 +106,18 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV public void setTaskStacks(ArrayList<TaskStack> stacks) { int numStacks = stacks.size(); - // Make a list of the stack view children only - ArrayList<TaskStackView> stackViews = new ArrayList<TaskStackView>(); - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child != mSearchBar) { - stackViews.add((TaskStackView) child); - } - } - // Remove all/extra stack views int numTaskStacksToKeep = 0; // Keep no tasks if we are recreating the layout if (mConfig.launchedReuseTaskStackViews) { - numTaskStacksToKeep = Math.min(childCount, numStacks); + numTaskStacksToKeep = Math.min(mTaskStackViews.size(), numStacks); } - for (int i = stackViews.size() - 1; i >= numTaskStacksToKeep; i--) { - removeView(stackViews.get(i)); - stackViews.remove(i); + for (int i = mTaskStackViews.size() - 1; i >= numTaskStacksToKeep; i--) { + removeView(mTaskStackViews.remove(i)); } // Update the stack views that we are keeping for (int i = 0; i < numTaskStacksToKeep; i++) { - TaskStackView tsv = stackViews.get(i); + TaskStackView tsv = mTaskStackViews.get(i); // If onRecentsHidden is not triggered, we need to the stack view again here tsv.reset(); tsv.setStack(stacks.get(i)); @@ -128,21 +125,19 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV // Add remaining/recreate stack views mStacks = stacks; - for (int i = stackViews.size(); i < numStacks; i++) { + for (int i = mTaskStackViews.size(); i < numStacks; i++) { TaskStack stack = stacks.get(i); TaskStackView stackView = new TaskStackView(getContext(), stack); stackView.setCallbacks(this); addView(stackView); + mTaskStackViews.add(stackView); } // Enable debug mode drawing on all the stacks if necessary if (mConfig.debugModeEnabled) { - for (int i = childCount - 1; i >= 0; i--) { - View v = getChildAt(i); - if (v != mSearchBar) { - TaskStackView stackView = (TaskStackView) v; - stackView.setDebugOverlay(mDebugOverlay); - } + for (int i = mTaskStackViews.size() - 1; i >= 0; i--) { + TaskStackView stackView = mTaskStackViews.get(i); + stackView.setDebugOverlay(mDebugOverlay); } } @@ -150,24 +145,75 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV requestLayout(); } + /** Gets the list of task views */ + List<TaskStackView> getTaskStackViews() { + return mTaskStackViews; + } + + /** Gets the next task in the stack - or if the last - the top task */ + public Task getNextTaskOrTopTask(Task taskToSearch) { + Task returnTask = null; + boolean found = false; + List<TaskStackView> stackViews = getTaskStackViews(); + int stackCount = stackViews.size(); + for (int i = stackCount - 1; i >= 0; --i) { + TaskStack stack = stackViews.get(i).getStack(); + ArrayList<Task> taskList = stack.getTasks(); + // Iterate the stack views and try and find the focused task + for (int j = taskList.size() - 1; j >= 0; --j) { + Task task = taskList.get(j); + // Return the next task in the line. + if (found) + return task; + // Remember the first possible task as the top task. + if (returnTask == null) + returnTask = task; + if (task == taskToSearch) + found = true; + } + } + return returnTask; + } + /** Launches the focused task from the first stack if possible */ public boolean launchFocusedTask() { // Get the first stack view - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child != mSearchBar) { - TaskStackView stackView = (TaskStackView) child; - TaskStack stack = stackView.mStack; - // Iterate the stack views and try and find the focused task - int taskCount = stackView.getChildCount(); - for (int j = 0; j < taskCount; j++) { - TaskView tv = (TaskView) stackView.getChildAt(j); - Task task = tv.getTask(); - if (tv.isFocusedTask()) { - onTaskViewClicked(stackView, tv, stack, task, false); - return true; - } + List<TaskStackView> stackViews = getTaskStackViews(); + int stackCount = stackViews.size(); + for (int i = 0; i < stackCount; i++) { + TaskStackView stackView = stackViews.get(i); + TaskStack stack = stackView.getStack(); + // Iterate the stack views and try and find the focused task + List<TaskView> taskViews = stackView.getTaskViews(); + int taskViewCount = taskViews.size(); + for (int j = 0; j < taskViewCount; j++) { + TaskView tv = taskViews.get(j); + Task task = tv.getTask(); + if (tv.isFocusedTask()) { + onTaskViewClicked(stackView, tv, stack, task, false); + return true; + } + } + } + return false; + } + + /** Launches a given task. */ + public boolean launchTask(Task task) { + // Get the first stack view + List<TaskStackView> stackViews = getTaskStackViews(); + int stackCount = stackViews.size(); + for (int i = 0; i < stackCount; i++) { + TaskStackView stackView = stackViews.get(i); + TaskStack stack = stackView.getStack(); + // Iterate the stack views and try and find the given task. + List<TaskView> taskViews = stackView.getTaskViews(); + int taskViewCount = taskViews.size(); + for (int j = 0; j < taskViewCount; j++) { + TaskView tv = taskViews.get(j); + if (tv.getTask() == task) { + onTaskViewClicked(stackView, tv, stack, task, false); + return true; } } } @@ -177,24 +223,22 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV /** Launches the task that Recents was launched from, if possible */ public boolean launchPreviousTask() { // Get the first stack view - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child != mSearchBar) { - TaskStackView stackView = (TaskStackView) child; - TaskStack stack = stackView.mStack; - ArrayList<Task> tasks = stack.getTasks(); - - // Find the launch task in the stack - if (!tasks.isEmpty()) { - int taskCount = tasks.size(); - for (int j = 0; j < taskCount; j++) { - if (tasks.get(j).isLaunchTarget) { - Task task = tasks.get(j); - TaskView tv = stackView.getChildViewForTask(task); - onTaskViewClicked(stackView, tv, stack, task, false); - return true; - } + List<TaskStackView> stackViews = getTaskStackViews(); + int stackCount = stackViews.size(); + for (int i = 0; i < stackCount; i++) { + TaskStackView stackView = stackViews.get(i); + TaskStack stack = stackView.getStack(); + ArrayList<Task> tasks = stack.getTasks(); + + // Find the launch task in the stack + if (!tasks.isEmpty()) { + int taskCount = tasks.size(); + for (int j = 0; j < taskCount; j++) { + if (tasks.get(j).isLaunchTarget) { + Task task = tasks.get(j); + TaskView tv = stackView.getChildViewForTask(task); + onTaskViewClicked(stackView, tv, stack, task, false); + return true; } } } @@ -208,13 +252,11 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV // to ensure that it runs ctx.postAnimationTrigger.increment(); - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child != mSearchBar) { - TaskStackView stackView = (TaskStackView) child; - stackView.startEnterRecentsAnimation(ctx); - } + List<TaskStackView> stackViews = getTaskStackViews(); + int stackCount = stackViews.size(); + for (int i = 0; i < stackCount; i++) { + TaskStackView stackView = stackViews.get(i); + stackView.startEnterRecentsAnimation(ctx); } ctx.postAnimationTrigger.decrement(); } @@ -224,13 +266,11 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV // We have to increment/decrement the post animation trigger in case there are no children // to ensure that it runs ctx.postAnimationTrigger.increment(); - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child != mSearchBar) { - TaskStackView stackView = (TaskStackView) child; - stackView.startExitToHomeAnimation(ctx); - } + List<TaskStackView> stackViews = getTaskStackViews(); + int stackCount = stackViews.size(); + for (int i = 0; i < stackCount; i++) { + TaskStackView stackView = stackViews.get(i); + stackView.startExitToHomeAnimation(ctx); } ctx.postAnimationTrigger.decrement(); @@ -239,7 +279,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV } /** Adds the search bar */ - public void setSearchBar(View searchBar) { + public void setSearchBar(RecentsAppWidgetHostView searchBar) { // Create the search bar (and hide it if we have no recent tasks) if (Constants.DebugFlags.App.EnableSearchLayout) { // Remove the previous search bar if one exists @@ -255,8 +295,8 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV } /** Returns whether there is currently a search bar */ - public boolean hasSearchBar() { - return mSearchBar != null; + public boolean hasValidSearchBar() { + return mSearchBar != null && !mSearchBar.isReinflateRequired(); } /** Sets the visibility of the search bar */ @@ -286,19 +326,23 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV } Rect taskStackBounds = new Rect(); - mConfig.getTaskStackBounds(width, height, mConfig.systemInsets.top, + mConfig.getAvailableTaskStackBounds(width, height, mConfig.systemInsets.top, mConfig.systemInsets.right, taskStackBounds); - // Measure each TaskStackView with the full width and height of the window since the + // Measure each TaskStackView with the full width and height of the window since the // transition view is a child of that stack view - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child != mSearchBar && child.getVisibility() != GONE) { - TaskStackView tsv = (TaskStackView) child; - // Set the insets to be the top/left inset + search bounds - tsv.setStackInsetRect(taskStackBounds); - tsv.measure(widthMeasureSpec, heightMeasureSpec); + List<TaskStackView> stackViews = getTaskStackViews(); + List<Rect> stackViewsBounds = mLayoutAlgorithm.computeStackRects(stackViews, + taskStackBounds); + int stackCount = stackViews.size(); + for (int i = 0; i < stackCount; i++) { + TaskStackView stackView = stackViews.get(i); + if (stackView.getVisibility() != GONE) { + // We are going to measure the TaskStackView with the whole RecentsView dimensions, + // but the actual stack is going to be inset to the bounds calculated by the layout + // algorithm + stackView.setStackInsetRect(stackViewsBounds.get(i)); + stackView.measure(widthMeasureSpec, heightMeasureSpec); } } @@ -321,12 +365,13 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV // Layout each TaskStackView with the full width and height of the window since the // transition view is a child of that stack view - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child != mSearchBar && child.getVisibility() != GONE) { - child.layout(left, top, left + child.getMeasuredWidth(), - top + child.getMeasuredHeight()); + List<TaskStackView> stackViews = getTaskStackViews(); + int stackCount = stackViews.size(); + for (int i = 0; i < stackCount; i++) { + TaskStackView stackView = stackViews.get(i); + if (stackView.getVisibility() != GONE) { + stackView.layout(left, top, left + stackView.getMeasuredWidth(), + top + stackView.getMeasuredHeight()); } } } @@ -342,41 +387,29 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV /** Notifies each task view of the user interaction. */ public void onUserInteraction() { // Get the first stack view - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child != mSearchBar) { - TaskStackView stackView = (TaskStackView) child; - stackView.onUserInteraction(); - } + List<TaskStackView> stackViews = getTaskStackViews(); + int stackCount = stackViews.size(); + for (int i = 0; i < stackCount; i++) { + TaskStackView stackView = stackViews.get(i); + stackView.onUserInteraction(); } } /** Focuses the next task in the first stack view */ public void focusNextTask(boolean forward) { // Get the first stack view - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child != mSearchBar) { - TaskStackView stackView = (TaskStackView) child; - stackView.focusNextTask(forward, true); - break; - } + List<TaskStackView> stackViews = getTaskStackViews(); + if (!stackViews.isEmpty()) { + stackViews.get(0).focusNextTask(forward, true); } } /** Dismisses the focused task. */ public void dismissFocusedTask() { // Get the first stack view - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child != mSearchBar) { - TaskStackView stackView = (TaskStackView) child; - stackView.dismissFocusedTask(); - break; - } + List<TaskStackView> stackViews = getTaskStackViews(); + if (!stackViews.isEmpty()) { + stackViews.get(0).dismissFocusedTask(); } } @@ -475,9 +508,16 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV } }; } - opts = ActivityOptions.makeThumbnailAspectScaleUpAnimation(sourceView, - b, offsetX, offsetY, transform.rect.width(), transform.rect.height(), - sourceView.getHandler(), animStartedListener); + if (mConfig.multiStackEnabled) { + opts = ActivityOptions.makeCustomAnimation(sourceView.getContext(), + R.anim.recents_from_unknown_enter, + R.anim.recents_from_unknown_exit, + sourceView.getHandler(), animStartedListener); + } else { + opts = ActivityOptions.makeThumbnailAspectScaleUpAnimation(sourceView, + b, offsetX, offsetY, transform.rect.width(), transform.rect.height(), + sourceView.getHandler(), animStartedListener); + } } final ActivityOptions launchOpts = opts; @@ -542,24 +582,29 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV loader.deleteTaskData(t, false); // Remove the old task from activity manager - RecentsTaskLoader.getInstance().getSystemServicesProxy().removeTask(t.key.id); + loader.getSystemServicesProxy().removeTask(t.key.id); } @Override - public void onAllTaskViewsDismissed() { + public void onAllTaskViewsDismissed(ArrayList<Task> removedTasks) { + if (removedTasks != null) { + int taskCount = removedTasks.size(); + for (int i = 0; i < taskCount; i++) { + onTaskViewDismissed(removedTasks.get(i)); + } + } + mCb.onAllTaskViewsDismissed(); } /** Final callback after Recents is finally hidden. */ public void onRecentsHidden() { // Notify each task stack view - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child != mSearchBar) { - TaskStackView stackView = (TaskStackView) child; - stackView.onRecentsHidden(); - } + List<TaskStackView> stackViews = getTaskStackViews(); + int stackCount = stackViews.size(); + for (int i = 0; i < stackCount; i++) { + TaskStackView stackView = stackViews.get(i); + stackView.onRecentsHidden(); } } @@ -591,18 +636,23 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV } } + @Override + public void onTaskResize(Task t) { + if (mCb != null) { + mCb.onTaskResize(t); + } + } + /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/ @Override public void onPackagesChanged(RecentsPackageMonitor monitor, String packageName, int userId) { // Propagate this event down to each task stack view - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child != mSearchBar) { - TaskStackView stackView = (TaskStackView) child; - stackView.onPackagesChanged(monitor, packageName, userId); - } + List<TaskStackView> stackViews = getTaskStackViews(); + int stackCount = stackViews.size(); + for (int i = 0; i < stackCount; i++) { + TaskStackView stackView = stackViews.get(i); + stackView.onPackagesChanged(monitor, packageName, userId); } } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewLayoutAlgorithm.java new file mode 100644 index 0000000..eea273c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewLayoutAlgorithm.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.recents.views; + +import android.graphics.Rect; +import com.android.systemui.recents.RecentsConfiguration; +import com.android.systemui.recents.model.TaskStack; + +import java.util.ArrayList; +import java.util.List; + +/* The layout logic for the RecentsView. */ +public class RecentsViewLayoutAlgorithm { + + RecentsConfiguration mConfig; + + public RecentsViewLayoutAlgorithm(RecentsConfiguration config) { + mConfig = config; + } + + /** Return the relative coordinate given coordinates in another size. */ + private int getRelativeCoordinate(int availableOffset, int availableSize, int otherCoord, int otherSize) { + float relPos = (float) otherCoord / otherSize; + return availableOffset + (int) (relPos * availableSize); + } + + /** + * Computes and returns the bounds that each of the stack views should take up. + */ + List<Rect> computeStackRects(List<TaskStackView> stackViews, Rect availableBounds) { + ArrayList<Rect> bounds = new ArrayList<Rect>(stackViews.size()); + int stackViewsCount = stackViews.size(); + for (int i = 0; i < stackViewsCount; i++) { + TaskStack stack = stackViews.get(i).getStack(); + Rect sb = stack.stackBounds; + Rect db = stack.displayBounds; + Rect ab = availableBounds; + bounds.add(new Rect(getRelativeCoordinate(ab.left, ab.width(), sb.left, db.width()), + getRelativeCoordinate(ab.top, ab.height(), sb.top, db.height()), + getRelativeCoordinate(ab.left, ab.width(), sb.right, db.width()), + getRelativeCoordinate(ab.top, ab.height(), sb.bottom, db.height()))); + } + return bounds; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java index 169683f..3a97a41 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -36,11 +36,14 @@ import com.android.systemui.recents.model.RecentsPackageMonitor; import com.android.systemui.recents.model.RecentsTaskLoader; import com.android.systemui.recents.model.Task; import com.android.systemui.recents.model.TaskStack; +import com.android.systemui.statusbar.DismissView; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.List; /* The visual representation of a task stack view */ @@ -54,9 +57,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal boolean lockToTask); public void onTaskViewAppInfoClicked(Task t); public void onTaskViewDismissed(Task t); - public void onAllTaskViewsDismissed(); + public void onAllTaskViewsDismissed(ArrayList<Task> removedTasks); public void onTaskStackFilterTriggered(); public void onTaskStackUnfilterTriggered(); + + public void onTaskResize(Task t); } RecentsConfiguration mConfig; @@ -72,6 +77,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal DozeTrigger mUIDozeTrigger; DebugOverlayView mDebugOverlay; Rect mTaskStackBounds = new Rect(); + DismissView mDismissAllButton; + boolean mDismissAllButtonAnimating; int mFocusedTaskIndex = -1; int mPrevAccessibilityFocusedIndex = -1; @@ -89,6 +96,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal Rect mTmpRect = new Rect(); TaskViewTransform mTmpTransform = new TaskViewTransform(); HashMap<Task, TaskView> mTmpTaskViewMap = new HashMap<Task, TaskView>(); + ArrayList<TaskView> mTaskViews = new ArrayList<TaskView>(); + List<TaskView> mImmutableTaskViews = new ArrayList<TaskView>(); LayoutInflater mInflater; // A convenience update listener to request updating clipping of tasks @@ -116,9 +125,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal @Override public void run() { // Show the task bar dismiss buttons - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - TaskView tv = (TaskView) getChildAt(i); + List<TaskView> taskViews = getTaskViews(); + int taskViewCount = taskViews.size(); + for (int i = 0; i < taskViewCount; i++) { + TaskView tv = taskViews.get(i); tv.startNoUserInteractionAnimation(); } } @@ -141,21 +151,44 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal requestLayout(); } + /** Returns the task stack. */ + TaskStack getStack() { + return mStack; + } + /** Sets the debug overlay */ public void setDebugOverlay(DebugOverlayView overlay) { mDebugOverlay = overlay; } + /** Updates the list of task views */ + void updateTaskViewsList() { + mTaskViews.clear(); + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + View v = getChildAt(i); + if (v instanceof TaskView) { + mTaskViews.add((TaskView) v); + } + } + mImmutableTaskViews = Collections.unmodifiableList(mTaskViews); + } + + /** Gets the list of task views */ + List<TaskView> getTaskViews() { + return mImmutableTaskViews; + } + /** Resets this TaskStackView for reuse. */ void reset() { // Reset the focused task resetFocusedTask(); // Return all the views to the pool - int childCount = getChildCount(); - for (int i = childCount - 1; i >= 0; i--) { - TaskView tv = (TaskView) getChildAt(i); - mViewPool.returnViewToPool(tv); + List<TaskView> taskViews = getTaskViews(); + int taskViewCount = taskViews.size(); + for (int i = taskViewCount - 1; i >= 0; i--) { + mViewPool.returnViewToPool(taskViews.get(i)); } // Mark each task view for relayout @@ -209,9 +242,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal /** Finds the child view given a specific task. */ public TaskView getChildViewForTask(Task t) { - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - TaskView tv = (TaskView) getChildAt(i); + List<TaskView> taskViews = getTaskViews(); + int taskViewCount = taskViews.size(); + for (int i = 0; i < taskViewCount; i++) { + TaskView tv = taskViews.get(i); if (tv.getTask() == t) { return tv; } @@ -299,17 +333,36 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal mDebugOverlay.setText("vis[" + visibleRange[1] + "-" + visibleRange[0] + "]"); } + // Inflate and add the dismiss button if necessary + if (Constants.DebugFlags.App.EnableDismissAll && mDismissAllButton == null) { + mDismissAllButton = (DismissView) + mInflater.inflate(R.layout.recents_dismiss_button, this, false); + mDismissAllButton.setOnButtonClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mStack.removeAllTasks(); + } + }); + addView(mDismissAllButton, 0); + } + // Return all the invisible children to the pool mTmpTaskViewMap.clear(); - int childCount = getChildCount(); - for (int i = childCount - 1; i >= 0; i--) { - TaskView tv = (TaskView) getChildAt(i); + List<TaskView> taskViews = getTaskViews(); + int taskViewCount = taskViews.size(); + for (int i = taskViewCount - 1; i >= 0; i--) { + TaskView tv = taskViews.get(i); Task task = tv.getTask(); int taskIndex = mStack.indexOfTask(task); if (visibleRange[1] <= taskIndex && taskIndex <= visibleRange[0]) { mTmpTaskViewMap.put(task, tv); } else { mViewPool.returnViewToPool(tv); + + // Hide the dismiss button if the front most task is invisible + if (task == mStack.getFrontMostTask()) { + hideDismissAllButton(null); + } } } @@ -333,6 +386,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } tv.updateViewPropertiesToTaskTransform(mTmpTransform, 0); } + + // If we show the front most task view then ensure that the dismiss button + // is visible too. + if (!mAwaitingFirstLayout && (task == mStack.getFrontMostTask())) { + showDismissAllButton(); + } } // Animate the task into place @@ -341,9 +400,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Request accessibility focus on the next view if we removed the task // that previously held accessibility focus - childCount = getChildCount(); - if (childCount > 0 && ssp.isTouchExplorationEnabled()) { - TaskView atv = (TaskView) getChildAt(childCount - 1); + taskViews = getTaskViews(); + taskViewCount = taskViews.size(); + if (taskViewCount > 0 && ssp.isTouchExplorationEnabled()) { + TaskView atv = taskViews.get(taskViewCount - 1); int indexOfTask = mStack.indexOfTask(atv.getTask()); if (mPrevAccessibilityFocusedIndex != indexOfTask) { tv.requestAccessibilityFocus(); @@ -364,44 +424,43 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal /** Updates the clip for each of the task views. */ void clipTaskViews() { // Update the clip on each task child - if (Constants.DebugFlags.App.EnableTaskStackClipping) { - int childCount = getChildCount(); - for (int i = 0; i < childCount - 1; i++) { - TaskView tv = (TaskView) getChildAt(i); - TaskView nextTv = null; - TaskView tmpTv = null; - int clipBottom = 0; - if (tv.shouldClipViewInStack()) { - // Find the next view to clip against - int nextIndex = i; - while (nextIndex < getChildCount()) { - tmpTv = (TaskView) getChildAt(++nextIndex); - if (tmpTv != null && tmpTv.shouldClipViewInStack()) { - nextTv = tmpTv; - break; - } + List<TaskView> taskViews = getTaskViews(); + int taskViewCount = taskViews.size(); + for (int i = 0; i < taskViewCount - 1; i++) { + TaskView tv = taskViews.get(i); + TaskView nextTv = null; + TaskView tmpTv = null; + int clipBottom = 0; + if (tv.shouldClipViewInStack()) { + // Find the next view to clip against + int nextIndex = i; + while (nextIndex < (taskViewCount - 1)) { + tmpTv = taskViews.get(++nextIndex); + if (tmpTv != null && tmpTv.shouldClipViewInStack()) { + nextTv = tmpTv; + break; } + } - // Clip against the next view, this is just an approximation since we are - // stacked and we can make assumptions about the visibility of the this - // task relative to the ones in front of it. - if (nextTv != null) { - // Map the top edge of next task view into the local space of the current - // task view to find the clip amount in local space - mTmpCoord[0] = mTmpCoord[1] = 0; - Utilities.mapCoordInDescendentToSelf(nextTv, this, mTmpCoord, false); - Utilities.mapCoordInSelfToDescendent(tv, this, mTmpCoord, mTmpMatrix); - clipBottom = (int) Math.floor(tv.getMeasuredHeight() - mTmpCoord[1] - - nextTv.getPaddingTop() - 1); - } + // Clip against the next view, this is just an approximation since we are + // stacked and we can make assumptions about the visibility of the this + // task relative to the ones in front of it. + if (nextTv != null) { + // Map the top edge of next task view into the local space of the current + // task view to find the clip amount in local space + mTmpCoord[0] = mTmpCoord[1] = 0; + Utilities.mapCoordInDescendentToSelf(nextTv, this, mTmpCoord, false); + Utilities.mapCoordInSelfToDescendent(tv, this, mTmpCoord, mTmpMatrix); + clipBottom = (int) Math.floor(tv.getMeasuredHeight() - mTmpCoord[1] + - nextTv.getPaddingTop() - 1); } - tv.getViewBounds().setClipBottom(clipBottom); - } - if (getChildCount() > 0) { - // The front most task should never be clipped - TaskView tv = (TaskView) getChildAt(getChildCount() - 1); - tv.getViewBounds().setClipBottom(0); } + tv.getViewBounds().setClipBottom(clipBottom); + } + if (taskViewCount > 0) { + // The front most task should never be clipped + TaskView tv = taskViews.get(taskViewCount - 1); + tv.getViewBounds().setClipBottom(0); } mStackViewsClipDirty = false; } @@ -479,9 +538,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // of the screen and use that as the currently focused task int x = mLayoutAlgorithm.mStackVisibleRect.centerX(); int y = mLayoutAlgorithm.mStackVisibleRect.centerY(); - int childCount = getChildCount(); - for (int i = childCount - 1; i >= 0; i--) { - TaskView tv = (TaskView) getChildAt(i); + List<TaskView> taskViews = getTaskViews(); + int taskViewCount = taskViews.size(); + for (int i = taskViewCount - 1; i >= 0; i--) { + TaskView tv = taskViews.get(i); tv.getHitRect(mTmpRect); if (mTmpRect.contains(x, y)) { mFocusedTaskIndex = mStack.indexOfTask(tv.getTask()); @@ -489,8 +549,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } } // If we can't find the center task, then use the front most index - if (mFocusedTaskIndex < 0 && childCount > 0) { - mFocusedTaskIndex = childCount - 1; + if (mFocusedTaskIndex < 0 && taskViewCount > 0) { + mFocusedTaskIndex = taskViewCount - 1; } } return mFocusedTaskIndex >= 0; @@ -543,10 +603,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); - int childCount = getChildCount(); - if (childCount > 0) { - TaskView backMostTask = (TaskView) getChildAt(0); - TaskView frontMostTask = (TaskView) getChildAt(childCount - 1); + List<TaskView> taskViews = getTaskViews(); + int taskViewCount = taskViews.size(); + if (taskViewCount > 0) { + TaskView backMostTask = taskViews.get(0); + TaskView frontMostTask = taskViews.get(taskViewCount - 1); event.setFromIndex(mStack.indexOfTask(backMostTask.getTask())); event.setToIndex(mStack.indexOfTask(frontMostTask.getTask())); event.setContentDescription(frontMostTask.getTask().activityLabel); @@ -571,12 +632,18 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal return mTouchHandler.onGenericMotionEvent(ev); } + /** Returns the region that touch gestures can be started in. */ + Rect getTouchableRegion() { + return mTaskStackBounds; + } + @Override public void computeScroll() { mStackScroller.computeScroll(); // Synchronize the views synchronizeStackViewsWithModel(); clipTaskViews(); + updateDismissButtonPosition(); // Notify accessibility sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED); } @@ -633,9 +700,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } // Measure each of the TaskViews - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - TaskView tv = (TaskView) getChildAt(i); + List<TaskView> taskViews = getTaskViews(); + int taskViewCount = taskViews.size(); + for (int i = 0; i < taskViewCount; i++) { + TaskView tv = taskViews.get(i); if (tv.getBackground() != null) { tv.getBackground().getPadding(mTmpRect); } else { @@ -650,6 +718,14 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal MeasureSpec.EXACTLY)); } + // Measure the dismiss button + if (mDismissAllButton != null) { + int taskRectWidth = mLayoutAlgorithm.mTaskRect.width(); + mDismissAllButton.measure( + MeasureSpec.makeMeasureSpec(taskRectWidth, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(mConfig.dismissAllButtonSizePx, MeasureSpec.EXACTLY)); + } + setMeasuredDimension(width, height); } @@ -661,9 +737,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { // Layout each of the children - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - TaskView tv = (TaskView) getChildAt(i); + List<TaskView> taskViews = getTaskViews(); + int taskViewCount = taskViews.size(); + for (int i = 0; i < taskViewCount; i++) { + TaskView tv = taskViews.get(i); if (tv.getBackground() != null) { tv.getBackground().getPadding(mTmpRect); } else { @@ -675,6 +752,15 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal mLayoutAlgorithm.mTaskRect.bottom + mTmpRect.bottom); } + // Layout the dismiss button at the top of the screen, and just translate it accordingly + // when synchronizing the views with the model to attach it to the bottom of the front-most + // task view + if (mDismissAllButton != null) { + mDismissAllButton.layout(mLayoutAlgorithm.mTaskRect.left, 0, + mLayoutAlgorithm.mTaskRect.left + mDismissAllButton.getMeasuredWidth(), + mDismissAllButton.getMeasuredHeight()); + } + if (mAwaitingFirstLayout) { mAwaitingFirstLayout = false; onFirstLayout(); @@ -688,9 +774,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Find the launch target task Task launchTargetTask = null; - int childCount = getChildCount(); - for (int i = childCount - 1; i >= 0; i--) { - TaskView tv = (TaskView) getChildAt(i); + List<TaskView> taskViews = getTaskViews(); + int taskViewCount = taskViews.size(); + for (int i = taskViewCount - 1; i >= 0; i--) { + TaskView tv = taskViews.get(i); Task task = tv.getTask(); if (task.isLaunchTarget) { launchTargetTask = task; @@ -699,8 +786,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } // Prepare the first view for its enter animation - for (int i = childCount - 1; i >= 0; i--) { - TaskView tv = (TaskView) getChildAt(i); + for (int i = taskViewCount - 1; i >= 0; i--) { + TaskView tv = taskViews.get(i); Task task = tv.getTask(); boolean occludesLaunchTarget = (launchTargetTask != null) && launchTargetTask.group.isTaskAboveTask(task, launchTargetTask); @@ -728,7 +815,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } // Start dozing - mUIDozeTrigger.startDozing(); + if (!mConfig.multiStackEnabled) { + mUIDozeTrigger.startDozing(); + } } /** Requests this task stacks to start it's enter-recents animation */ @@ -743,9 +832,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal if (mStack.getTaskCount() > 0) { // Find the launch target task Task launchTargetTask = null; - int childCount = getChildCount(); - for (int i = childCount - 1; i >= 0; i--) { - TaskView tv = (TaskView) getChildAt(i); + List<TaskView> taskViews = getTaskViews(); + int taskViewCount = taskViews.size(); + for (int i = taskViewCount - 1; i >= 0; i--) { + TaskView tv = taskViews.get(i); Task task = tv.getTask(); if (task.isLaunchTarget) { launchTargetTask = task; @@ -754,12 +844,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } // Animate all the task views into view - for (int i = childCount - 1; i >= 0; i--) { - TaskView tv = (TaskView) getChildAt(i); + for (int i = taskViewCount - 1; i >= 0; i--) { + TaskView tv = taskViews.get(i); Task task = tv.getTask(); ctx.currentTaskTransform = new TaskViewTransform(); ctx.currentStackViewIndex = i; - ctx.currentStackViewCount = childCount; + ctx.currentStackViewCount = taskViewCount; ctx.currentTaskRect = mLayoutAlgorithm.mTaskRect; ctx.currentTaskOccludesLaunchTarget = (launchTargetTask != null) && launchTargetTask.group.isTaskAboveTask(task, launchTargetTask); @@ -778,11 +868,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); SystemServicesProxy ssp = loader.getSystemServicesProxy(); - int childCount = getChildCount(); - if (childCount > 0) { + List<TaskView> taskViews = getTaskViews(); + int taskViewCount = taskViews.size(); + if (taskViewCount > 0) { // Focus the first view if accessibility is enabled if (ssp.isTouchExplorationEnabled()) { - TaskView tv = ((TaskView) getChildAt(childCount - 1)); + TaskView tv = taskViews.get(taskViewCount - 1); tv.requestAccessibilityFocus(); mPrevAccessibilityFocusedIndex = mStack.indexOfTask(tv.getTask()); } @@ -790,17 +881,20 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Start the focus animation when alt-tabbing if (mConfig.launchedWithAltTab && !mConfig.launchedHasConfigurationChanged) { - View tv = getChildAt(mFocusedTaskIndex); + TaskView tv = getChildViewForTask(mStack.getTasks().get(mFocusedTaskIndex)); if (tv != null) { - ((TaskView) tv).setFocusedTask(true); + tv.setFocusedTask(true); } } + + // Show the dismiss button + showDismissAllButton(); } }); } } - /** Requests this task stacks to start it's exit-recents animation. */ + /** Requests this task stack to start it's exit-recents animation. */ public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) { // Stop any scrolling mStackScroller.stopScroller(); @@ -808,19 +902,44 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Animate all the task views out of view ctx.offscreenTranslationY = mLayoutAlgorithm.mViewRect.bottom - (mLayoutAlgorithm.mTaskRect.top - mLayoutAlgorithm.mViewRect.top); - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - TaskView tv = (TaskView) getChildAt(i); + // Animate the dismiss-all button + hideDismissAllButton(null); + + List<TaskView> taskViews = getTaskViews(); + int taskViewCount = taskViews.size(); + for (int i = 0; i < taskViewCount; i++) { + TaskView tv = taskViews.get(i); tv.startExitToHomeAnimation(ctx); } } + /** Requests this task stack to start it's dismiss-all animation. */ + public void startDismissAllAnimation(final Runnable postAnimationRunnable) { + // Clear the focused task + resetFocusedTask(); + // Animate the dismiss-all button + hideDismissAllButton(new Runnable() { + @Override + public void run() { + List<TaskView> taskViews = getTaskViews(); + int taskViewCount = taskViews.size(); + int count = 0; + for (int i = taskViewCount - 1; i >= 0; i--) { + TaskView tv = taskViews.get(i); + tv.startDeleteTaskAnimation(i > 0 ? null : postAnimationRunnable, count * 50); + count++; + } + } + }); + } + /** Animates a task view in this stack as it launches. */ public void startLaunchTaskAnimation(TaskView tv, Runnable r, boolean lockToTask) { Task launchTargetTask = tv.getTask(); - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - TaskView t = (TaskView) getChildAt(i); + List<TaskView> taskViews = getTaskViews(); + int taskViewCount = taskViews.size(); + for (int i = 0; i < taskViewCount; i++) { + TaskView t = taskViews.get(i); if (t == tv) { t.setClipViewInStack(false); t.startLaunchTaskAnimation(r, true, true, lockToTask); @@ -832,6 +951,69 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } } + /** Shows the dismiss button */ + void showDismissAllButton() { + if (mDismissAllButton == null) return; + + if (mDismissAllButtonAnimating || mDismissAllButton.getVisibility() != View.VISIBLE || + Float.compare(mDismissAllButton.getAlpha(), 0f) == 0) { + mDismissAllButtonAnimating = true; + mDismissAllButton.setVisibility(View.VISIBLE); + mDismissAllButton.showClearButton(); + mDismissAllButton.findViewById(R.id.dismiss_text).setAlpha(1f); + mDismissAllButton.setAlpha(0f); + mDismissAllButton.animate() + .alpha(1f) + .setDuration(250) + .withEndAction(new Runnable() { + @Override + public void run() { + mDismissAllButtonAnimating = false; + } + }) + .start(); + } + } + + /** Hides the dismiss button */ + void hideDismissAllButton(final Runnable postAnimRunnable) { + if (mDismissAllButton == null) return; + + mDismissAllButtonAnimating = true; + mDismissAllButton.animate() + .alpha(0f) + .setDuration(200) + .withEndAction(new Runnable() { + @Override + public void run() { + mDismissAllButtonAnimating = false; + mDismissAllButton.setVisibility(View.GONE); + if (postAnimRunnable != null) { + postAnimRunnable.run(); + } + } + }) + .start(); + } + + /** Updates the dismiss button position */ + void updateDismissButtonPosition() { + if (mDismissAllButton == null) return; + + // Update the position of the clear-all button to hang it off the first task view + if (mStack.getTaskCount() > 0) { + mTmpCoord[0] = mTmpCoord[1] = 0; + TaskView tv = getChildViewForTask(mStack.getFrontMostTask()); + TaskViewTransform transform = mCurrentTaskTransforms.get(mStack.getTaskCount() - 1); + if (tv != null && transform.visible) { + Utilities.mapCoordInDescendentToSelf(tv, this, mTmpCoord, false); + mDismissAllButton.setTranslationY(mTmpCoord[1] + (tv.getScaleY() * tv.getHeight())); + mDismissAllButton.setTranslationX(-(mLayoutAlgorithm.mStackRect.width() - + transform.rect.width()) / 2f); + } + } + } + /** Final callback after Recents is finally hidden. */ void onRecentsHidden() { reset(); @@ -908,12 +1090,30 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal shouldFinishActivity = (mStack.getTaskCount() == 0); } if (shouldFinishActivity) { - mCb.onAllTaskViewsDismissed(); + mCb.onAllTaskViewsDismissed(null); } + } else { + // Fade the dismiss button back in + showDismissAllButton(); } } @Override + public void onStackAllTasksRemoved(TaskStack stack, final ArrayList<Task> removedTasks) { + // Announce for accessibility + String msg = getContext().getString(R.string.accessibility_recents_all_items_dismissed); + announceForAccessibility(msg); + + startDismissAllAnimation(new Runnable() { + @Override + public void run() { + // Notify that all tasks have been removed + mCb.onAllTaskViewsDismissed(removedTasks); + } + }); + } + + @Override public void onStackFiltered(TaskStack newStack, final ArrayList<Task> curTasks, Task filteredTask) { /* @@ -998,6 +1198,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Detach the view from the hierarchy detachViewFromParent(tv); + // Update the task views list after removing the task view + updateTaskViewsList(); // Reset the view properties tv.resetViewProperties(); @@ -1019,7 +1221,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal RecentsTaskLoader.getInstance().loadTaskData(task); // If the doze trigger has already fired, then update the state for this task view - if (mUIDozeTrigger.hasTriggered()) { + if (mConfig.multiStackEnabled || mUIDozeTrigger.hasTriggered()) { tv.setNoUserInteractionState(); } @@ -1032,11 +1234,14 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal int insertIndex = -1; int taskIndex = mStack.indexOfTask(task); if (taskIndex != -1) { - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - Task tvTask = ((TaskView) getChildAt(i)).getTask(); + + List<TaskView> taskViews = getTaskViews(); + int taskViewCount = taskViews.size(); + for (int i = 0; i < taskViewCount; i++) { + Task tvTask = taskViews.get(i).getTask(); if (taskIndex < mStack.indexOfTask(tvTask)) { - insertIndex = i; + // Offset by 1 if we have a dismiss-all button + insertIndex = i + (Constants.DebugFlags.App.EnableDismissAll ? 1 : 0); break; } } @@ -1051,6 +1256,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal tv.requestLayout(); } } + // Update the task views list after adding the new task view + updateTaskViewsList(); // Set the new state for this view, including the callbacks and view clipping tv.setCallbacks(this); @@ -1133,6 +1340,13 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } } + @Override + public void onTaskResize(TaskView tv) { + if (mCb != null) { + mCb.onTaskResize(tv.getTask()); + } + } + /**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/ @Override @@ -1163,7 +1377,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal public void run() { mStack.removeTask(t); } - }); + }, 0); } else { // Otherwise, remove the task from the stack immediately mStack.removeTask(t); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewFilterAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewFilterAlgorithm.java index 9cd5ae4..614ca53 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewFilterAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewFilterAlgorithm.java @@ -22,6 +22,7 @@ import com.android.systemui.recents.model.Task; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; /* The layout logic for a TaskStackView */ public class TaskStackViewFilterAlgorithm { @@ -142,9 +143,10 @@ public class TaskStackViewFilterAlgorithm { // the new stack) or to their final positions in the new stack int offset = 0; int movement = 0; - int childCount = mStackView.getChildCount(); - for (int i = 0; i < childCount; i++) { - TaskView tv = (TaskView) mStackView.getChildAt(i); + List<TaskView> taskViews = mStackView.getTaskViews(); + int taskViewCount = taskViews.size(); + for (int i = 0; i < taskViewCount; i++) { + TaskView tv = taskViews.get(i); Task task = tv.getTask(); int taskIndex = tasks.indexOf(task); TaskViewTransform toTransform; diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java index 49b9129..f6df881 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java @@ -17,6 +17,7 @@ package com.android.systemui.recents.views; import android.graphics.Rect; +import com.android.systemui.recents.Constants; import com.android.systemui.recents.RecentsConfiguration; import com.android.systemui.recents.misc.Utilities; import com.android.systemui.recents.model.Task; @@ -131,6 +132,11 @@ public class TaskStackViewLayoutAlgorithm { float pNavBarOffset = pAtBottomOfStackRect - screenYToCurveProgress(mStackVisibleRect.bottom - (mStackVisibleRect.bottom - mStackRect.bottom)); + float pDismissAllButtonOffset = 0f; + if (Constants.DebugFlags.App.EnableDismissAll) { + pDismissAllButtonOffset = pAtBottomOfStackRect - + screenYToCurveProgress(mStackVisibleRect.bottom - mConfig.dismissAllButtonSizePx); + } // Update the task offsets float pAtBackMostCardTop = 0.5f; @@ -148,7 +154,8 @@ public class TaskStackViewLayoutAlgorithm { } } - mMaxScrollP = pAtFrontMostCardTop - ((1f - pTaskHeightOffset - pNavBarOffset)); + mMaxScrollP = pAtFrontMostCardTop + pDismissAllButtonOffset - + ((1f - pTaskHeightOffset - pNavBarOffset)); mMinScrollP = tasks.size() == 1 ? Math.max(mMaxScrollP, 0f) : 0f; if (launchedWithAltTab && launchedFromHome) { // Center the top most task, since that will be focused first diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java index f7067be..fabc86d 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java @@ -96,23 +96,13 @@ public class TaskStackViewScroller { } return false; } - /** Bounds the current scroll if necessary, but does not synchronize the stack view with the model. */ - public boolean boundScrollRaw() { - float curScroll = getStackScroll(); - float newScroll = getBoundedStackScroll(curScroll); - if (Float.compare(newScroll, curScroll) != 0) { - setStackScrollRaw(newScroll); - return true; - } - return false; - } /** Returns the bounded stack scroll */ float getBoundedStackScroll(float scroll) { return Math.max(mLayoutAlgorithm.mMinScrollP, Math.min(mLayoutAlgorithm.mMaxScrollP, scroll)); } - /** Returns the amount that the aboslute value of how much the scroll is out of bounds. */ + /** Returns the amount that the absolute value of how much the scroll is out of bounds. */ float getScrollAmountOutOfBounds(float scroll) { if (scroll < mLayoutAlgorithm.mMinScrollP) { return Math.abs(scroll - mLayoutAlgorithm.mMinScrollP); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java index 59e38f4..509560eb 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java @@ -24,8 +24,11 @@ import android.view.View; import android.view.ViewConfiguration; import android.view.ViewParent; import com.android.systemui.recents.Constants; +import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsConfiguration; +import java.util.List; + /* Handles touch events for a TaskStackView. */ class TaskStackViewTouchHandler implements SwipeHelper.Callback { static int INACTIVE_POINTER_ID = -1; @@ -51,6 +54,8 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { int mScrollTouchSlop; // The page touch slop is used to calculate when we start swiping float mPagingTouchSlop; + // Used to calculate when a tap is outside a task view rectangle. + final int mWindowTouchSlop; SwipeHelper mSwipeHelper; boolean mInterceptedBySwipeHelper; @@ -62,6 +67,7 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); mScrollTouchSlop = configuration.getScaledTouchSlop(); mPagingTouchSlop = configuration.getScaledPagingTouchSlop(); + mWindowTouchSlop = configuration.getScaledWindowTouchSlop(); mSv = sv; mScroller = scroller; mConfig = config; @@ -93,9 +99,10 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { /** Returns the view at the specified coordinates */ TaskView findViewAtPoint(int x, int y) { - int childCount = mSv.getChildCount(); - for (int i = childCount - 1; i >= 0; i--) { - TaskView tv = (TaskView) mSv.getChildAt(i); + List<TaskView> taskViews = mSv.getTaskViews(); + int taskViewCount = taskViews.size(); + for (int i = taskViewCount - 1; i >= 0; i--) { + TaskView tv = taskViews.get(i); if (tv.getVisibility() == View.VISIBLE) { if (mSv.isTransformedTouchPointInView(x, y, tv)) { return tv; @@ -115,11 +122,21 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { /** Touch preprocessing for handling below */ public boolean onInterceptTouchEvent(MotionEvent ev) { // Return early if we have no children - boolean hasChildren = (mSv.getChildCount() > 0); - if (!hasChildren) { + boolean hasTaskViews = (mSv.getTaskViews().size() > 0); + if (!hasTaskViews) { return false; } + int action = ev.getAction(); + if (mConfig.multiStackEnabled) { + // Check if we are within the bounds of the stack view contents + if ((action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) { + if (!mSv.getTouchableRegion().contains((int) ev.getX(), (int) ev.getY())) { + return false; + } + } + } + // Pass through to swipe helper if we are swiping mInterceptedBySwipeHelper = mSwipeHelper.onInterceptTouchEvent(ev); if (mInterceptedBySwipeHelper) { @@ -128,7 +145,6 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { boolean wasScrolling = mScroller.isScrolling() || (mScroller.mScrollAnimator != null && mScroller.mScrollAnimator.isRunning()); - int action = ev.getAction(); switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: { // Save the touch down info @@ -190,11 +206,21 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { /** Handles touch events once we have intercepted them */ public boolean onTouchEvent(MotionEvent ev) { // Short circuit if we have no children - boolean hasChildren = (mSv.getChildCount() > 0); - if (!hasChildren) { + boolean hasTaskViews = (mSv.getTaskViews().size() > 0); + if (!hasTaskViews) { return false; } + int action = ev.getAction(); + if (mConfig.multiStackEnabled) { + // Check if we are within the bounds of the stack view contents + if ((action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) { + if (!mSv.getTouchableRegion().contains((int) ev.getX(), (int) ev.getY())) { + return false; + } + } + } + // Pass through to swipe helper if we are swiping if (mInterceptedBySwipeHelper && mSwipeHelper.onTouchEvent(ev)) { return true; @@ -203,7 +229,6 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { // Update the velocity tracker initVelocityTrackerIfNotExists(); - int action = ev.getAction(); switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: { // Save the touch down info @@ -279,7 +304,7 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { float overscrollRangePct = Math.abs((float) velocity / mMaximumVelocity); int overscrollRange = (int) (Math.min(1f, overscrollRangePct) * (Constants.Values.TaskStackView.TaskStackMaxOverscrollRange - - Constants.Values.TaskStackView.TaskStackMinOverscrollRange)); + Constants.Values.TaskStackView.TaskStackMinOverscrollRange)); mScroller.mScroller.fling(0, mScroller.progressToScrollRange(mScroller.getStackScroll()), 0, velocity, @@ -293,6 +318,9 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { } else if (mScroller.isScrollOutOfBounds()) { // Animate the scroll back into bounds mScroller.animateBoundScroll(); + } else if (mActiveTaskView == null) { + // This tap didn't start on a task. + maybeHideRecentsFromBackgroundTap((int) ev.getX(), (int) ev.getY()); } mActivePointerId = INACTIVE_POINTER_ID; @@ -330,6 +358,34 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { return true; } + /** Hides recents if the up event at (x, y) is a tap on the background area. */ + void maybeHideRecentsFromBackgroundTap(int x, int y) { + // Ignore the up event if it's too far from its start position. The user might have been + // trying to scroll or swipe. + int dx = Math.abs(mInitialMotionX - x); + int dy = Math.abs(mInitialMotionY - y); + if (dx > mScrollTouchSlop || dy > mScrollTouchSlop) { + return; + } + + // Shift the tap position toward the center of the task stack and check to see if it would + // have hit a view. The user might have tried to tap on a task and missed slightly. + int shiftedX = x; + if (x > mSv.getTouchableRegion().centerX()) { + shiftedX -= mWindowTouchSlop; + } else { + shiftedX += mWindowTouchSlop; + } + if (findViewAtPoint(shiftedX, y) != null) { + return; + } + + // The user intentionally tapped on the background, which is like a tap on the "desktop". + // Hide recents and transition to the launcher. + Recents recents = Recents.getInstanceAndStartIfNeeded(mSv.getContext()); + recents.hideRecents(false /* altTab */, true /* homeKey */); + } + /** Handles generic motion events */ public boolean onGenericMotionEvent(MotionEvent ev) { if ((ev.getSource() & InputDevice.SOURCE_CLASS_POINTER) == @@ -378,6 +434,8 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { if (parent != null) { parent.requestDisallowInterceptTouchEvent(true); } + // Fade out the dismiss button + mSv.hideDismissAllButton(null); } @Override @@ -403,6 +461,8 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { tv.setClipViewInStack(true); // Re-enable touch events from this task view tv.setTouchEnabled(true); + // Restore the dismiss button + mSv.showDismissAllButton(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java index faa728d..682775b 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java @@ -45,6 +45,8 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, public void onTaskViewDismissed(TaskView tv); public void onTaskViewClipStateChanged(TaskView tv); public void onTaskViewFocusChanged(TaskView tv, boolean focused); + + public void onTaskResize(TaskView tv); } RecentsConfiguration mConfig; @@ -381,6 +383,11 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, ctx.postAnimationTrigger.increment(); } + /** Animates this task view away when dismissing all tasks. */ + void startDismissAllAnimation() { + dismissTask(); + } + /** Animates this task view as it exits recents */ void startLaunchTaskAnimation(final Runnable postAnimRunnable, boolean isLaunchingTask, boolean occludesLaunchTarget, boolean lockToTask) { @@ -428,25 +435,22 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, } /** Animates the deletion of this task view */ - void startDeleteTaskAnimation(final Runnable r) { + void startDeleteTaskAnimation(final Runnable r, int delay) { // Disabling clipping with the stack while the view is animating away setClipViewInStack(false); animate().translationX(mConfig.taskViewRemoveAnimTranslationXPx) .alpha(0f) - .setStartDelay(0) + .setStartDelay(delay) .setUpdateListener(null) .setInterpolator(mConfig.fastOutSlowInInterpolator) .setDuration(mConfig.taskViewRemoveAnimDuration) .withEndAction(new Runnable() { @Override public void run() { - // We just throw this into a runnable because starting a view property - // animation using layers can cause inconsisten results if we try and - // update the layers while the animation is running. In some cases, - // the runnabled passed in may start an animation which also uses layers - // so we defer all this by posting this. - r.run(); + if (r != null) { + r.run(); + } // Re-enable clipping with the stack (we will reuse this view) setClipViewInStack(true); @@ -455,6 +459,11 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, .start(); } + /** Enables/disables handling touch on this task view. */ + void setTouchEnabled(boolean enabled) { + setOnClickListener(enabled ? this : null); + } + /** Animates this task view if the user does not interact with the stack after a certain time. */ void startNoUserInteractionAnimation() { mHeaderView.startNoUserInteractionAnimation(); @@ -481,7 +490,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, mCb.onTaskViewDismissed(tv); } } - }); + }, 0); } /** @@ -665,6 +674,9 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, // Rebind any listeners mHeaderView.mApplicationIcon.setOnClickListener(this); mHeaderView.mDismissButton.setOnClickListener(this); + if (mConfig.multiStackEnabled) { + mHeaderView.mMoveTaskButton.setOnClickListener(this); + } mActionButtonView.setOnClickListener(this); if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) { if (mConfig.developerOptionsEnabled) { @@ -685,6 +697,9 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, // Unbind any listeners mHeaderView.mApplicationIcon.setOnClickListener(null); mHeaderView.mDismissButton.setOnClickListener(null); + if (mConfig.multiStackEnabled) { + mHeaderView.mMoveTaskButton.setOnClickListener(null); + } mActionButtonView.setOnClickListener(null); if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) { mHeaderView.mApplicationIcon.setOnLongClickListener(null); @@ -693,9 +708,9 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, mTaskDataLoaded = false; } - /** Enables/disables handling touch on this task view. */ - void setTouchEnabled(boolean enabled) { - setOnClickListener(enabled ? this : null); + @Override + public void onMultiStackDebugTaskStackIdChanged() { + mHeaderView.rebindToTask(mTask); } /**** View.OnClickListener Implementation ****/ @@ -715,6 +730,10 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, } } else if (v == mHeaderView.mDismissButton) { dismissTask(); + } else if (v == mHeaderView.mMoveTaskButton) { + if (mCb != null) { + mCb.onTaskResize(tv); + } } } }, 125); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java index 05f6f40..60a91bf 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java @@ -24,20 +24,18 @@ import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.ColorStateList; -import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.RippleDrawable; import android.graphics.Outline; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.PorterDuffXfermode; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.GradientDrawable; -import android.graphics.drawable.RippleDrawable; +import android.graphics.Rect; import android.util.AttributeSet; -import android.view.MotionEvent; import android.view.View; import android.view.ViewOutlineProvider; import android.widget.FrameLayout; @@ -46,7 +44,9 @@ import android.widget.TextView; import com.android.systemui.R; import com.android.systemui.recents.Constants; import com.android.systemui.recents.RecentsConfiguration; +import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.recents.misc.Utilities; +import com.android.systemui.recents.model.RecentsTaskLoader; import com.android.systemui.recents.model.Task; @@ -54,8 +54,10 @@ import com.android.systemui.recents.model.Task; public class TaskViewHeader extends FrameLayout { RecentsConfiguration mConfig; + private SystemServicesProxy mSsp; // Header views + ImageView mMoveTaskButton; ImageView mDismissButton; ImageView mApplicationIcon; TextView mActivityDescription; @@ -93,6 +95,7 @@ public class TaskViewHeader extends FrameLayout { public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); mConfig = RecentsConfiguration.getInstance(); + mSsp = RecentsTaskLoader.getInstance().getSystemServicesProxy(); setWillNotDraw(false); setClipToOutline(true); setOutlineProvider(new ViewOutlineProvider() { @@ -103,11 +106,10 @@ public class TaskViewHeader extends FrameLayout { }); // Load the dismiss resources - Resources res = context.getResources(); - mLightDismissDrawable = res.getDrawable(R.drawable.recents_dismiss_light); - mDarkDismissDrawable = res.getDrawable(R.drawable.recents_dismiss_dark); + mLightDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_light); + mDarkDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_dark); mDismissContentDescription = - res.getString(R.string.accessibility_recents_item_will_be_dismissed); + context.getString(R.string.accessibility_recents_item_will_be_dismissed); // Configure the highlight paint if (sHighlightPaint == null) { @@ -121,19 +123,12 @@ public class TaskViewHeader extends FrameLayout { } @Override - public boolean onTouchEvent(MotionEvent event) { - // We ignore taps on the task bar except on the filter and dismiss buttons - if (!Constants.DebugFlags.App.EnableTaskBarTouchEvents) return true; - - return super.onTouchEvent(event); - } - - @Override protected void onFinishInflate() { // Initialize the icon and description views mApplicationIcon = (ImageView) findViewById(R.id.application_icon); mActivityDescription = (TextView) findViewById(R.id.activity_description); mDismissButton = (ImageView) findViewById(R.id.dismiss_task); + mMoveTaskButton = (ImageView) findViewById(R.id.move_task); // Hide the backgrounds if they are ripple drawables if (!Constants.DebugFlags.App.EnableTaskFiltering) { @@ -200,8 +195,7 @@ public class TaskViewHeader extends FrameLayout { mActivityDescription.setText(t.activityLabel); } // Try and apply the system ui tint - int existingBgColor = (getBackground() instanceof ColorDrawable) ? - ((ColorDrawable) getBackground()).getColor() : 0; + int existingBgColor = getBackgroundColor(); if (existingBgColor != t.colorPrimary) { mBackgroundColorDrawable.setColor(t.colorPrimary); mBackgroundColor = t.colorPrimary; @@ -214,6 +208,43 @@ public class TaskViewHeader extends FrameLayout { mLightDismissDrawable : mDarkDismissDrawable); mDismissButton.setContentDescription(String.format(mDismissContentDescription, t.activityLabel)); + mMoveTaskButton.setVisibility((mConfig.multiStackEnabled) ? View.VISIBLE : View.INVISIBLE); + if (mConfig.multiStackEnabled) { + updateResizeTaskBarIcon(t); + } + } + + /** Updates the resize task bar button. */ + void updateResizeTaskBarIcon(Task t) { + Rect display = mSsp.getWindowRect(); + Rect taskRect = mSsp.getTaskBounds(t.key.stackId); + int resId = R.drawable.star; + if (display.equals(taskRect) || taskRect.isEmpty()) { + resId = R.drawable.vector_drawable_place_fullscreen; + } else { + boolean top = display.top == taskRect.top; + boolean bottom = display.bottom == taskRect.bottom; + boolean left = display.left == taskRect.left; + boolean right = display.right == taskRect.right; + if (top && bottom && left) { + resId = R.drawable.vector_drawable_place_left; + } else if (top && bottom && right) { + resId = R.drawable.vector_drawable_place_right; + } else if (top && left && right) { + resId = R.drawable.vector_drawable_place_top; + } else if (bottom && left && right) { + resId = R.drawable.vector_drawable_place_bottom; + } else if (top && right) { + resId = R.drawable.vector_drawable_place_top_right; + } else if (top && left) { + resId = R.drawable.vector_drawable_place_top_left; + } else if (bottom && right) { + resId = R.drawable.vector_drawable_place_bottom_right; + } else if (bottom && left) { + resId = R.drawable.vector_drawable_place_bottom_left; + } + } + mMoveTaskButton.setImageResource(resId); } /** Unbinds the bar view from the task */ @@ -284,23 +315,26 @@ public class TaskViewHeader extends FrameLayout { } if (focused) { + int currentColor = mBackgroundColor; int secondaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark); int[][] states = new int[][] { + new int[] {}, new int[] { android.R.attr.state_enabled }, new int[] { android.R.attr.state_pressed } }; int[] newStates = new int[]{ + 0, android.R.attr.state_enabled, android.R.attr.state_pressed }; int[] colors = new int[] { + currentColor, secondaryColor, secondaryColor }; mBackground.setColor(new ColorStateList(states, colors)); mBackground.setState(newStates); // Pulse the background color - int currentColor = mBackgroundColor; int lightPrimaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark); ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(), currentColor, lightPrimaryColor); @@ -327,7 +361,7 @@ public class TaskViewHeader extends FrameLayout { mFocusAnimator = new AnimatorSet(); mFocusAnimator.playTogether(backgroundColor, translation); - mFocusAnimator.setStartDelay(750); + mFocusAnimator.setStartDelay(150); mFocusAnimator.setDuration(750); mFocusAnimator.start(); } else { diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java index 42c0f9f..a55e026 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java @@ -21,7 +21,6 @@ import android.graphics.Rect; import android.view.View; import android.view.ViewPropertyAnimator; import android.view.animation.Interpolator; -import com.android.systemui.recents.Constants; /* The transform state for a task view */ @@ -133,6 +132,8 @@ public class TaskViewTransform { /** Reset the transform on a view. */ public static void reset(View v) { + // Cancel any running animations + v.animate().cancel(); v.setTranslationX(0f); v.setTranslationY(0f); v.setTranslationZ(0f); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index d9fea47..105bf0f 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -122,7 +122,7 @@ class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Voi // Prepare all the output metadata mImageTime = System.currentTimeMillis(); - String imageDate = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date(mImageTime)); + String imageDate = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date(mImageTime)); mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, imageDate); mScreenshotDir = new File(Environment.getExternalStoragePublicDirectory( @@ -341,7 +341,6 @@ class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Voi class GlobalScreenshot { private static final String TAG = "GlobalScreenshot"; - private static final int SCREENSHOT_NOTIFICATION_ID = 789; private static final int SCREENSHOT_FLASH_TO_PEAK_DURATION = 130; private static final int SCREENSHOT_DROP_IN_DURATION = 430; private static final int SCREENSHOT_DROP_OUT_DELAY = 500; @@ -464,7 +463,7 @@ class GlobalScreenshot { mSaveInBgTask.cancel(false); } mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data, mNotificationManager, - SCREENSHOT_NOTIFICATION_ID).execute(data); + R.id.notification_screenshot).execute(data); } /** @@ -725,12 +724,12 @@ class GlobalScreenshot { .setVisibility(Notification.VISIBILITY_PUBLIC) // ok to show outside lockscreen .setCategory(Notification.CATEGORY_ERROR) .setAutoCancel(true) - .setColor(context.getResources().getColor( + .setColor(context.getColor( com.android.internal.R.color.system_notification_accent_color)); Notification n = new Notification.BigTextStyle(b) .bigText(r.getString(R.string.screenshot_failed_text)) .build(); - nManager.notify(SCREENSHOT_NOTIFICATION_ID, n); + nManager.notify(R.id.notification_screenshot, n); } } diff --git a/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java index a1704ff..74267a5 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java +++ b/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java @@ -17,11 +17,7 @@ package com.android.systemui.settings; import android.app.Activity; -import android.app.Dialog; -import android.content.Context; -import android.content.res.Resources; import android.os.Bundle; -import android.os.Handler; import android.view.Gravity; import android.view.KeyEvent; import android.view.Window; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java index 465a141..c0b3a9b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java @@ -94,7 +94,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView = new PathInterpolator(0, 0, 0.5f, 1); private final int mTintedRippleColor; private final int mLowPriorityRippleColor; - private final int mNormalRippleColor; + protected final int mNormalRippleColor; private boolean mDimmed; private boolean mDark; @@ -115,7 +115,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView private OnActivatedListener mOnActivatedListener; private final Interpolator mLinearOutSlowInInterpolator; - private final Interpolator mFastOutSlowInInterpolator; + protected final Interpolator mFastOutSlowInInterpolator; private final Interpolator mSlowOutFastInInterpolator; private final Interpolator mSlowOutLinearInInterpolator; private final Interpolator mLinearInterpolator; @@ -154,15 +154,15 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mAppearAnimationFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP); mRoundedRectCornerRadius = getResources().getDimensionPixelSize( R.dimen.notification_material_rounded_rect_radius); - mLegacyColor = getResources().getColor(R.color.notification_legacy_background_color); - mNormalColor = getResources().getColor(R.color.notification_material_background_color); - mLowPriorityColor = getResources().getColor( + mLegacyColor = context.getColor(R.color.notification_legacy_background_color); + mNormalColor = context.getColor(R.color.notification_material_background_color); + mLowPriorityColor = context.getColor( R.color.notification_material_background_low_priority_color); - mTintedRippleColor = context.getResources().getColor( + mTintedRippleColor = context.getColor( R.color.notification_ripple_tinted_color); - mLowPriorityRippleColor = context.getResources().getColor( + mLowPriorityRippleColor = context.getColor( R.color.notification_ripple_color_low_priority); - mNormalRippleColor = context.getResources().getColor( + mNormalRippleColor = context.getColor( R.color.notification_ripple_untinted_color); } @@ -388,7 +388,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } private void updateBackgroundTint() { - int color = getBackgroundColor(); + int color = getBgColor(); int rippleColor = getRippleColor(); if (color == mNormalColor) { // We don't need to tint a normal notification @@ -652,7 +652,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } private void updateAppearAnimationAlpha() { - int backgroundColor = getBackgroundColor(); + int backgroundColor = getBgColor(); if (backgroundColor != -1) { float contentAlphaProgress = mAppearAnimationFraction; contentAlphaProgress = contentAlphaProgress / (1.0f - ALPHA_ANIMATION_END); @@ -666,7 +666,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } } - private int getBackgroundColor() { + private int getBgColor() { if (mBgTint != 0) { return mBgTint; } else if (mShowingLegacyBackground) { @@ -678,7 +678,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } } - private int getRippleColor() { + protected int getRippleColor() { if (mBgTint != 0) { return mTintedRippleColor; } else if (mShowingLegacyBackground) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedButton.java new file mode 100644 index 0000000..87c12c2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedButton.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.Button; + +/** + * A Button which doesn't have overlapping drawing commands + */ +public class AlphaOptimizedButton extends Button { + public AlphaOptimizedButton(Context context) { + super(context); + } + + public AlphaOptimizedButton(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public AlphaOptimizedButton(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public AlphaOptimizedButton(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + public boolean hasOverlappingRendering() { + return false; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java index a835c0e..359272e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java @@ -19,7 +19,6 @@ package com.android.systemui.statusbar; import android.content.Context; import android.util.AttributeSet; import android.widget.FrameLayout; -import android.widget.LinearLayout; /** * A frame layout which does not have overlapping renderings commands and therefore does not need a diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedImageView.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedImageView.java index 094161d..858c118 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedImageView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedImageView.java @@ -18,7 +18,6 @@ package com.android.systemui.statusbar; import android.content.Context; import android.util.AttributeSet; -import android.view.View; import android.widget.ImageView; /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index 8a03a2b..f75dd73 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -24,6 +24,7 @@ import android.app.ActivityManagerNative; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; +import android.app.RemoteInput; import android.app.TaskStackBuilder; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; @@ -49,6 +50,7 @@ import android.os.Message; import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; @@ -69,16 +71,13 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewAnimationUtils; import android.view.ViewGroup; -import android.view.ViewGroup.LayoutParams; import android.view.ViewParent; -import android.view.ViewStub; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityManager; import android.view.animation.AnimationUtils; import android.widget.DateTimeView; import android.widget.ImageView; -import android.widget.LinearLayout; import android.widget.RemoteViews; import android.widget.TextView; @@ -89,14 +88,16 @@ import com.android.internal.util.NotificationColorUtil; import com.android.internal.widget.LockPatternUtils; import com.android.systemui.R; import com.android.systemui.RecentsComponent; -import com.android.systemui.SearchPanelView; import com.android.systemui.SwipeHelper; import com.android.systemui.SystemUI; +import com.android.systemui.recents.Recents; import com.android.systemui.statusbar.NotificationData.Entry; import com.android.systemui.statusbar.phone.NavigationBarView; +import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.HeadsUpNotificationView; import com.android.systemui.statusbar.policy.PreviewInflater; +import com.android.systemui.statusbar.policy.RemoteInputView; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import java.util.ArrayList; @@ -116,6 +117,11 @@ public abstract class BaseStatusBar extends SystemUI implements // STOPSHIP disable once we resolve b/18102199 private static final boolean NOTIFICATION_CLICK_DEBUG = true; + public static final boolean ENABLE_REMOTE_INPUT = + Build.IS_DEBUGGABLE && SystemProperties.getBoolean("debug.enable_remote_input", false); + public static final boolean ENABLE_CHILD_NOTIFICATIONS = Build.IS_DEBUGGABLE + && SystemProperties.getBoolean("debug.child_notifs", false); + protected static final int MSG_SHOW_RECENT_APPS = 1019; protected static final int MSG_HIDE_RECENT_APPS = 1020; protected static final int MSG_TOGGLE_RECENTS_APPS = 1021; @@ -123,11 +129,9 @@ public abstract class BaseStatusBar extends SystemUI implements protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023; protected static final int MSG_SHOW_NEXT_AFFILIATED_TASK = 1024; protected static final int MSG_SHOW_PREV_AFFILIATED_TASK = 1025; - protected static final int MSG_CLOSE_SEARCH_PANEL = 1027; protected static final int MSG_SHOW_HEADS_UP = 1028; protected static final int MSG_HIDE_HEADS_UP = 1029; protected static final int MSG_ESCALATE_HEADS_UP = 1030; - protected static final int MSG_DECAY_HEADS_UP = 1031; protected static final boolean ENABLE_HEADS_UP = true; // scores above this threshold should be displayed in heads up mode. @@ -137,10 +141,6 @@ public abstract class BaseStatusBar extends SystemUI implements // Should match the value in PhoneWindowManager public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps"; - public static final int EXPANDED_LEAVE_ALONE = -10000; - public static final int EXPANDED_FULL_OPEN = -10001; - - private static final int HIDDEN_NOTIFICATION_ID = 10000; private static final String BANNER_ACTION_CANCEL = "com.android.systemui.statusbar.banner_action_cancel"; private static final String BANNER_ACTION_SETUP = @@ -154,13 +154,12 @@ public abstract class BaseStatusBar extends SystemUI implements protected NotificationData mNotificationData; protected NotificationStackScrollLayout mStackScroller; + protected NotificationGroupManager mGroupManager = new NotificationGroupManager(); + // for heads up notifications protected HeadsUpNotificationView mHeadsUpNotificationView; protected int mHeadsUpNotificationDecay; - // Search panel - protected SearchPanelView mSearchPanelView; - protected int mCurrentUserId = 0; final protected SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>(); @@ -387,7 +386,7 @@ public abstract class BaseStatusBar extends SystemUI implements } else if (BANNER_ACTION_CANCEL.equals(action) || BANNER_ACTION_SETUP.equals(action)) { NotificationManager noMan = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); - noMan.cancel(HIDDEN_NOTIFICATION_ID); + noMan.cancel(R.id.notification_hidden); Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0); @@ -414,7 +413,7 @@ public abstract class BaseStatusBar extends SystemUI implements @Override public void run() { for (StatusBarNotification sbn : notifications) { - addNotification(sbn, currentRanking); + addNotification(sbn, currentRanking, null /* oldEntry */); } } }); @@ -424,61 +423,69 @@ public abstract class BaseStatusBar extends SystemUI implements public void onNotificationPosted(final StatusBarNotification sbn, final RankingMap rankingMap) { if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn); - mHandler.post(new Runnable() { - @Override - public void run() { - Notification n = sbn.getNotification(); - boolean isUpdate = mNotificationData.get(sbn.getKey()) != null - || isHeadsUp(sbn.getKey()); - - // Ignore children of notifications that have a summary, since we're not - // going to show them anyway. This is true also when the summary is canceled, - // because children are automatically canceled by NoMan in that case. - if (n.isGroupChild() && - mNotificationData.isGroupWithSummary(sbn.getGroupKey())) { - if (DEBUG) { - Log.d(TAG, "Ignoring group child due to existing summary: " + sbn); - } + if (sbn != null) { + mHandler.post(new Runnable() { + @Override + public void run() { + processForRemoteInput(sbn.getNotification()); + Notification n = sbn.getNotification(); + boolean isUpdate = mNotificationData.get(sbn.getKey()) != null + || isHeadsUp(sbn.getKey()); + + // In case we don't allow child notifications, we ignore children of + // notifications that have a summary, since we're not going to show them + // anyway. This is true also when the summary is canceled, + // because children are automatically canceled by NoMan in that case. + if (!ENABLE_CHILD_NOTIFICATIONS + && mGroupManager.isChildInGroupWithSummary(sbn)) { + if (DEBUG) { + Log.d(TAG, "Ignoring group child due to existing summary: " + sbn); + } - // Remove existing notification to avoid stale data. + // Remove existing notification to avoid stale data. + if (isUpdate) { + removeNotification(sbn.getKey(), rankingMap); + } else { + mNotificationData.updateRanking(rankingMap); + } + return; + } if (isUpdate) { - removeNotification(sbn.getKey(), rankingMap); + updateNotification(sbn, rankingMap); } else { - mNotificationData.updateRanking(rankingMap); + addNotification(sbn, rankingMap, null /* oldEntry */); } - return; - } - if (isUpdate) { - updateNotification(sbn, rankingMap); - } else { - addNotification(sbn, rankingMap); } - } - }); + }); + } } @Override - public void onNotificationRemoved(final StatusBarNotification sbn, + public void onNotificationRemoved(StatusBarNotification sbn, final RankingMap rankingMap) { if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn); - mHandler.post(new Runnable() { - @Override - public void run() { - removeNotification(sbn.getKey(), rankingMap); - } - }); + if (sbn != null) { + final String key = sbn.getKey(); + mHandler.post(new Runnable() { + @Override + public void run() { + removeNotification(key, rankingMap); + } + }); + } } @Override public void onNotificationRankingUpdate(final RankingMap rankingMap) { if (DEBUG) Log.d(TAG, "onRankingUpdate"); + if (rankingMap != null) { mHandler.post(new Runnable() { @Override public void run() { updateNotificationRanking(rankingMap); } }); - } + } } }; @@ -511,7 +518,6 @@ public abstract class BaseStatusBar extends SystemUI implements ServiceManager.checkService(DreamService.DREAM_SERVICE)); mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); - mSettingsObserver.onChange(false); // set up mContext.getContentResolver().registerContentObserver( Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), true, mSettingsObserver); @@ -532,7 +538,7 @@ public abstract class BaseStatusBar extends SystemUI implements mBarService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); - mRecents = getComponent(RecentsComponent.class); + mRecents = getComponent(Recents.class); mRecents.setCallback(this); final Configuration currentConfig = mContext.getResources().getConfiguration(); @@ -561,6 +567,7 @@ public abstract class BaseStatusBar extends SystemUI implements createAndAddWindows(); + mSettingsObserver.onChange(false); // set up disable(switches[0], false /* animate */); setSystemUiVisibility(switches[1], 0xffffffff); topAppWindowChanged(switches[2] != 0); @@ -647,7 +654,7 @@ public abstract class BaseStatusBar extends SystemUI implements .setContentText(mContext.getString(R.string.hidden_notifications_text)) .setPriority(Notification.PRIORITY_HIGH) .setOngoing(true) - .setColor(res.getColor(colorRes)) + .setColor(mContext.getColor(colorRes)) .setContentIntent(setupIntent) .addAction(R.drawable.ic_close, mContext.getString(R.string.hidden_notifications_cancel), @@ -658,7 +665,7 @@ public abstract class BaseStatusBar extends SystemUI implements NotificationManager noMan = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); - noMan.notify(HIDDEN_NOTIFICATION_ID, note.build()); + noMan.notify(R.id.notification_hidden, note.build()); } } @@ -698,6 +705,11 @@ public abstract class BaseStatusBar extends SystemUI implements return null; } + @Override + public NotificationGroupManager getGroupManager() { + return mGroupManager; + } + /** * Takes the necessary steps to prepare the status bar for starting an activity, then starts it. * @param action A dismiss action that is called if it's safe to start the activity. @@ -770,18 +782,14 @@ public abstract class BaseStatusBar extends SystemUI implements final int color = sbn.getNotification().color; if (isMediaNotification(entry)) { entry.row.setTintColor(color == Notification.COLOR_DEFAULT - ? mContext.getResources().getColor( + ? mContext.getColor( R.color.notification_material_background_media_default_color) : color); } } if (entry.icon != null) { - if (entry.targetSdk >= Build.VERSION_CODES.LOLLIPOP) { - entry.icon.setColorFilter(mContext.getResources().getColor(android.R.color.white)); - } else { - entry.icon.setColorFilter(null); - } + entry.icon.setTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP); } } @@ -834,16 +842,13 @@ public abstract class BaseStatusBar extends SystemUI implements }, false /* afterKeyguardGone */); } - private void inflateGuts(ExpandableNotificationRow row) { - ViewStub stub = (ViewStub) row.findViewById(R.id.notification_guts_stub); - if (stub != null) { - stub.inflate(); - } + private void bindGuts(ExpandableNotificationRow row) { + row.inflateGuts(); final StatusBarNotification sbn = row.getStatusBarNotification(); PackageManager pmUser = getPackageManagerForUser( sbn.getUser().getIdentifier()); row.setTag(sbn.getPackageName()); - final View guts = row.findViewById(R.id.notification_guts); + final View guts = row.getGuts(); final String pkg = sbn.getPackageName(); String appname = pkg; Drawable pkgicon = null; @@ -921,11 +926,11 @@ public abstract class BaseStatusBar extends SystemUI implements return false; } - inflateGuts((ExpandableNotificationRow) v); + ExpandableNotificationRow row = (ExpandableNotificationRow) v; + bindGuts(row); // Assume we are a status_bar_notification_row - final NotificationGuts guts = (NotificationGuts) v.findViewById( - R.id.notification_guts); + final NotificationGuts guts = row.getGuts(); if (guts == null) { // This view has no guts. Examples are the more card or the dismiss all view return false; @@ -1031,50 +1036,6 @@ public abstract class BaseStatusBar extends SystemUI implements mHandler.sendEmptyMessage(msg); } - @Override - public void showSearchPanel() { - if (mSearchPanelView != null && mSearchPanelView.isAssistantAvailable()) { - mSearchPanelView.show(true, true); - } - } - - @Override - public void hideSearchPanel() { - int msg = MSG_CLOSE_SEARCH_PANEL; - mHandler.removeMessages(msg); - mHandler.sendEmptyMessage(msg); - } - - protected abstract WindowManager.LayoutParams getSearchLayoutParams( - LayoutParams layoutParams); - - protected void updateSearchPanel() { - // Search Panel - boolean visible = false; - if (mSearchPanelView != null) { - visible = mSearchPanelView.isShowing(); - mWindowManager.removeView(mSearchPanelView); - } - - // Provide SearchPanel with a temporary parent to allow layout params to work. - LinearLayout tmpRoot = new LinearLayout(mContext); - mSearchPanelView = (SearchPanelView) LayoutInflater.from(mContext).inflate( - R.layout.status_bar_search_panel, tmpRoot, false); - mSearchPanelView.setOnTouchListener( - new TouchOutsideListener(MSG_CLOSE_SEARCH_PANEL, mSearchPanelView)); - mSearchPanelView.setVisibility(View.GONE); - boolean vertical = mNavigationBarView != null && mNavigationBarView.isVertical(); - mSearchPanelView.setHorizontal(vertical); - - WindowManager.LayoutParams lp = getSearchLayoutParams(mSearchPanelView.getLayoutParams()); - - mWindowManager.addView(mSearchPanelView, lp); - mSearchPanelView.setBar(this); - if (visible) { - mSearchPanelView.show(true, false); - } - } - protected H createHandler() { return new H(); } @@ -1161,7 +1122,7 @@ public abstract class BaseStatusBar extends SystemUI implements // Do nothing } - public abstract void resetHeadsUpDecayTimer(); + public abstract void scheduleHeadsUpDecay(long delay); public abstract void scheduleHeadsUpOpen(); @@ -1190,14 +1151,15 @@ public abstract class BaseStatusBar extends SystemUI implements } if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) { - final boolean allowed = 0 != Settings.Secure.getIntForUser( + final boolean allowedByUser = 0 != Settings.Secure.getIntForUser( mContext.getContentResolver(), Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle); final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(null /* admin */, userHandle); final boolean allowedByDpm = (dpmFlags & DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS) == 0; - mUsersAllowingPrivateNotifications.append(userHandle, allowed && allowedByDpm); + final boolean allowed = allowedByUser && allowedByDpm; + mUsersAllowingPrivateNotifications.append(userHandle, allowed); return allowed; } @@ -1250,47 +1212,19 @@ public abstract class BaseStatusBar extends SystemUI implements case MSG_SHOW_PREV_AFFILIATED_TASK: showRecentsPreviousAffiliatedTask(); break; - case MSG_CLOSE_SEARCH_PANEL: - if (DEBUG) Log.d(TAG, "closing search panel"); - if (mSearchPanelView != null && mSearchPanelView.isShowing()) { - mSearchPanelView.show(false, true); - } - break; } } } - public class TouchOutsideListener implements View.OnTouchListener { - private int mMsg; - private StatusBarPanel mPanel; - - public TouchOutsideListener(int msg, StatusBarPanel panel) { - mMsg = msg; - mPanel = panel; - } - - public boolean onTouch(View v, MotionEvent ev) { - final int action = ev.getAction(); - if (action == MotionEvent.ACTION_OUTSIDE - || (action == MotionEvent.ACTION_DOWN - && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) { - mHandler.removeMessages(mMsg); - mHandler.sendEmptyMessage(mMsg); - return true; - } - return false; - } - } - protected void workAroundBadLayerDrawableOpacity(View v) { } - private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) { + protected boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) { return inflateViews(entry, parent, false); } protected boolean inflateViewsForHeadsUp(NotificationData.Entry entry, ViewGroup parent) { - return inflateViews(entry, parent, true); + return inflateViews(entry, parent, true); } private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent, boolean isHeadsUp) { @@ -1331,6 +1265,7 @@ public abstract class BaseStatusBar extends SystemUI implements hasUserChangedExpansion = row.hasUserChangedExpansion(); userExpanded = row.isUserExpanded(); userLocked = row.isUserLocked(); + entry.row.setHeadsUp(isHeadsUp); entry.reset(); if (hasUserChangedExpansion) { row.setUserExpanded(userExpanded); @@ -1342,6 +1277,7 @@ public abstract class BaseStatusBar extends SystemUI implements row = (ExpandableNotificationRow) inflater.inflate(R.layout.status_bar_notification_row, parent, false); row.setExpansionLogger(this, entry.notification.getKey()); + row.setGroupManager(mGroupManager); } workAroundBadLayerDrawableOpacity(row); @@ -1358,11 +1294,13 @@ public abstract class BaseStatusBar extends SystemUI implements (NotificationContentView) row.findViewById(R.id.expandedPublic); row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); + if (ENABLE_REMOTE_INPUT) { + row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); + } PendingIntent contentIntent = sbn.getNotification().contentIntent; if (contentIntent != null) { - final View.OnClickListener listener = makeClicker(contentIntent, sbn.getKey(), - isHeadsUp); + final View.OnClickListener listener = makeClicker(contentIntent, sbn.getKey()); row.setOnClickListener(listener); } else { row.setOnClickListener(null); @@ -1520,23 +1458,114 @@ public abstract class BaseStatusBar extends SystemUI implements } row.setUserLocked(userLocked); row.setStatusBarNotification(entry.notification); + applyRemoteInput(entry); return true; } - public NotificationClicker makeClicker(PendingIntent intent, String notificationKey, - boolean forHun) { - return new NotificationClicker(intent, notificationKey, forHun); + /** + * Adds RemoteInput actions from the WearableExtender; to be removed once more apps support this + * via first-class API. + * + * TODO: Remove once enough apps specify remote inputs on their own. + */ + private void processForRemoteInput(Notification n) { + if (!ENABLE_REMOTE_INPUT) return; + + if (n.extras != null && n.extras.containsKey("android.wearable.EXTENSIONS") && + (n.actions == null || n.actions.length == 0)) { + Notification.Action viableAction = null; + Notification.WearableExtender we = new Notification.WearableExtender(n); + + List<Notification.Action> actions = we.getActions(); + final int numActions = actions.size(); + + for (int i = 0; i < numActions; i++) { + Notification.Action action = actions.get(i); + RemoteInput[] remoteInputs = action.getRemoteInputs(); + for (RemoteInput ri : action.getRemoteInputs()) { + if (ri.getAllowFreeFormInput()) { + viableAction = action; + break; + } + } + if (viableAction != null) { + break; + } + } + + if (viableAction != null) { + Notification stripped = n.clone(); + Notification.Builder.stripForDelivery(stripped); + stripped.actions = new Notification.Action[] { viableAction }; + stripped.extras.putBoolean("android.rebuild.contentView", true); + stripped.contentView = null; + stripped.extras.putBoolean("android.rebuild.bigView", true); + stripped.bigContentView = null; + + // Don't create the HUN input view for now because input doesn't work there yet. + // TODO: Enable once HUNs can take remote input correctly. + if (false) { + stripped.extras.putBoolean("android.rebuild.hudView", true); + stripped.headsUpContentView = null; + } + + Notification rebuilt = Notification.Builder.rebuild(mContext, stripped); + + n.actions = rebuilt.actions; + n.bigContentView = rebuilt.bigContentView; + n.headsUpContentView = rebuilt.headsUpContentView; + n.publicVersion = rebuilt.publicVersion; + } + } + } + + private void applyRemoteInput(final Entry entry) { + if (!ENABLE_REMOTE_INPUT) return; + + RemoteInput remoteInput = null; + + // See if the notification has exactly one action and this action allows free-form input + // TODO: relax restrictions once we support more than one remote input action. + Notification.Action[] actions = entry.notification.getNotification().actions; + if (actions != null && actions.length == 1) { + if (actions[0].getRemoteInputs() != null) { + for (RemoteInput ri : actions[0].getRemoteInputs()) { + if (ri.getAllowFreeFormInput()) { + remoteInput = ri; + break; + } + } + } + } + + // See if we have somewhere to put that remote input + ViewGroup actionContainer = null; + if (remoteInput != null && entry.expandedBig != null) { + View actionContainerCandidate = entry.expandedBig + .findViewById(com.android.internal.R.id.actions); + if (actionContainerCandidate instanceof ViewGroup) { + actionContainer = (ViewGroup) actionContainerCandidate; + } + } + + if (actionContainer != null) { + actionContainer.removeAllViews(); + actionContainer.addView( + RemoteInputView.inflate(mContext, actionContainer, actions[0], remoteInput)); + } + } + + public NotificationClicker makeClicker(PendingIntent intent, String notificationKey) { + return new NotificationClicker(intent, notificationKey); } protected class NotificationClicker implements View.OnClickListener { private PendingIntent mIntent; private final String mNotificationKey; - private boolean mIsHeadsUp; - public NotificationClicker(PendingIntent intent, String notificationKey, boolean forHun) { + public NotificationClicker(PendingIntent intent, String notificationKey) { mIntent = intent; mNotificationKey = notificationKey; - mIsHeadsUp = forHun; } public void onClick(final View v) { @@ -1549,12 +1578,12 @@ public abstract class BaseStatusBar extends SystemUI implements mCurrentUserId); dismissKeyguardThenExecute(new OnDismissAction() { public boolean onDismiss() { - if (mIsHeadsUp) { + if (mNotificationKey.equals(mHeadsUpNotificationView.getKey())) { // Release the HUN notification to the shade. // // In most cases, when FLAG_AUTO_CANCEL is set, the notification will // become canceled shortly by NoMan, but we can't assume that. - mHeadsUpNotificationView.releaseAndClose(); + mHeadsUpNotificationView.releaseImmediately(); } new Thread() { @Override @@ -1693,6 +1722,21 @@ public abstract class BaseStatusBar extends SystemUI implements if (DEBUG) { Log.d(TAG, "createNotificationViews(notification=" + sbn); } + final StatusBarIconView iconView = createIcon(sbn); + if (iconView == null) { + return null; + } + + // Construct the expanded view. + NotificationData.Entry entry = new NotificationData.Entry(sbn, iconView); + if (!inflateViews(entry, mStackScroller)) { + handleNotificationError(sbn, "Couldn't expand RemoteViews for: " + sbn); + return null; + } + return entry; + } + + protected StatusBarIconView createIcon(StatusBarNotification sbn) { // Construct the icon. Notification n = sbn.getNotification(); final StatusBarIconView iconView = new StatusBarIconView(mContext, @@ -1709,13 +1753,7 @@ public abstract class BaseStatusBar extends SystemUI implements handleNotificationError(sbn, "Couldn't create icon: " + ic); return null; } - // Construct the expanded view. - NotificationData.Entry entry = new NotificationData.Entry(sbn, iconView); - if (!inflateViews(entry, mStackScroller)) { - handleNotificationError(sbn, "Couldn't expand RemoteViews for: " + sbn); - return null; - } - return entry; + return iconView; } protected void addNotificationViews(Entry entry, RankingMap ranking) { @@ -1755,22 +1793,25 @@ public abstract class BaseStatusBar extends SystemUI implements entry.row.setSystemExpanded(top); } } + boolean isInvisibleChild = !mGroupManager.isVisible(entry.notification); boolean showOnKeyguard = shouldShowOnKeyguard(entry.notification); if ((isLockscreenPublicMode() && !mShowLockscreenNotifications) || (onKeyguard && (visibleNotifications >= maxKeyguardNotifications - || !showOnKeyguard))) { + || !showOnKeyguard || isInvisibleChild))) { entry.row.setVisibility(View.GONE); - if (onKeyguard && showOnKeyguard) { + if (onKeyguard && showOnKeyguard && !isInvisibleChild) { mKeyguardIconOverflowContainer.getIconsView().addNotification(entry); } } else { boolean wasGone = entry.row.getVisibility() == View.GONE; entry.row.setVisibility(View.VISIBLE); - if (wasGone) { - // notify the scroller of a child addition - mStackScroller.generateAddAnimation(entry.row, true /* fromMoreCard */); + if (!isInvisibleChild) { + if (wasGone) { + // notify the scroller of a child addition + mStackScroller.generateAddAnimation(entry.row, true /* fromMoreCard */); + } + visibleNotifications++; } - visibleNotifications++; } } @@ -1813,15 +1854,12 @@ public abstract class BaseStatusBar extends SystemUI implements setShowLockscreenNotifications(show && allowedByDpm); } - protected abstract void haltTicker(); protected abstract void setAreThereNotifications(); protected abstract void updateNotifications(); - protected abstract void tick(StatusBarNotification n, boolean firstTime); - protected abstract void updateExpandedViewPos(int expandedPosition); - protected abstract boolean shouldDisableNavbarGestures(); + public abstract boolean shouldDisableNavbarGestures(); public abstract void addNotification(StatusBarNotification notification, - RankingMap ranking); + RankingMap ranking, Entry oldEntry); protected abstract void updateNotificationRanking(RankingMap ranking); public abstract void removeNotification(String key, RankingMap ranking); @@ -1902,17 +1940,15 @@ public abstract class BaseStatusBar extends SystemUI implements && oldPublicContentView.getPackage() != null && oldPublicContentView.getPackage().equals(publicContentView.getPackage()) && oldPublicContentView.getLayoutId() == publicContentView.getLayoutId()); - boolean updateTicker = n.tickerText != null - && !TextUtils.equals(n.tickerText, - oldEntry.notification.getNotification().tickerText); final boolean shouldInterrupt = shouldInterrupt(notification); - final boolean alertAgain = alertAgain(oldEntry, n); + final boolean alertAgain = shouldInterrupt && alertAgain(oldEntry, n); boolean updateSuccessful = false; if (contentsUnchanged && bigContentsUnchanged && headsUpContentsUnchanged && publicUnchanged) { if (DEBUG) Log.d(TAG, "reusing notification for key: " + key); oldEntry.notification = notification; + mGroupManager.onEntryUpdated(oldEntry, oldNotification); try { if (oldEntry.icon != null) { // Update the icon @@ -1930,20 +1966,20 @@ public abstract class BaseStatusBar extends SystemUI implements } if (wasHeadsUp) { - if (shouldInterrupt) { - updateHeadsUpViews(oldEntry, notification); - if (alertAgain) { - resetHeadsUpDecayTimer(); - } - } else { + // Release may hang on to the views for a bit, so we should always update them. + updateHeadsUpViews(oldEntry, notification); + mHeadsUpNotificationView.updateNotification(oldEntry, alertAgain); + if (!shouldInterrupt) { // we updated the notification above, so release to build a new shade entry - mHeadsUpNotificationView.releaseAndClose(); + mHeadsUpNotificationView.release(); return; } } else { if (shouldInterrupt && alertAgain) { + mStackScroller.setRemoveAnimationEnabled(false); removeNotificationViews(key, ranking); - addNotification(notification, ranking); //this will pop the headsup + mStackScroller.setRemoveAnimationEnabled(true); + addNotification(notification, ranking, oldEntry); //this will pop the headsup } else { updateNotificationViews(oldEntry, notification); } @@ -1960,33 +1996,32 @@ public abstract class BaseStatusBar extends SystemUI implements if (!updateSuccessful) { if (DEBUG) Log.d(TAG, "not reusing notification for key: " + key); if (wasHeadsUp) { - if (shouldInterrupt) { - if (DEBUG) Log.d(TAG, "rebuilding heads up for key: " + key); - Entry newEntry = new Entry(notification, null); - ViewGroup holder = mHeadsUpNotificationView.getHolder(); - if (inflateViewsForHeadsUp(newEntry, holder)) { - mHeadsUpNotificationView.showNotification(newEntry); - if (alertAgain) { - resetHeadsUpDecayTimer(); - } - } else { - Log.w(TAG, "Couldn't create new updated headsup for package " - + contentView.getPackage()); - } + if (DEBUG) Log.d(TAG, "rebuilding heads up for key: " + key); + ViewGroup holder = mHeadsUpNotificationView.getHolder(); + if (inflateViewsForHeadsUp(oldEntry, holder)) { + mHeadsUpNotificationView.updateNotification(oldEntry, alertAgain); } else { + Log.w(TAG, "Couldn't create new updated headsup for package " + + contentView.getPackage()); + } + if (!shouldInterrupt) { if (DEBUG) Log.d(TAG, "releasing heads up for key: " + key); oldEntry.notification = notification; - mHeadsUpNotificationView.releaseAndClose(); + mGroupManager.onEntryUpdated(oldEntry, oldNotification); + mHeadsUpNotificationView.release(); return; } } else { if (shouldInterrupt && alertAgain) { if (DEBUG) Log.d(TAG, "reposting to invoke heads up for key: " + key); + mStackScroller.setRemoveAnimationEnabled(false); removeNotificationViews(key, ranking); - addNotification(notification, ranking); //this will pop the headsup + mStackScroller.setRemoveAnimationEnabled(true); + addNotification(notification, ranking, oldEntry); //this will pop the headsup } else { if (DEBUG) Log.d(TAG, "rebuilding update in place for key: " + key); oldEntry.notification = notification; + mGroupManager.onEntryUpdated(oldEntry, oldNotification); final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(), notification.getUser(), n.icon, @@ -2010,15 +2045,8 @@ public abstract class BaseStatusBar extends SystemUI implements boolean isForCurrentUser = isNotificationForCurrentProfiles(notification); if (DEBUG) Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you"); - // Restart the ticker if it's still running - if (updateTicker && isForCurrentUser) { - haltTicker(); - tick(notification, false); - } - // Recalculate the position of the sliding windows and the titles. setAreThereNotifications(); - updateExpandedViewPos(EXPANDED_LEAVE_ALONE); } private void updateNotificationViews(NotificationData.Entry entry, @@ -2053,8 +2081,7 @@ public abstract class BaseStatusBar extends SystemUI implements // update the contentIntent final PendingIntent contentIntent = notification.getNotification().contentIntent; if (contentIntent != null) { - final View.OnClickListener listener = makeClicker(contentIntent, notification.getKey(), - isHeadsUp); + final View.OnClickListener listener = makeClicker(contentIntent, notification.getKey()); entry.row.setOnClickListener(listener); } else { entry.row.setOnClickListener(null); @@ -2062,6 +2089,8 @@ public abstract class BaseStatusBar extends SystemUI implements entry.row.setStatusBarNotification(notification); entry.row.notifyContentUpdated(); entry.row.resetHeight(); + + applyRemoteInput(entry); } protected void notifyHeadsUpScreenOn(boolean screenOn) { @@ -2133,9 +2162,6 @@ public abstract class BaseStatusBar extends SystemUI implements } public void destroy() { - if (mSearchPanelView != null) { - mWindowManager.removeViewImmediate(mSearchPanelView); - } mContext.unregisterReceiver(mBroadcastReceiver); try { mNotificationListener.unregisterAsSystemService(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 0b1b883..7aa9a90 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar; import android.os.Handler; import android.os.IBinder; import android.os.Message; +import android.util.Pair; import com.android.internal.statusbar.IStatusBar; import com.android.internal.statusbar.StatusBarIcon; @@ -57,6 +58,9 @@ public class CommandQueue extends IStatusBar.Stub { private static final int MSG_NOTIFICATION_LIGHT_OFF = 16 << MSG_SHIFT; private static final int MSG_NOTIFICATION_LIGHT_PULSE = 17 << MSG_SHIFT; private static final int MSG_SHOW_SCREEN_PIN_REQUEST = 18 << MSG_SHIFT; + private static final int MSG_APP_TRANSITION_PENDING = 19 << MSG_SHIFT; + private static final int MSG_APP_TRANSITION_CANCELLED = 20 << MSG_SHIFT; + private static final int MSG_APP_TRANSITION_STARTING = 21 << MSG_SHIFT; public static final int FLAG_EXCLUDE_NONE = 0; public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0; @@ -92,13 +96,14 @@ public class CommandQueue extends IStatusBar.Stub { public void toggleRecentApps(); public void preloadRecentApps(); public void cancelPreloadRecentApps(); - public void showSearchPanel(); - public void hideSearchPanel(); public void setWindowState(int window, int state); public void buzzBeepBlinked(); public void notificationLightOff(); public void notificationLightPulse(int argb, int onMillis, int offMillis); public void showScreenPinningRequest(); + public void appTransitionPending(); + public void appTransitionCancelled(); + public void appTransitionStarting(long startTime, long duration); } public CommandQueue(Callbacks callbacks, StatusBarIconList list) { @@ -246,6 +251,28 @@ public class CommandQueue extends IStatusBar.Stub { } } + public void appTransitionPending() { + synchronized (mList) { + mHandler.removeMessages(MSG_APP_TRANSITION_PENDING); + mHandler.sendEmptyMessage(MSG_APP_TRANSITION_PENDING); + } + } + + public void appTransitionCancelled() { + synchronized (mList) { + mHandler.removeMessages(MSG_APP_TRANSITION_PENDING); + mHandler.sendEmptyMessage(MSG_APP_TRANSITION_PENDING); + } + } + + public void appTransitionStarting(long startTime, long duration) { + synchronized (mList) { + mHandler.removeMessages(MSG_APP_TRANSITION_STARTING); + mHandler.obtainMessage(MSG_APP_TRANSITION_STARTING, Pair.create(startTime, duration)) + .sendToTarget(); + } + } + private final class H extends Handler { public void handleMessage(Message msg) { final int what = msg.what & MSG_MASK; @@ -328,6 +355,16 @@ public class CommandQueue extends IStatusBar.Stub { case MSG_SHOW_SCREEN_PIN_REQUEST: mCallbacks.showScreenPinningRequest(); break; + case MSG_APP_TRANSITION_PENDING: + mCallbacks.appTransitionPending(); + break; + case MSG_APP_TRANSITION_CANCELLED: + mCallbacks.appTransitionCancelled(); + break; + case MSG_APP_TRANSITION_STARTING: + Pair<Long, Long> data = (Pair<Long, Long>) msg.obj; + mCallbacks.appTransitionStarting(data.first, data.second); + break; } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DelegateViewHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/DelegateViewHelper.java index 7ae6764..9e2207e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/DelegateViewHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/DelegateViewHelper.java @@ -22,11 +22,12 @@ import android.graphics.RectF; import android.view.MotionEvent; import android.view.View; import com.android.systemui.R; +import com.android.systemui.statusbar.phone.PhoneStatusBar; public class DelegateViewHelper { private View mDelegateView; private View mSourceView; - private BaseStatusBar mBar; + private PhoneStatusBar mBar; private int[] mTempPoint = new int[2]; private float[] mDownPoint = new float[2]; private float mTriggerThreshhold; @@ -45,7 +46,7 @@ public class DelegateViewHelper { mDelegateView = view; } - public void setBar(BaseStatusBar phoneStatusBar) { + public void setBar(PhoneStatusBar phoneStatusBar) { mBar = phoneStatusBar; } @@ -79,7 +80,7 @@ public class DelegateViewHelper { float y = k < historySize ? event.getHistoricalY(k) : event.getY(); final float distance = mSwapXY ? (mDownPoint[0] - x) : (mDownPoint[1] - y); if (distance > mTriggerThreshhold) { - mBar.showSearchPanel(); + mBar.invokeAssistGesture(false /* vibrate */); mPanelShowing = true; break; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DismissViewButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/DismissViewButton.java index f2a5673..00665f4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/DismissViewButton.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/DismissViewButton.java @@ -21,12 +21,9 @@ import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.Drawable; -import android.os.SystemClock; import android.util.AttributeSet; -import android.view.Choreographer; import android.view.View; import android.view.ViewGroup; -import android.view.ViewRootImpl; import android.widget.Button; import com.android.systemui.R; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java index c9f0260..15a092c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java @@ -162,20 +162,20 @@ public class DragDownHelper implements Gefingerpoken { ? RUBBERBAND_FACTOR_EXPANDABLE : RUBBERBAND_FACTOR_STATIC; float rubberband = heightDelta * rubberbandFactor; - if (expandable && (rubberband + child.getMinHeight()) > child.getMaxHeight()) { - float overshoot = (rubberband + child.getMinHeight()) - child.getMaxHeight(); + if (expandable && (rubberband + child.getMinHeight()) > child.getMaxContentHeight()) { + float overshoot = (rubberband + child.getMinHeight()) - child.getMaxContentHeight(); overshoot *= (1 - RUBBERBAND_FACTOR_STATIC); rubberband -= overshoot; } - child.setActualHeight((int) (child.getMinHeight() + rubberband)); + child.setContentHeight((int) (child.getMinHeight() + rubberband)); } private void cancelExpansion(final ExpandableView child) { - if (child.getActualHeight() == child.getMinHeight()) { + if (child.getContentHeight() == child.getMinHeight()) { return; } - ObjectAnimator anim = ObjectAnimator.ofInt(child, "actualHeight", - child.getActualHeight(), child.getMinHeight()); + ObjectAnimator anim = ObjectAnimator.ofInt(child, "contentHeight", + child.getContentHeight(), child.getMinHeight()); anim.setInterpolator(mInterpolator); anim.setDuration(SPRING_BACK_ANIMATION_LENGTH_MS); anim.addListener(new AnimatorListenerAdapter() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java index 0825aa3..5db0699 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java @@ -20,11 +20,9 @@ import android.content.Context; import android.content.res.Configuration; import android.util.AttributeSet; import android.view.View; -import android.view.animation.Interpolator; import android.widget.TextView; import com.android.systemui.R; -import com.android.systemui.statusbar.phone.PhoneStatusBar; public class EmptyShadeView extends StackScrollerDecorView { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java index 8ad8406..06a174e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -16,9 +16,13 @@ package com.android.systemui.statusbar; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; import android.content.Context; import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.AnimationDrawable; +import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.service.notification.StatusBarNotification; import android.util.AttributeSet; @@ -26,12 +30,25 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewStub; import android.view.accessibility.AccessibilityEvent; +import android.view.animation.LinearInterpolator; import android.widget.ImageView; + import com.android.systemui.R; +import com.android.systemui.statusbar.phone.NotificationGroupManager; +import com.android.systemui.statusbar.phone.PhoneStatusBar; +import com.android.systemui.statusbar.stack.NotificationChildrenContainer; +import com.android.systemui.statusbar.stack.StackScrollState; +import com.android.systemui.statusbar.stack.StackStateAnimator; +import com.android.systemui.statusbar.stack.StackViewState; + +import java.util.List; public class ExpandableNotificationRow extends ActivatableNotificationView { + + private static final int DEFAULT_DIVIDER_ALPHA = 0x29; + private static final int COLORED_DIVIDER_ALPHA = 0x7B; + private final LinearInterpolator mLinearInterpolator = new LinearInterpolator(); private int mRowMinHeight; - private int mRowMaxHeight; /** Does this row contain layouts that can adapt to row expansion */ private boolean mExpandable; @@ -70,6 +87,27 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { private StatusBarNotification mStatusBarNotification; private boolean mIsHeadsUp; + private View mExpandButton; + private View mExpandButtonDivider; + private ViewStub mExpandButtonStub; + private ViewStub mChildrenContainerStub; + private NotificationGroupManager mGroupManager; + private View mExpandButtonContainer; + private boolean mChildrenExpanded; + private NotificationChildrenContainer mChildrenContainer; + private ValueAnimator mChildExpandAnimator; + private float mChildrenExpandProgress; + private float mExpandButtonStart; + private ViewStub mGutsStub; + private boolean mHasExpandAction; + private boolean mIsSystemChildExpanded; + private OnClickListener mExpandClickListener = new OnClickListener() { + @Override + public void onClick(View v) { + mGroupManager.setGroupExpanded(mStatusBarNotification, + !mChildrenExpanded); + } + }; public void setIconAnimationRunning(boolean running) { setIconAnimationRunning(running, mPublicLayout); @@ -119,6 +157,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { public void setStatusBarNotification(StatusBarNotification statusBarNotification) { mStatusBarNotification = statusBarNotification; updateVetoButton(); + updateExpandButton(); } public StatusBarNotification getStatusBarNotification() { @@ -129,6 +168,101 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { mIsHeadsUp = isHeadsUp; } + public void setGroupManager(NotificationGroupManager groupManager) { + mGroupManager = groupManager; + } + + public void addChildNotification(ExpandableNotificationRow row) { + addChildNotification(row, -1); + } + + /** + * Add a child notification to this view. + * + * @param row the row to add + * @param childIndex the index to add it at, if -1 it will be added at the end + */ + public void addChildNotification(ExpandableNotificationRow row, int childIndex) { + if (mChildrenContainer == null) { + mChildrenContainerStub.inflate(); + } + mChildrenContainer.addNotification(row, childIndex); + } + + public void removeChildNotification(ExpandableNotificationRow row) { + if (mChildrenContainer != null) { + mChildrenContainer.removeNotification(row); + } + } + + @Override + public boolean areChildrenExpanded() { + return mChildrenExpanded; + } + + public List<ExpandableNotificationRow> getNotificationChildren() { + return mChildrenContainer == null ? null : mChildrenContainer.getNotificationChildren(); + } + + /** + * Apply the order given in the list to the children. + * + * @param childOrder the new list order + * @return whether the list order has changed + */ + public boolean applyChildOrder(List<ExpandableNotificationRow> childOrder) { + return mChildrenContainer != null && mChildrenContainer.applyChildOrder(childOrder); + } + + public void getChildrenStates(StackScrollState resultState) { + if (mChildrenExpanded) { + StackViewState parentState = resultState.getViewStateForView(this); + mChildrenContainer.getState(resultState, parentState); + } + } + + public void applyChildrenState(StackScrollState state) { + if (mChildrenExpanded) { + mChildrenContainer.applyState(state); + } + } + + public void prepareExpansionChanged(StackScrollState state) { + if (mChildrenExpanded) { + mChildrenContainer.prepareExpansionChanged(state); + } + } + + public void startChildAnimation(StackScrollState finalState, + StackStateAnimator stateAnimator, boolean withDelays, long delay, long duration) { + if (mChildrenExpanded) { + mChildrenContainer.startAnimationToState(finalState, stateAnimator, withDelays, delay, + duration); + } + } + + public ExpandableNotificationRow getViewAtPosition(float y) { + if (!mChildrenExpanded) { + return this; + } else { + ExpandableNotificationRow view = mChildrenContainer.getViewAtPosition(y); + return view == null ? this : view; + } + } + + public NotificationGuts getGuts() { + return mGuts; + } + + protected int calculateContentHeightFromActualHeight(int actualHeight) { + int realActualHeight = actualHeight; + if (hasBottomDecor()) { + realActualHeight -= getBottomDecorHeight(); + } + realActualHeight = Math.max(getMinHeight(), realActualHeight); + return realActualHeight; + } + public interface ExpansionLogger { public void logNotificationExpansion(String key, boolean userAction, boolean expanded); } @@ -145,7 +279,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { super.reset(); mRowMinHeight = 0; final boolean wasExpanded = isExpanded(); - mRowMaxHeight = 0; + mMaxViewHeight = 0; mExpandable = false; mHasUserChangedExpansion = false; mUserLocked = false; @@ -180,21 +314,97 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { super.onFinishInflate(); mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic); mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded); - ViewStub gutsStub = (ViewStub) findViewById(R.id.notification_guts_stub); - gutsStub.setOnInflateListener(new ViewStub.OnInflateListener() { + mGutsStub = (ViewStub) findViewById(R.id.notification_guts_stub); + mGutsStub.setOnInflateListener(new ViewStub.OnInflateListener() { @Override public void onInflate(ViewStub stub, View inflated) { mGuts = (NotificationGuts) inflated; mGuts.setClipTopAmount(getClipTopAmount()); mGuts.setActualHeight(getActualHeight()); + mGutsStub = null; + } + }); + mExpandButtonStub = (ViewStub) findViewById(R.id.more_button_stub); + mExpandButtonStub.setOnInflateListener(new ViewStub.OnInflateListener() { + + @Override + public void onInflate(ViewStub stub, View inflated) { + mExpandButtonContainer = inflated; + mExpandButton = inflated.findViewById(R.id.notification_expand_button); + mExpandButtonDivider = inflated.findViewById(R.id.notification_expand_divider); + mExpandButtonContainer.setOnClickListener(mExpandClickListener); + } + }); + mChildrenContainerStub = (ViewStub) findViewById(R.id.child_container_stub); + mChildrenContainerStub.setOnInflateListener(new ViewStub.OnInflateListener() { + + @Override + public void onInflate(ViewStub stub, View inflated) { + mChildrenContainer = (NotificationChildrenContainer) inflated; + mChildrenContainer.setCollapseClickListener(mExpandClickListener); + updateChildrenVisibility(false); } }); mVetoButton = findViewById(R.id.veto); } + public void inflateGuts() { + if (mGuts == null) { + mGutsStub.inflate(); + } + } + + private void updateChildrenVisibility(boolean animated) { + if (mChildrenContainer == null) { + return; + } + if (mChildExpandAnimator != null) { + mChildExpandAnimator.cancel(); + } + float targetProgress = mChildrenExpanded ? 1.0f : 0.0f; + if (animated) { + if (mChildrenExpanded) { + mChildrenContainer.setVisibility(VISIBLE); + } + mExpandButtonStart = mExpandButtonContainer.getTranslationY(); + mChildExpandAnimator = ValueAnimator.ofFloat(mChildrenExpandProgress, targetProgress); + mChildExpandAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + setChildrenExpandProgress((float) animation.getAnimatedValue()); + } + }); + mChildExpandAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mChildExpandAnimator = null; + if (!mChildrenExpanded) { + mChildrenContainer.setVisibility(INVISIBLE); + } + } + }); + mChildExpandAnimator.setInterpolator(mLinearInterpolator); + mChildExpandAnimator.setDuration( + StackStateAnimator.ANIMATION_DURATION_EXPAND_CLICKED); + mChildExpandAnimator.start(); + } else { + setChildrenExpandProgress(targetProgress); + mChildrenContainer.setVisibility(mChildrenExpanded ? VISIBLE : INVISIBLE); + } + } + + private void setChildrenExpandProgress(float progress) { + mChildrenExpandProgress = progress; + updateExpandButtonAppearance(); + NotificationContentView showingLayout = getShowingLayout(); + float alpha = 1.0f - mChildrenExpandProgress; + alpha = PhoneStatusBar.ALPHA_OUT.getInterpolation(alpha); + showingLayout.setAlpha(alpha); + } + @Override - public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) { - if (super.onRequestSendAccessibilityEvent(child, event)) { + public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) { + if (super.onRequestSendAccessibilityEventInternal(child, event)) { // Add a record for the entire layout since its content is somehow small. // The event comes from a leaf view that is interacted with. AccessibilityEvent record = AccessibilityEvent.obtain(); @@ -217,7 +427,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { public void setHeightRange(int rowMinHeight, int rowMaxHeight) { mRowMinHeight = rowMinHeight; - mRowMaxHeight = rowMaxHeight; + mMaxViewHeight = rowMaxHeight; } public boolean isExpandable() { @@ -281,7 +491,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { if (expand != mIsSystemExpanded) { final boolean wasExpanded = isExpanded(); mIsSystemExpanded = expand; - notifyHeightChanged(); + notifyHeightChanged(false /* needsAnimation */); logExpansionEvent(false, wasExpanded); } } @@ -295,7 +505,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { mExpansionDisabled = expansionDisabled; logExpansionEvent(false, wasExpanded); if (wasExpanded != isExpanded()) { - notifyHeightChanged(); + notifyHeightChanged(false /* needsAnimation */); } } } @@ -313,9 +523,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { public void applyExpansionToLayout() { boolean expand = isExpanded(); if (expand && mExpandable) { - setActualHeight(mMaxExpandHeight); + setContentHeight(mMaxExpandHeight); } else { - setActualHeight(mRowMinHeight); + setContentHeight(mRowMinHeight); } } @@ -325,12 +535,26 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { return getActualHeight(); } boolean inExpansionState = isExpanded(); - if (!inExpansionState) { - // not expanded, so we return the collapsed size - return mRowMinHeight; + int maxContentHeight; + if ((!inExpansionState && !mChildrenExpanded) || mShowingPublicForIntrinsicHeight) { + maxContentHeight = mRowMinHeight; + } else if (mChildrenExpanded) { + maxContentHeight = mChildrenContainer.getIntrinsicHeight(); + } else { + maxContentHeight = getMaxExpandHeight(); } + return maxContentHeight + getBottomDecorHeight(); + } + + @Override + protected boolean hasBottomDecor() { + return BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS + && !mIsHeadsUp && mGroupManager.hasGroupChildren(mStatusBarNotification); + } - return mShowingPublicForIntrinsicHeight ? mRowMinHeight : getMaxExpandHeight(); + @Override + protected boolean canHaveBottomDecor() { + return BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS && !mIsHeadsUp; } /** @@ -343,7 +567,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { */ private boolean isExpanded() { return !mExpansionDisabled - && (!hasUserChangedExpansion() && isSystemExpanded() || isUserExpanded()); + && (!hasUserChangedExpansion() && (isSystemExpanded() || isSystemChildExpanded()) + || isUserExpanded()); + } + + private boolean isSystemChildExpanded() { + return mIsSystemChildExpanded; + } + + public void setSystemChildExpanded(boolean expanded) { + mIsSystemChildExpanded = expanded; } @Override @@ -357,11 +590,20 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { mWasReset = false; } + @Override + protected boolean isChildInvisible(View child) { + + // We don't want to layout the ChildrenContainer if this is a heads-up view, otherwise the + // view will get too high and the shadows will be off. + boolean isInvisibleChildContainer = child == mChildrenContainer && mIsHeadsUp; + return super.isChildInvisible(child) || isInvisibleChildContainer; + } + private void updateMaxExpandHeight() { int intrinsicBefore = getIntrinsicHeight(); mMaxExpandHeight = mPrivateLayout.getMaxHeight(); if (intrinsicBefore != getIntrinsicHeight()) { - notifyHeightChanged(); + notifyHeightChanged(false /* needsAnimation */); } } @@ -428,8 +670,127 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { mVetoButton.setVisibility(isClearable() && !mShowingPublic ? View.VISIBLE : View.GONE); } + public void setChildrenExpanded(boolean expanded, boolean animate) { + mChildrenExpanded = expanded; + updateChildrenVisibility(animate); + } + + public void updateExpandButton() { + boolean hasExpand = hasBottomDecor(); + if (hasExpand != mHasExpandAction) { + if (hasExpand) { + if (mExpandButtonContainer == null) { + mExpandButtonStub.inflate(); + } + mExpandButtonContainer.setVisibility(View.VISIBLE); + updateExpandButtonAppearance(); + updateExpandButtonColor(); + } else if (mExpandButtonContainer != null) { + mExpandButtonContainer.setVisibility(View.GONE); + } + notifyHeightChanged(true /* needsAnimation */); + } + mHasExpandAction = hasExpand; + } + + private void updateExpandButtonAppearance() { + if (mExpandButtonContainer == null) { + return; + } + float expandButtonAlpha = 0.0f; + float expandButtonTranslation = 0.0f; + float containerTranslation = 0.0f; + int minHeight = getMinHeight(); + if (!mChildrenExpanded || mChildExpandAnimator != null) { + int expandActionHeight = getBottomDecorHeight(); + int translationY = getActualHeight() - expandActionHeight; + if (translationY > minHeight) { + containerTranslation = translationY; + expandButtonAlpha = 1.0f; + expandButtonTranslation = 0.0f; + } else { + containerTranslation = minHeight; + float progress = expandActionHeight != 0 + ? (minHeight - translationY) / (float) expandActionHeight + : 1.0f; + expandButtonTranslation = -progress * expandActionHeight * 0.7f; + float alphaProgress = Math.min(progress / 0.7f, 1.0f); + alphaProgress = PhoneStatusBar.ALPHA_OUT.getInterpolation(alphaProgress); + expandButtonAlpha = 1.0f - alphaProgress; + } + } + if (mChildExpandAnimator != null || mChildrenExpanded) { + expandButtonAlpha = (1.0f - mChildrenExpandProgress) + * expandButtonAlpha; + expandButtonTranslation = (1.0f - mChildrenExpandProgress) + * expandButtonTranslation; + float newTranslation = -getBottomDecorHeight(); + + // We don't want to take the actual height of the view as this is already + // interpolated by a custom interpolator leading to a confusing animation. We want + // to have a stable end value to interpolate in between + float collapsedHeight = !mChildrenExpanded + ? Math.max(StackStateAnimator.getFinalActualHeight(this) + - getBottomDecorHeight(), minHeight) + : mExpandButtonStart; + float translationProgress = mFastOutSlowInInterpolator.getInterpolation( + mChildrenExpandProgress); + containerTranslation = (1.0f - translationProgress) * collapsedHeight + + translationProgress * newTranslation; + } + mExpandButton.setAlpha(expandButtonAlpha); + mExpandButtonDivider.setAlpha(expandButtonAlpha); + mExpandButton.setTranslationY(expandButtonTranslation); + mExpandButtonContainer.setTranslationY(containerTranslation); + NotificationContentView showingLayout = getShowingLayout(); + float layoutTranslation = + mExpandButtonContainer.getTranslationY() - showingLayout.getContentHeight(); + layoutTranslation = Math.min(layoutTranslation, 0); + if (!mChildrenExpanded && mChildExpandAnimator == null) { + // Needed for the DragDownHelper in order not to jump there, as the position + // can be negative for a short time. + layoutTranslation = 0; + } + showingLayout.setTranslationY(layoutTranslation); + if (mChildrenContainer != null) { + mChildrenContainer.setTranslationY( + mExpandButtonContainer.getTranslationY() + getBottomDecorHeight()); + } + } + + private void updateExpandButtonColor() { + // TODO: This needs some more baking, currently only the divider is colored according to + // the tint, but legacy black doesn't work yet perfectly for the button etc. + int color = getRippleColor(); + if (color == mNormalRippleColor) { + color = 0; + } + if (mExpandButtonDivider != null) { + applyTint(mExpandButtonDivider, color); + } + if (mChildrenContainer != null) { + mChildrenContainer.setTintColor(color); + } + } + + public static void applyTint(View v, int color) { + int alpha; + if (color != 0) { + alpha = COLORED_DIVIDER_ALPHA; + } else { + color = 0xff000000; + alpha = DEFAULT_DIVIDER_ALPHA; + } + if (v.getBackground() instanceof ColorDrawable) { + ColorDrawable background = (ColorDrawable) v.getBackground(); + background.mutate(); + background.setColor(color); + background.setAlpha(alpha); + } + } + public int getMaxExpandHeight() { - return mShowingPublicForIntrinsicHeight ? mRowMinHeight : mMaxExpandHeight; + return mMaxExpandHeight; } @Override @@ -440,17 +801,19 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { @Override public void setActualHeight(int height, boolean notifyListeners) { - mPrivateLayout.setActualHeight(height); - mPublicLayout.setActualHeight(height); + super.setActualHeight(height, notifyListeners); + int contentHeight = calculateContentHeightFromActualHeight(height); + mPrivateLayout.setContentHeight(contentHeight); + mPublicLayout.setContentHeight(contentHeight); if (mGuts != null) { mGuts.setActualHeight(height); } invalidate(); - super.setActualHeight(height, notifyListeners); + updateExpandButtonAppearance(); } @Override - public int getMaxHeight() { + public int getMaxContentHeight() { NotificationContentView showingLayout = getShowingLayout(); return showingLayout.getMaxHeight(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java index ebc663c..7ae0d6d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java @@ -33,24 +33,32 @@ import java.util.ArrayList; */ public abstract class ExpandableView extends FrameLayout { - private final int mMaxNotificationHeight; - - private OnHeightChangedListener mOnHeightChangedListener; + private final int mBottomDecorHeight; + protected OnHeightChangedListener mOnHeightChangedListener; + protected int mMaxViewHeight; private int mActualHeight; protected int mClipTopAmount; private boolean mActualHeightInitialized; private boolean mDark; private ArrayList<View> mMatchParentViews = new ArrayList<View>(); + private int mClipTopOptimization; + private static Rect mClipRect = new Rect(); public ExpandableView(Context context, AttributeSet attrs) { super(context, attrs); - mMaxNotificationHeight = getResources().getDimensionPixelSize( + mMaxViewHeight = getResources().getDimensionPixelSize( R.dimen.notification_max_height); + mBottomDecorHeight = resolveBottomDecorHeight(); + } + + protected int resolveBottomDecorHeight() { + return getResources().getDimensionPixelSize( + R.dimen.notification_bottom_decor_height); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int ownMaxHeight = mMaxNotificationHeight; + int ownMaxHeight = mMaxViewHeight; int heightMode = MeasureSpec.getMode(heightMeasureSpec); boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY; boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST; @@ -63,6 +71,9 @@ public abstract class ExpandableView extends FrameLayout { int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View child = getChildAt(i); + if (child.getVisibility() == GONE || isChildInvisible(child)) { + continue; + } int childHeightSpec = newHeightSpec; ViewGroup.LayoutParams layoutParams = child.getLayoutParams(); if (layoutParams.height != ViewGroup.LayoutParams.MATCH_PARENT) { @@ -81,7 +92,8 @@ public abstract class ExpandableView extends FrameLayout { mMatchParentViews.add(child); } } - int ownHeight = hasFixedHeight ? ownMaxHeight : maxChildHeight; + int ownHeight = hasFixedHeight ? ownMaxHeight : + isHeightLimited ? Math.min(ownMaxHeight, maxChildHeight) : maxChildHeight; newHeightSpec = MeasureSpec.makeMeasureSpec(ownHeight, MeasureSpec.EXACTLY); for (View child : mMatchParentViews) { child.measure(getChildMeasureSpec( @@ -90,6 +102,10 @@ public abstract class ExpandableView extends FrameLayout { } mMatchParentViews.clear(); int width = MeasureSpec.getSize(widthMeasureSpec); + if (canHaveBottomDecor()) { + // We always account for the expandAction as well. + ownHeight += mBottomDecorHeight; + } setMeasuredDimension(width, ownHeight); } @@ -99,7 +115,7 @@ public abstract class ExpandableView extends FrameLayout { if (!mActualHeightInitialized && mActualHeight == 0) { int initialHeight = getInitialHeight(); if (initialHeight != 0) { - setActualHeight(initialHeight); + setContentHeight(initialHeight); } } } @@ -140,13 +156,14 @@ public abstract class ExpandableView extends FrameLayout { public void setActualHeight(int actualHeight, boolean notifyListeners) { mActualHeightInitialized = true; mActualHeight = actualHeight; + updateClipping(); if (notifyListeners) { - notifyHeightChanged(); + notifyHeightChanged(false /* needsAnimation */); } } - public void setActualHeight(int actualHeight) { - setActualHeight(actualHeight, true); + public void setContentHeight(int contentHeight) { + setActualHeight(contentHeight + getBottomDecorHeight(), true); } /** @@ -159,14 +176,39 @@ public abstract class ExpandableView extends FrameLayout { } /** + * This view may have a bottom decor which will be placed below the content. If it has one, this + * view will be layouted higher than just the content by {@link #mBottomDecorHeight}. + * @return the height of the decor if it currently has one + */ + public int getBottomDecorHeight() { + return hasBottomDecor() ? mBottomDecorHeight : 0; + } + + /** + * @return whether this view may have a bottom decor at all. This will force the view to layout + * itself higher than just it's content + */ + protected boolean canHaveBottomDecor() { + return false; + } + + /** + * @return whether this view has a decor view below it's content. This will make the intrinsic + * height from {@link #getIntrinsicHeight()} higher as well + */ + protected boolean hasBottomDecor() { + return false; + } + + /** * @return The maximum height of this notification. */ - public int getMaxHeight() { + public int getMaxContentHeight() { return getHeight(); } /** - * @return The minimum height of this notification. + * @return The minimum content height of this notification. */ public int getMinHeight() { return getHeight(); @@ -245,9 +287,9 @@ public abstract class ExpandableView extends FrameLayout { return false; } - public void notifyHeightChanged() { + public void notifyHeightChanged(boolean needsAnimation) { if (mOnHeightChangedListener != null) { - mOnHeightChangedListener.onHeightChanged(this); + mOnHeightChangedListener.onHeightChanged(this, needsAnimation); } } @@ -298,6 +340,41 @@ public abstract class ExpandableView extends FrameLayout { outRect.top += getTranslationY() + getClipTopAmount(); } + public int getContentHeight() { + return mActualHeight - getBottomDecorHeight(); + } + + /** + * @return whether the given child can be ignored for layouting and measuring purposes + */ + protected boolean isChildInvisible(View child) { + return false; + } + + public boolean areChildrenExpanded() { + return false; + } + + private void updateClipping() { + mClipRect.set(0, mClipTopOptimization, getWidth(), getActualHeight()); + setClipBounds(mClipRect); + } + + public int getClipTopOptimization() { + return mClipTopOptimization; + } + + /** + * Set that the view will be clipped by a given amount from the top. Contrary to + * {@link #setClipTopAmount} this amount doesn't effect shadows and the background. + * + * @param clipTopOptimization the amount to clip from the top + */ + public void setClipTopOptimization(int clipTopOptimization) { + mClipTopOptimization = clipTopOptimization; + updateClipping(); + } + /** * A listener notifying when {@link #getActualHeight} changes. */ @@ -306,8 +383,9 @@ public abstract class ExpandableView extends FrameLayout { /** * @param view the view for which the height changed, or {@code null} if just the top * padding or the padding between the elements changed + * @param needsAnimation whether the view height needs to be animated */ - void onHeightChanged(ExpandableView view); + void onHeightChanged(ExpandableView view, boolean needsAnimation); /** * Called when the view is reset and therefore the height will change abruptly diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java index 9f0f84e..0fa088b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java @@ -17,7 +17,6 @@ package com.android.systemui.statusbar; import android.animation.Animator; -import android.animation.ValueAnimator; import android.content.Context; import android.view.ViewPropertyAnimator; import android.view.animation.AnimationUtils; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java index 0fc46e9..01aa8d1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java @@ -44,7 +44,7 @@ public class NotificationBackgroundView extends View { } private void draw(Canvas canvas, Drawable drawable) { - if (drawable != null) { + if (drawable != null && mActualHeight > mClipTopAmount) { drawable.setBounds(0, mClipTopAmount, getWidth(), mActualHeight); drawable.draw(canvas); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java index 914b3d8..745e75d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java @@ -17,21 +17,16 @@ package com.android.systemui.statusbar; import android.content.Context; -import android.graphics.ColorFilter; -import android.graphics.ColorMatrix; -import android.graphics.ColorMatrixColorFilter; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; -import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.View; import android.view.ViewTreeObserver; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; import android.widget.FrameLayout; -import android.widget.ImageView; import com.android.systemui.R; /** @@ -52,7 +47,7 @@ public class NotificationContentView extends FrameLayout { private int mSmallHeight; private int mClipTopAmount; - private int mActualHeight; + private int mContentHeight; private final Interpolator mLinearInterpolator = new LinearInterpolator(); @@ -102,7 +97,7 @@ public class NotificationContentView extends FrameLayout { mSmallHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height); mContractedVisible = true; if (resetActualHeight) { - mActualHeight = mSmallHeight; + mContentHeight = mSmallHeight; } } @@ -159,12 +154,17 @@ public class NotificationContentView extends FrameLayout { } } - public void setActualHeight(int actualHeight) { - mActualHeight = actualHeight; + public void setContentHeight(int contentHeight) { + contentHeight = Math.max(Math.min(contentHeight, getHeight()), getMinHeight()); + mContentHeight = contentHeight; selectLayout(mAnimate /* animate */, false /* force */); updateClipping(); } + public int getContentHeight() { + return mContentHeight; + } + public int getMaxHeight() { // The maximum height is just the laid out height. @@ -181,7 +181,7 @@ public class NotificationContentView extends FrameLayout { } private void updateClipping() { - mClipBounds.set(0, mClipTopAmount, getWidth(), mActualHeight); + mClipBounds.set(0, mClipTopAmount, getWidth(), mContentHeight); setClipBounds(mClipBounds); } @@ -240,7 +240,7 @@ public class NotificationContentView extends FrameLayout { } private boolean showContractedChild() { - return mActualHeight <= mSmallHeight || mExpandedChild == null; + return mContentHeight <= mSmallHeight || mExpandedChild == null; } public void notifyContentUpdated() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java index 34c458a..912f414 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java @@ -22,9 +22,10 @@ import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.StatusBarNotification; import android.util.ArrayMap; -import android.util.ArraySet; import android.view.View; +import com.android.systemui.statusbar.phone.NotificationGroupManager; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; @@ -91,10 +92,12 @@ public class NotificationData { private final ArrayMap<String, Entry> mEntries = new ArrayMap<>(); private final ArrayList<Entry> mSortedAndFiltered = new ArrayList<>(); - private ArraySet<String> mGroupsWithSummaries = new ArraySet<>(); + + private NotificationGroupManager mGroupManager; private RankingMap mRankingMap; private final Ranking mTmpRanking = new Ranking(); + private final Comparator<Entry> mRankingComparator = new Comparator<Entry>() { private final Ranking mRankingA = new Ranking(); private final Ranking mRankingB = new Ranking(); @@ -141,6 +144,7 @@ public class NotificationData { public NotificationData(Environment environment) { mEnvironment = environment; + mGroupManager = environment.getGroupManager(); } /** @@ -163,12 +167,14 @@ public class NotificationData { public void add(Entry entry, RankingMap ranking) { mEntries.put(entry.notification.getKey(), entry); updateRankingAndSort(ranking); + mGroupManager.onEntryAdded(entry); } public Entry remove(String key, RankingMap ranking) { Entry removed = mEntries.remove(key); if (removed == null) return null; updateRankingAndSort(ranking); + mGroupManager.onEntryRemoved(removed); return removed; } @@ -203,7 +209,6 @@ public class NotificationData { // anything changed, and this class should call back the UI so it updates itself. public void filterAndSort() { mSortedAndFiltered.clear(); - mGroupsWithSummaries.clear(); final int N = mEntries.size(); for (int i = 0; i < N; i++) { @@ -214,32 +219,12 @@ public class NotificationData { continue; } - if (sbn.getNotification().isGroupSummary()) { - mGroupsWithSummaries.add(sbn.getGroupKey()); - } mSortedAndFiltered.add(entry); } - // Second pass: Filter out group children with summary. - if (!mGroupsWithSummaries.isEmpty()) { - final int M = mSortedAndFiltered.size(); - for (int i = M - 1; i >= 0; i--) { - Entry ent = mSortedAndFiltered.get(i); - StatusBarNotification sbn = ent.notification; - if (sbn.getNotification().isGroupChild() && - mGroupsWithSummaries.contains(sbn.getGroupKey())) { - mSortedAndFiltered.remove(i); - } - } - } - Collections.sort(mSortedAndFiltered, mRankingComparator); } - public boolean isGroupWithSummary(String groupKey) { - return mGroupsWithSummaries.contains(groupKey); - } - boolean shouldFilterOut(StatusBarNotification sbn) { if (!(mEnvironment.isDeviceProvisioned() || showNotificationEvenIfUnprovisioned(sbn))) { @@ -254,6 +239,11 @@ public class NotificationData { mEnvironment.shouldHideSensitiveContents(sbn.getUserId())) { return true; } + + if (!BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS + && mGroupManager.isChildInGroupWithSummary(sbn)) { + return true; + } return false; } @@ -328,5 +318,6 @@ public class NotificationData { public boolean isDeviceProvisioned(); public boolean isNotificationForCurrentProfiles(StatusBarNotification sbn); public String getCurrentMediaNotificationKey(); + public NotificationGroupManager getGroupManager(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java index bfa3aa5..5fa7070 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java @@ -62,4 +62,13 @@ public class NotificationOverflowContainer extends ActivatableNotificationView { public NotificationOverflowIconsView getIconsView() { return mIconsView; } + + protected int getContentHeightFromActualHeight(int actualHeight) { + int realActualHeight = actualHeight; + if (hasBottomDecor()) { + realActualHeight -= getBottomDecorHeight(); + } + realActualHeight = Math.max(getMinHeight(), realActualHeight); + return realActualHeight; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowIconsView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowIconsView.java index c4c9dac..88bb714 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowIconsView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowIconsView.java @@ -18,7 +18,6 @@ package com.android.systemui.statusbar; import android.app.Notification; import android.content.Context; -import android.content.res.Configuration; import android.graphics.PorterDuff; import android.util.AttributeSet; import android.widget.ImageView; @@ -46,7 +45,7 @@ public class NotificationOverflowIconsView extends IconMerger { protected void onFinishInflate() { super.onFinishInflate(); mNotificationColorUtil = NotificationColorUtil.getInstance(getContext()); - mTintColor = getResources().getColor(R.color.keyguard_overflow_content_color); + mTintColor = getContext().getColor(R.color.keyguard_overflow_content_color); mIconSize = getResources().getDimensionPixelSize( com.android.internal.R.dimen.status_bar_icon_size); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java index 57d162b..958b8b5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java @@ -60,7 +60,7 @@ public class NotificationTemplateViewWrapper extends NotificationViewWrapper { super(view); mIconDarkAlpha = ctx.getResources().getInteger(R.integer.doze_small_icon_alpha); mIconBackgroundDarkColor = - ctx.getResources().getColor(R.color.doze_small_icon_background_color); + ctx.getColor(R.color.doze_small_icon_background_color); mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(ctx, android.R.interpolator.linear_out_slow_in); resolveViews(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java index 78b9739..44e8b85 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java @@ -19,8 +19,6 @@ package com.android.systemui.statusbar; import android.content.Context; import android.view.View; -import com.android.internal.R; - /** * Wraps the actual notification content view; used to implement behaviors which are different for * the individual templates and custom views. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ServiceMonitor.java b/packages/SystemUI/src/com/android/systemui/statusbar/ServiceMonitor.java index aea9ec6..602989a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ServiceMonitor.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ServiceMonitor.java @@ -177,6 +177,7 @@ public class ServiceMonitor { IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_PACKAGE_ADDED); filter.addAction(Intent.ACTION_PACKAGE_CHANGED); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); filter.addDataScheme("package"); mContext.registerReceiver(mBroadcastReceiver, filter); @@ -196,13 +197,14 @@ public class ServiceMonitor { + " extras=" + bundleToString(intent.getExtras())); if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())) { mHandler.sendEmptyMessage(MSG_START_SERVICE); - } else if (Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction())) { - PackageManager pm = mContext.getPackageManager(); - boolean serviceEnabled = - pm.getApplicationEnabledSetting(mServiceName.getPackageName()) - != PackageManager.COMPONENT_ENABLED_STATE_DISABLED + } else if (Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction()) + || Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) { + final PackageManager pm = mContext.getPackageManager(); + final boolean serviceEnabled = isPackageAvailable() + && pm.getApplicationEnabledSetting(mServiceName.getPackageName()) + != PackageManager.COMPONENT_ENABLED_STATE_DISABLED && pm.getComponentEnabledSetting(mServiceName) - != PackageManager.COMPONENT_ENABLED_STATE_DISABLED; + != PackageManager.COMPONENT_ENABLED_STATE_DISABLED; if (mBound && !serviceEnabled) { stopService(); scheduleCheckBound(); @@ -279,4 +281,25 @@ public class ServiceMonitor { } return sb.append('}').toString(); } + + public ComponentName getComponent() { + return getComponentNameFromSetting(); + } + + public void setComponent(ComponentName component) { + final String setting = component == null ? null : component.flattenToShortString(); + Settings.Secure.putStringForUser(mContext.getContentResolver(), + mSettingKey, setting, UserHandle.USER_CURRENT); + } + + public boolean isPackageAvailable() { + final ComponentName component = getComponent(); + if (component == null) return false; + try { + return mContext.getPackageManager().isPackageAvailable(component.getPackageName()); + } catch (RuntimeException e) { + Log.w(mTag, "Error checking package availability", e); + return false; + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java index 8e35ee9..a82afcf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java @@ -17,6 +17,9 @@ package com.android.systemui.statusbar; import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.Color; +import android.graphics.PorterDuff; import android.telephony.SubscriptionInfo; import android.util.AttributeSet; import android.util.Log; @@ -55,9 +58,11 @@ public class SignalClusterView private int mAirplaneContentDescription; private String mWifiDescription; private ArrayList<PhoneState> mPhoneStates = new ArrayList<PhoneState>(); + private int mIconTint = Color.WHITE; + private float mDarkIntensity; ViewGroup mWifiGroup; - ImageView mVpn, mWifi, mAirplane, mNoSims; + ImageView mVpn, mWifi, mAirplane, mNoSims, mWifiDark, mNoSimsDark; View mWifiAirplaneSpacer; View mWifiSignalSpacer; LinearLayout mMobileSignalGroup; @@ -111,8 +116,10 @@ public class SignalClusterView mVpn = (ImageView) findViewById(R.id.vpn); mWifiGroup = (ViewGroup) findViewById(R.id.wifi_combo); mWifi = (ImageView) findViewById(R.id.wifi_signal); + mWifiDark = (ImageView) findViewById(R.id.wifi_signal_dark); mAirplane = (ImageView) findViewById(R.id.airplane); mNoSims = (ImageView) findViewById(R.id.no_sims); + mNoSimsDark = (ImageView) findViewById(R.id.no_sims_dark); mWifiAirplaneSpacer = findViewById(R.id.wifi_airplane_spacer); mWifiSignalSpacer = findViewById(R.id.wifi_signal_spacer); mMobileSignalGroup = (LinearLayout) findViewById(R.id.mobile_signal_group); @@ -121,6 +128,7 @@ public class SignalClusterView } apply(); + applyIconTint(); } @Override @@ -187,6 +195,9 @@ public class SignalClusterView for (int i = 0; i < n; i++) { inflatePhoneState(subs.get(i).getSubscriptionId()); } + if (isAttachedToWindow()) { + applyIconTint(); + } } private PhoneState getOrInflateState(int subId) { @@ -217,7 +228,7 @@ public class SignalClusterView } @Override - public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) { // Standard group layout onPopulateAccessibilityEvent() implementations // ignore content description, so populate manually if (mWifiVisible && mWifiGroup != null && mWifiGroup.getContentDescription() != null) @@ -225,7 +236,7 @@ public class SignalClusterView for (PhoneState state : mPhoneStates) { state.populateAccessibilityEvent(event); } - return super.dispatchPopulateAccessibilityEvent(event); + return super.dispatchPopulateAccessibilityEventInternal(event); } @Override @@ -265,6 +276,7 @@ public class SignalClusterView if (DEBUG) Log.d(TAG, String.format("vpn: %s", mVpnVisible ? "VISIBLE" : "GONE")); if (mWifiVisible) { mWifi.setImageResource(mWifiStrengthId); + mWifiDark.setImageResource(mWifiStrengthId); mWifiGroup.setContentDescription(mWifiDescription); mWifiGroup.setVisibility(View.VISIBLE); } else { @@ -309,12 +321,42 @@ public class SignalClusterView } mNoSims.setVisibility(mNoSimsVisible ? View.VISIBLE : View.GONE); + mNoSimsDark.setVisibility(mNoSimsVisible ? View.VISIBLE : View.GONE); boolean anythingVisible = mNoSimsVisible || mWifiVisible || mIsAirplaneMode || anyMobileVisible || mVpnVisible; setPaddingRelative(0, 0, anythingVisible ? mEndPadding : mEndPaddingNothingVisible, 0); } + public void setIconTint(int tint, float darkIntensity) { + boolean changed = tint != mIconTint || darkIntensity != mDarkIntensity; + mIconTint = tint; + mDarkIntensity = darkIntensity; + if (changed && isAttachedToWindow()) { + applyIconTint(); + } + } + + private void applyIconTint() { + setTint(mVpn, mIconTint); + setTint(mAirplane, mIconTint); + applyDarkIntensity(mDarkIntensity, mNoSims, mNoSimsDark); + applyDarkIntensity(mDarkIntensity, mWifi, mWifiDark); + for (int i = 0; i < mPhoneStates.size(); i++) { + mPhoneStates.get(i).setIconTint(mIconTint, mDarkIntensity); + } + } + + private void applyDarkIntensity(float darkIntensity, View lightIcon, View darkIcon) { + lightIcon.setAlpha(1 - darkIntensity); + darkIcon.setAlpha(darkIntensity); + } + + private void setTint(ImageView v, int tint) { + v.setImageTintMode(PorterDuff.Mode.SRC_ATOP); + v.setImageTintList(ColorStateList.valueOf(tint)); + } + private class PhoneState { private final int mSubId; private boolean mMobileVisible = false; @@ -323,7 +365,7 @@ public class SignalClusterView private String mMobileDescription, mMobileTypeDescription; private ViewGroup mMobileGroup; - private ImageView mMobile, mMobileType; + private ImageView mMobile, mMobileDark, mMobileType; public PhoneState(int subId, Context context) { ViewGroup root = (ViewGroup) LayoutInflater.from(context) @@ -335,12 +377,14 @@ public class SignalClusterView public void setViews(ViewGroup root) { mMobileGroup = root; mMobile = (ImageView) root.findViewById(R.id.mobile_signal); + mMobileDark = (ImageView) root.findViewById(R.id.mobile_signal_dark); mMobileType = (ImageView) root.findViewById(R.id.mobile_type); } public boolean apply(boolean isSecondaryIcon) { if (mMobileVisible && !mIsAirplaneMode) { mMobile.setImageResource(mMobileStrengthId); + mMobileDark.setImageResource(mMobileStrengthId); mMobileType.setImageResource(mMobileTypeId); mMobileGroup.setContentDescription(mMobileTypeDescription + " " + mMobileDescription); @@ -354,6 +398,8 @@ public class SignalClusterView 0, 0, 0); mMobile.setPaddingRelative(mIsMobileTypeIconWide ? mWideTypeIconStartPadding : 0, 0, 0, 0); + mMobileDark.setPaddingRelative(mIsMobileTypeIconWide ? mWideTypeIconStartPadding : 0, + 0, 0, 0); if (DEBUG) Log.d(TAG, String.format("mobile: %s sig=%d typ=%d", (mMobileVisible ? "VISIBLE" : "GONE"), mMobileStrengthId, mMobileTypeId)); @@ -369,6 +415,11 @@ public class SignalClusterView event.getText().add(mMobileGroup.getContentDescription()); } } + + public void setIconTint(int tint, float darkIntensity) { + applyDarkIntensity(darkIntensity, mMobile, mMobileDark); + setTint(mMobileType, tint); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java index 20dd3e7..e6847d8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java @@ -55,7 +55,7 @@ public class StatusBarIconView extends AnimatedImageView { mSlot = slot; mNumberPain = new Paint(); mNumberPain.setTextAlign(Paint.Align.CENTER); - mNumberPain.setColor(res.getColor(R.drawable.notification_number_text_color)); + mNumberPain.setColor(context.getColor(R.drawable.notification_number_text_color)); mNumberPain.setAntiAlias(true); setNotification(notification); @@ -147,6 +147,9 @@ public class StatusBarIconView extends AnimatedImageView { } private boolean updateDrawable(boolean withClear) { + if (mIcon == null) { + return false; + } Drawable drawable = getIcon(mIcon); if (drawable == null) { Log.w(TAG, "No icon for slot " + mSlot); @@ -226,6 +229,12 @@ public class StatusBarIconView extends AnimatedImageView { } @Override + public void onRtlPropertiesChanged(int layoutDirection) { + super.onRtlPropertiesChanged(layoutDirection); + updateDrawable(); + } + + @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java index e89e15d..1601b83 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java @@ -145,12 +145,12 @@ public class BarTransitions { mTransparent = 0x2f0000ff; mWarning = 0xffff0000; } else { - mOpaque = res.getColor(R.color.system_bar_background_opaque); - mSemiTransparent = res.getColor(R.color.system_bar_background_semi_transparent); - mTransparent = res.getColor(R.color.system_bar_background_transparent); - mWarning = res.getColor(com.android.internal.R.color.battery_saver_mode_color); + mOpaque = context.getColor(R.color.system_bar_background_opaque); + mSemiTransparent = context.getColor(R.color.system_bar_background_semi_transparent); + mTransparent = context.getColor(R.color.system_bar_background_transparent); + mWarning = context.getColor(com.android.internal.R.color.battery_saver_mode_color); } - mGradient = res.getDrawable(gradientResourceId); + mGradient = context.getDrawable(gradientResourceId); mInterpolator = new LinearInterpolator(); } @@ -160,7 +160,7 @@ public class BarTransitions { } @Override - public void setColorFilter(ColorFilter cf) { + public void setColorFilter(ColorFilter colorFilter) { // noop } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java index 6cb5bcc..44168bc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java @@ -90,24 +90,12 @@ public class DemoStatusIcons extends LinearLayout implements DemoMode { : 0; updateSlot("alarm_clock", null, iconId); } - String sync = args.getString("sync"); - if (sync != null) { - int iconId = sync.equals("show") ? R.drawable.stat_sys_sync - : 0; - updateSlot("sync_active", null, iconId); - } String tty = args.getString("tty"); if (tty != null) { int iconId = tty.equals("show") ? R.drawable.stat_sys_tty_mode : 0; updateSlot("tty", null, iconId); } - String eri = args.getString("eri"); - if (eri != null) { - int iconId = eri.equals("show") ? R.drawable.stat_sys_roaming_cdma_0 - : 0; - updateSlot("cdma_eri", null, iconId); - } String mute = args.getString("mute"); if (mute != null) { int iconId = mute.equals("show") ? android.R.drawable.stat_notify_call_mute diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index 0c21b20..a247c8e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -336,7 +336,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } public void launchCamera() { - mFlashlightController.killFlashlight(); Intent intent = getCameraIntent(); boolean wouldLaunchResolverActivity = PreviewInflater.wouldLaunchResolverActivity( mContext, intent, mLockPatternUtils.getCurrentUser()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java index d0fe32e..262d955 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java @@ -25,10 +25,9 @@ import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import com.android.internal.widget.LockPatternUtils; -import com.android.keyguard.KeyguardViewBase; +import com.android.keyguard.KeyguardHostView; import com.android.keyguard.R; import com.android.keyguard.ViewMediatorCallback; -import com.android.systemui.keyguard.KeyguardViewMediator; import static com.android.keyguard.KeyguardHostView.OnDismissAction; import static com.android.keyguard.KeyguardSecurityModel.SecurityMode; @@ -43,7 +42,7 @@ public class KeyguardBouncer { private LockPatternUtils mLockPatternUtils; private ViewGroup mContainer; private StatusBarWindowManager mWindowManager; - private KeyguardViewBase mKeyguardView; + private KeyguardHostView mKeyguardView; private ViewGroup mRoot; private boolean mShowingSoon; private Choreographer mChoreographer = Choreographer.getInstance(); @@ -140,16 +139,6 @@ public class KeyguardBouncer { } } - public long getUserActivityTimeout() { - if (mKeyguardView != null) { - long timeout = mKeyguardView.getUserActivityTimeout(); - if (timeout >= 0) { - return timeout; - } - } - return KeyguardViewMediator.AWAKE_INTERVAL_DEFAULT_MS; - } - public boolean isShowing() { return mShowingSoon || (mRoot != null && mRoot.getVisibility() == View.VISIBLE); } @@ -171,7 +160,7 @@ public class KeyguardBouncer { private void inflateView() { removeView(); mRoot = (ViewGroup) LayoutInflater.from(mContext).inflate(R.layout.keyguard_bouncer, null); - mKeyguardView = (KeyguardViewBase) mRoot.findViewById(R.id.keyguard_host_view); + mKeyguardView = (KeyguardHostView) mRoot.findViewById(R.id.keyguard_host_view); mKeyguardView.setLockPatternUtils(mLockPatternUtils); mKeyguardView.setViewMediatorCallback(mCallback); mContainer.addView(mRoot, mContainer.getChildCount()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java index 7579039..076e5f1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java @@ -20,7 +20,6 @@ import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; -import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.WindowInsets; @@ -47,7 +46,7 @@ public class KeyguardPreviewContainer extends FrameLayout { } @Override - public void setColorFilter(ColorFilter cf) { + public void setColorFilter(ColorFilter colorFilter) { // noop } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java index 40c9134..13b3898 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.phone; -import android.animation.LayoutTransition; import android.content.Context; import android.content.res.Configuration; import android.graphics.drawable.Drawable; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java index 7ec84da..a712d29 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java @@ -26,12 +26,9 @@ import android.view.animation.AccelerateInterpolator; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.R; -import com.android.systemui.statusbar.policy.KeyButtonView; public final class NavigationBarTransitions extends BarTransitions { - private static final int CONTENT_FADE_DURATION = 200; - private final NavigationBarView mView; private final IStatusBarService mBarService; @@ -78,48 +75,11 @@ public final class NavigationBarTransitions extends BarTransitions { } private void applyMode(int mode, boolean animate, boolean force) { - // apply to key buttons - final float alpha = alphaForMode(mode); - setKeyButtonViewQuiescentAlpha(mView.getHomeButton(), alpha, animate); - setKeyButtonViewQuiescentAlpha(mView.getRecentsButton(), alpha, animate); - setKeyButtonViewQuiescentAlpha(mView.getMenuButton(), alpha, animate); - setKeyButtonViewQuiescentAlpha(mView.getImeSwitchButton(), alpha, animate); - - applyBackButtonQuiescentAlpha(mode, animate); // apply to lights out applyLightsOut(isLightsOut(mode), animate, force); } - private float alphaForMode(int mode) { - final boolean isOpaque = mode == MODE_OPAQUE || mode == MODE_LIGHTS_OUT; - return isOpaque ? KeyButtonView.DEFAULT_QUIESCENT_ALPHA : 1f; - } - - public void applyBackButtonQuiescentAlpha(int mode, boolean animate) { - float backAlpha = 0; - backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getHomeButton()); - backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getRecentsButton()); - backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getMenuButton()); - backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getImeSwitchButton()); - if (backAlpha > 0) { - setKeyButtonViewQuiescentAlpha(mView.getBackButton(), backAlpha, animate); - } - } - - private static float maxVisibleQuiescentAlpha(float max, View v) { - if ((v instanceof KeyButtonView) && v.isShown()) { - return Math.max(max, ((KeyButtonView)v).getQuiescentAlpha()); - } - return max; - } - - private void setKeyButtonViewQuiescentAlpha(View button, float alpha, boolean animate) { - if (button instanceof KeyButtonView) { - ((KeyButtonView) button).setQuiescentAlpha(alpha, animate); - } - } - private void applyLightsOut(boolean lightsOut, boolean animate, boolean force) { if (!force && lightsOut == mLightsOut) return; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 1e4dfb4..c62ad66 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -197,7 +197,7 @@ public class NavigationBarView extends LinearLayout { mDelegateHelper.setDelegateView(view); } - public void setBar(BaseStatusBar phoneStatusBar) { + public void setBar(PhoneStatusBar phoneStatusBar) { mTaskSwitchHelper.setBar(phoneStatusBar); mDelegateHelper.setBar(phoneStatusBar); } @@ -263,8 +263,8 @@ public class NavigationBarView extends LinearLayout { return mCurrentView.findViewById(R.id.back); } - public View getHomeButton() { - return mCurrentView.findViewById(R.id.home); + public KeyButtonView getHomeButton() { + return (KeyButtonView) mCurrentView.findViewById(R.id.home); } public View getImeSwitchButton() { @@ -369,8 +369,6 @@ public class NavigationBarView extends LinearLayout { getBackButton() .setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE); getHomeButton() .setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE); getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE); - - mBarTransitions.applyBackButtonQuiescentAlpha(mBarTransitions.getMode(), true /*animate*/); } private boolean inLockTask() { @@ -662,10 +660,6 @@ public class NavigationBarView extends LinearLayout { + " " + visibilityToString(button.getVisibility()) + " alpha=" + button.getAlpha() ); - if (button instanceof KeyButtonView) { - pw.print(" drawingAlpha=" + ((KeyButtonView)button).getDrawingAlpha()); - pw.print(" quiescentAlpha=" + ((KeyButtonView)button).getQuiescentAlpha()); - } } pw.println(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java new file mode 100644 index 0000000..7072dcb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.phone; + +import android.app.Notification; +import android.service.notification.StatusBarNotification; + +import com.android.systemui.statusbar.ExpandableNotificationRow; +import com.android.systemui.statusbar.NotificationData; +import com.android.systemui.statusbar.StatusBarState; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * A class to handle notifications and their corresponding groups. + */ +public class NotificationGroupManager { + + private final HashMap<String, NotificationGroup> mGroupMap = new HashMap<>(); + private OnGroupChangeListener mListener; + private int mBarState = -1; + + public void setOnGroupChangeListener(OnGroupChangeListener listener) { + mListener = listener; + } + + public boolean isGroupExpanded(StatusBarNotification sbn) { + NotificationGroup group = mGroupMap.get(sbn.getGroupKey()); + if (group == null) { + return false; + } + return group.expanded; + } + + public void setGroupExpanded(StatusBarNotification sbn, boolean expanded) { + NotificationGroup group = mGroupMap.get(sbn.getGroupKey()); + if (group == null) { + return; + } + setGroupExpanded(group, expanded); + } + + private void setGroupExpanded(NotificationGroup group, boolean expanded) { + group.expanded = expanded; + if (group.summary != null) { + mListener.onGroupExpansionChanged(group.summary.row, expanded); + } + } + + public void onEntryRemoved(NotificationData.Entry removed) { + onEntryRemovedInternal(removed, removed.notification); + } + + /** + * An entry was removed. + * + * @param removed the removed entry + * @param sbn the notification the entry has, which doesn't need to be the same as it's internal + * notification + */ + private void onEntryRemovedInternal(NotificationData.Entry removed, + final StatusBarNotification sbn) { + Notification notif = sbn.getNotification(); + String groupKey = sbn.getGroupKey(); + final NotificationGroup group = mGroupMap.get(groupKey); + if (notif.isGroupSummary()) { + group.summary = null; + } else { + group.children.remove(removed); + } + if (group.children.isEmpty()) { + if (group.summary == null) { + mGroupMap.remove(groupKey); + } else { + if (group.expanded) { + // only the summary is left. Change it to unexpanded in a few ms. We do this to + // avoid raceconditions + removed.row.post(new Runnable() { + @Override + public void run() { + if (group.children.isEmpty()) { + setGroupExpanded(sbn, false); + } + } + }); + } else { + group.summary.row.updateExpandButton(); + } + } + } + } + + public void onEntryAdded(NotificationData.Entry added) { + StatusBarNotification sbn = added.notification; + Notification notif = sbn.getNotification(); + String groupKey = sbn.getGroupKey(); + NotificationGroup group = mGroupMap.get(groupKey); + if (group == null) { + group = new NotificationGroup(); + mGroupMap.put(groupKey, group); + } + if (notif.isGroupSummary()) { + group.summary = added; + group.expanded = added.row.areChildrenExpanded(); + if (!group.children.isEmpty()) { + mListener.onGroupCreatedFromChildren(group); + } + } else { + group.children.add(added); + if (group.summary != null && group.children.size() == 1 && !group.expanded) { + group.summary.row.updateExpandButton(); + } + } + } + + public void onEntryUpdated(NotificationData.Entry entry, + StatusBarNotification oldNotification) { + if (mGroupMap.get(oldNotification.getGroupKey()) != null) { + onEntryRemovedInternal(entry, oldNotification); + } + onEntryAdded(entry); + } + + public boolean isVisible(StatusBarNotification sbn) { + if (!sbn.getNotification().isGroupChild()) { + return true; + } + NotificationGroup group = mGroupMap.get(sbn.getGroupKey()); + if (group != null && group.expanded) { + return true; + } + return false; + } + + public boolean hasGroupChildren(StatusBarNotification sbn) { + if (areGroupsProhibited()) { + return false; + } + if (!sbn.getNotification().isGroupSummary()) { + return false; + } + NotificationGroup group = mGroupMap.get(sbn.getGroupKey()); + if (group == null) { + return false; + } + return !group.children.isEmpty(); + } + + public void setStatusBarState(int newState) { + if (mBarState == newState) { + return; + } + boolean prohibitedBefore = areGroupsProhibited(); + mBarState = newState; + boolean nowProhibited = areGroupsProhibited(); + if (nowProhibited != prohibitedBefore) { + if (nowProhibited) { + for (NotificationGroup group : mGroupMap.values()) { + if (group.expanded) { + setGroupExpanded(group, false); + } + } + } + mListener.onGroupsProhibitedChanged(); + } + } + + private boolean areGroupsProhibited() { + return mBarState == StatusBarState.KEYGUARD; + } + + /** + * @return whether a given notification is a child in a group which has a summary + */ + public boolean isChildInGroupWithSummary(StatusBarNotification sbn) { + if (!sbn.getNotification().isGroupChild()) { + return false; + } + NotificationGroup group = mGroupMap.get(sbn.getGroupKey()); + if (group == null || group.summary == null) { + return false; + } + return true; + } + + public ExpandableNotificationRow getGroupSummary(StatusBarNotification sbn) { + NotificationGroup group = mGroupMap.get(sbn.getGroupKey()); + return group == null ? null + : group.summary == null ? null + : group.summary.row; + } + + public static class NotificationGroup { + public final HashSet<NotificationData.Entry> children = new HashSet<>(); + public NotificationData.Entry summary; + public boolean expanded; + } + + public interface OnGroupChangeListener { + /** + * The expansion of a group has changed. + * + * @param changedRow the row for which the expansion has changed, which is also the summary + * @param expanded a boolean indicating the new expanded state + */ + void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded); + + /** + * Children group policy has changed and children may no be prohibited or allowed. + */ + void onGroupsProhibitedChanged(); + + /** + * A group of children just received a summary notification and should therefore become + * children of it. + * + * @param group the group created + */ + void onGroupCreatedFromChildren(NotificationGroup group); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index 0ae34bb..195da46 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -112,6 +112,7 @@ public class NotificationPanelView extends PanelView implements private boolean mQsFullyExpanded; private boolean mKeyguardShowing; private boolean mDozing; + private boolean mDozingOnDown; private int mStatusBarState; private float mInitialHeightOnTouch; private float mInitialTouchX; @@ -163,7 +164,7 @@ public class NotificationPanelView extends PanelView implements private Runnable mLaunchAnimationEndRunnable; private boolean mOnlyAffordanceInThisMotion; private boolean mKeyguardStatusViewAnimating; - private boolean mHeaderAnimatingIn; + private boolean mHeaderAnimating; private ObjectAnimator mQsContainerAnimator; private ValueAnimator mQsSizeChangeAnimator; @@ -222,9 +223,8 @@ public class NotificationPanelView extends PanelView implements // recompute internal state when qspanel height changes mQsContainer.addOnLayoutChangeListener(new OnLayoutChangeListener() { @Override - public void onLayoutChange(View v, int left, int top, int right, - int bottom, int oldLeft, int oldTop, int oldRight, - int oldBottom) { + public void onLayoutChange(View v, int left, int top, int right, int bottom, + int oldLeft, int oldTop, int oldRight, int oldBottom) { final int height = bottom - top; final int oldHeight = oldBottom - oldTop; if (height != oldHeight) { @@ -493,14 +493,14 @@ public class NotificationPanelView extends PanelView implements } @Override - public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) { if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { event.getText().add(getKeyguardOrLockScreenString()); mLastAnnouncementWasQuickSettings = false; return true; } - return super.dispatchPopulateAccessibilityEvent(event); + return super.dispatchPopulateAccessibilityEventInternal(event); } @Override @@ -508,7 +508,7 @@ public class NotificationPanelView extends PanelView implements if (mBlockTouches) { return false; } - resetDownStates(event); + initDownStates(event); int pointerIndex = event.findPointerIndex(mTrackingPointer); if (pointerIndex < 0) { pointerIndex = 0; @@ -594,10 +594,11 @@ public class NotificationPanelView extends PanelView implements && x < stackScrollerX + mNotificationStackScroller.getWidth(); } - private void resetDownStates(MotionEvent event) { + private void initDownStates(MotionEvent event) { if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { mOnlyAffordanceInThisMotion = false; mQsTouchAboveFalsingThreshold = mQsFullyExpanded; + mDozingOnDown = isDozing(); } } @@ -642,7 +643,7 @@ public class NotificationPanelView extends PanelView implements if (mBlockTouches) { return false; } - resetDownStates(event); + initDownStates(event); if ((!mIsExpanding || mHintAnimationRunning) && !mQsExpanded && mStatusBar.getBarState() != StatusBarState.SHADE) { @@ -873,26 +874,28 @@ public class NotificationPanelView extends PanelView implements public void setBarState(int statusBarState, boolean keyguardFadingAway, boolean goingToFullShade) { - boolean keyguardShowing = statusBarState == StatusBarState.KEYGUARD - || statusBarState == StatusBarState.SHADE_LOCKED; - if (!mKeyguardShowing && keyguardShowing) { - setQsTranslation(mQsExpansionHeight); - mHeader.setTranslationY(0f); - } + int oldState = mStatusBarState; + boolean keyguardShowing = statusBarState == StatusBarState.KEYGUARD; setKeyguardStatusViewVisibility(statusBarState, keyguardFadingAway, goingToFullShade); setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade); - if (goingToFullShade) { + + mStatusBarState = statusBarState; + mKeyguardShowing = keyguardShowing; + + if (goingToFullShade || (oldState == StatusBarState.KEYGUARD + && statusBarState == StatusBarState.SHADE_LOCKED)) { animateKeyguardStatusBarOut(); + animateHeaderSlidingIn(); + } else if (oldState == StatusBarState.SHADE_LOCKED + && statusBarState == StatusBarState.KEYGUARD) { + animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD); + animateHeaderSlidingOut(); } else { mKeyguardStatusBar.setAlpha(1f); mKeyguardStatusBar.setVisibility(keyguardShowing ? View.VISIBLE : View.INVISIBLE); } - mStatusBarState = statusBarState; - mKeyguardShowing = keyguardShowing; + updateQsState(); - if (goingToFullShade) { - animateHeaderSlidingIn(); - } } private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = new Runnable() { @@ -914,7 +917,7 @@ public class NotificationPanelView extends PanelView implements = new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - mHeaderAnimatingIn = false; + mHeaderAnimating = false; mQsContainerAnimator = null; mQsContainer.removeOnLayoutChangeListener(mQsContainerAnimatorUpdater); } @@ -942,10 +945,13 @@ public class NotificationPanelView extends PanelView implements @Override public boolean onPreDraw() { getViewTreeObserver().removeOnPreDrawListener(this); + long delay = mStatusBarState == StatusBarState.SHADE_LOCKED + ? 0 + : mStatusBar.calculateGoingToFullShadeDelay(); mHeader.setTranslationY(-mHeader.getCollapsedHeight() - mQsPeekHeight); mHeader.animate() .translationY(0f) - .setStartDelay(mStatusBar.calculateGoingToFullShadeDelay()) + .setStartDelay(delay) .setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE) .setInterpolator(mFastOutSlowInInterpolator) .start(); @@ -954,7 +960,7 @@ public class NotificationPanelView extends PanelView implements mQsContainer.getTranslationY(), mHeader.getCollapsedHeight() + mQsPeekHeight - mQsContainer.getHeight() - mQsContainer.getTop()); - mQsContainerAnimator.setStartDelay(mStatusBar.calculateGoingToFullShadeDelay()); + mQsContainerAnimator.setStartDelay(delay); mQsContainerAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE); mQsContainerAnimator.setInterpolator(mFastOutSlowInInterpolator); mQsContainerAnimator.addListener(mAnimateHeaderSlidingInListener); @@ -963,11 +969,33 @@ public class NotificationPanelView extends PanelView implements return true; } }; - + private void animateHeaderSlidingIn() { - mHeaderAnimatingIn = true; + mHeaderAnimating = true; getViewTreeObserver().addOnPreDrawListener(mStartHeaderSlidingIn); + } + private void animateHeaderSlidingOut() { + mHeaderAnimating = true; + mHeader.animate().y(-mHeader.getHeight()) + .setStartDelay(0) + .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD) + .setInterpolator(mFastOutSlowInInterpolator) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mHeader.animate().setListener(null); + mHeaderAnimating = false; + updateQsState(); + } + }) + .start(); + mQsContainer.animate() + .y(-mQsContainer.getHeight()) + .setStartDelay(0) + .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD) + .setInterpolator(mFastOutSlowInInterpolator) + .start(); } private final Runnable mAnimateKeyguardStatusBarInvisibleEndRunnable = new Runnable() { @@ -982,8 +1010,12 @@ public class NotificationPanelView extends PanelView implements private void animateKeyguardStatusBarOut() { mKeyguardStatusBar.animate() .alpha(0f) - .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay()) - .setDuration(mStatusBar.getKeyguardFadingAwayDuration()/2) + .setStartDelay(mStatusBar.isKeyguardFadingAway() + ? mStatusBar.getKeyguardFadingAwayDelay() + : 0) + .setDuration(mStatusBar.isKeyguardFadingAway() + ? mStatusBar.getKeyguardFadingAwayDuration() / 2 + : StackStateAnimator.ANIMATION_DURATION_STANDARD) .setInterpolator(PhoneStatusBar.ALPHA_OUT) .setUpdateListener(mStatusBarAnimateAlphaListener) .withEndAction(mAnimateKeyguardStatusBarInvisibleEndRunnable) @@ -998,13 +1030,13 @@ public class NotificationPanelView extends PanelView implements } }; - private void animateKeyguardStatusBarIn() { + private void animateKeyguardStatusBarIn(long duration) { mKeyguardStatusBar.setVisibility(View.VISIBLE); mKeyguardStatusBar.setAlpha(0f); mKeyguardStatusBar.animate() .alpha(1f) .setStartDelay(0) - .setDuration(DOZE_ANIMATION_DURATION) + .setDuration(duration) .setInterpolator(mDozeAnimationInterpolator) .setUpdateListener(mStatusBarAnimateAlphaListener) .start(); @@ -1084,9 +1116,12 @@ public class NotificationPanelView extends PanelView implements } private void updateQsState() { - boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling; - mHeader.setVisibility((mQsExpanded || !mKeyguardShowing) ? View.VISIBLE : View.INVISIBLE); - mHeader.setExpanded(mKeyguardShowing || (mQsExpanded && !mStackScrollerOverscrolling)); + boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling || mHeaderAnimating; + mHeader.setVisibility((mQsExpanded || !mKeyguardShowing || mHeaderAnimating) + ? View.VISIBLE + : View.INVISIBLE); + mHeader.setExpanded((mKeyguardShowing && !mHeaderAnimating) + || (mQsExpanded && !mStackScrollerOverscrolling)); mNotificationStackScroller.setScrollingEnabled( mStatusBarState != StatusBarState.KEYGUARD && (!mQsExpanded || mQsExpansionFromOverscroll)); @@ -1124,6 +1159,10 @@ public class NotificationPanelView extends PanelView implements if (mKeyguardShowing) { updateHeaderKeyguard(); } + if (mStatusBarState == StatusBarState.SHADE_LOCKED + || mStatusBarState == StatusBarState.KEYGUARD) { + updateKeyguardBottomAreaAlpha(); + } if (mStatusBarState == StatusBarState.SHADE && mQsExpanded && !mStackScrollerOverscrolling && mQsScrimEnabled) { mQsNavbarScrim.setAlpha(getQsExpansionFraction()); @@ -1164,10 +1203,10 @@ public class NotificationPanelView extends PanelView implements } private void setQsTranslation(float height) { - if (!mHeaderAnimatingIn) { + if (!mHeaderAnimating) { mQsContainer.setY(height - mQsContainer.getDesiredHeight() + getHeaderTranslation()); } - if (mKeyguardShowing) { + if (mKeyguardShowing && !mHeaderAnimating) { mHeader.setY(interpolate(getQsExpansionFraction(), -mHeader.getHeight(), 0)); } } @@ -1479,8 +1518,7 @@ public class NotificationPanelView extends PanelView implements * Hides the header when notifications are colliding with it. */ private void updateHeader() { - if (mStatusBar.getBarState() == StatusBarState.KEYGUARD - || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) { + if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) { updateHeaderKeyguard(); } else { updateHeaderShade(); @@ -1489,15 +1527,14 @@ public class NotificationPanelView extends PanelView implements } private void updateHeaderShade() { - if (!mHeaderAnimatingIn) { + if (!mHeaderAnimating) { mHeader.setTranslationY(getHeaderTranslation()); } setQsTranslation(mQsExpansionHeight); } private float getHeaderTranslation() { - if (mStatusBar.getBarState() == StatusBarState.KEYGUARD - || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) { + if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) { return 0; } if (mNotificationStackScroller.getNotGoneChildCount() == 0) { @@ -1510,30 +1547,42 @@ public class NotificationPanelView extends PanelView implements return Math.min(0, mNotificationStackScroller.getTranslationY()) / HEADER_RUBBERBAND_FACTOR; } - private void updateHeaderKeyguard() { - float alphaNotifications; + /** + * @return the alpha to be used to fade out the contents on Keyguard (status bar, bottom area) + * during swiping up + */ + private float getKeyguardContentsAlpha() { + float alpha; if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) { // When on Keyguard, we hide the header as soon as the top card of the notification // stack scroller is close enough (collision distance) to the bottom of the header. - alphaNotifications = getNotificationsTopY() + alpha = getNotificationsTopY() / (mKeyguardStatusBar.getHeight() + mNotificationsHeaderCollideDistance); } else { // In SHADE_LOCKED, the top card is already really close to the header. Hide it as // soon as we start translating the stack. - alphaNotifications = getNotificationsTopY() / mKeyguardStatusBar.getHeight(); + alpha = getNotificationsTopY() / mKeyguardStatusBar.getHeight(); } - alphaNotifications = MathUtils.constrain(alphaNotifications, 0, 1); - alphaNotifications = (float) Math.pow(alphaNotifications, 0.75); + alpha = MathUtils.constrain(alpha, 0, 1); + alpha = (float) Math.pow(alpha, 0.75); + return alpha; + } + + private void updateHeaderKeyguard() { float alphaQsExpansion = 1 - Math.min(1, getQsExpansionFraction() * 2); - mKeyguardStatusBar.setAlpha(Math.min(alphaNotifications, alphaQsExpansion) + mKeyguardStatusBar.setAlpha(Math.min(getKeyguardContentsAlpha(), alphaQsExpansion) * mKeyguardStatusBarAnimateAlpha); - mKeyguardBottomArea.setAlpha(Math.min(1 - getQsExpansionFraction(), alphaNotifications)); setQsTranslation(mQsExpansionHeight); } + private void updateKeyguardBottomAreaAlpha() { + mKeyguardBottomArea.setAlpha( + Math.min(getKeyguardContentsAlpha(), 1 - getQsExpansionFraction())); + } + private float getNotificationsTopY() { if (mNotificationStackScroller.getNotGoneChildCount() == 0) { return getExpandedHeight(); @@ -1631,7 +1680,7 @@ public class NotificationPanelView extends PanelView implements } @Override - public void onHeightChanged(ExpandableView view) { + public void onHeightChanged(ExpandableView view, boolean needsAnimation) { // Block update if we are in quick settings and just the top padding changed // (i.e. view == null). @@ -1764,6 +1813,7 @@ public class NotificationPanelView extends PanelView implements mSecureCameraLaunchManager.onSwipingStarted(); requestDisallowInterceptTouchEvent(true); mOnlyAffordanceInThisMotion = true; + mQsTracking = false; } @Override @@ -1905,7 +1955,7 @@ public class NotificationPanelView extends PanelView implements mKeyguardBottomArea.setVisibility(View.VISIBLE); mKeyguardStatusBar.setVisibility(View.VISIBLE); if (animate) { - animateKeyguardStatusBarIn(); + animateKeyguardStatusBarIn(DOZE_ANIMATION_DURATION); mKeyguardBottomArea.startFinishDozeAnimation(); } } @@ -1955,6 +2005,32 @@ public class NotificationPanelView extends PanelView implements onEmptySpaceClick(x); } + protected boolean onMiddleClicked() { + switch (mStatusBar.getBarState()) { + case StatusBarState.KEYGUARD: + if (!mDozingOnDown) { + EventLogTags.writeSysuiLockscreenGesture( + EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_TAP_UNLOCK_HINT, + 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */); + startUnlockHintAnimation(); + } + return true; + case StatusBarState.SHADE_LOCKED: + if (!mQsExpanded) { + mStatusBar.goToKeyguard(); + } + return true; + case StatusBarState.SHADE: + + // This gets called in the middle of the touch handling, where the state is still + // that we are tracking the panel. Collapse the panel after this is done. + post(mPostCollapseRunnable); + return false; + default: + return true; + } + } + @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java index e7b0c4c..a03c297 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java @@ -18,7 +18,6 @@ package com.android.systemui.statusbar.phone; import android.content.Context; import android.graphics.Canvas; -import android.graphics.Rect; import android.util.AttributeSet; import android.view.View; import android.view.ViewStub; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java index d86ccee..4bbf690 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java @@ -73,6 +73,7 @@ public abstract class PanelView extends FrameLayout { private boolean mTouchAboveFalsingThreshold; private int mUnlockFalsingThreshold; private boolean mTouchStartedInEmptyArea; + private boolean mMotionAborted; private ValueAnimator mHeightAnimator; private ObjectAnimator mPeekAnimator; @@ -100,7 +101,6 @@ public abstract class PanelView extends FrameLayout { private boolean mCollapseAfterPeek; private boolean mExpanding; private boolean mGestureWaitForTouchSlop; - private boolean mDozingOnDown; private Runnable mPeekRunnable = new Runnable() { @Override public void run() { @@ -209,7 +209,8 @@ public abstract class PanelView extends FrameLayout { @Override public boolean onTouchEvent(MotionEvent event) { - if (mInstantExpanding || mTouchDisabled) { + if (mInstantExpanding || mTouchDisabled + || (mMotionAborted && event.getActionMasked() != MotionEvent.ACTION_DOWN)) { return false; } @@ -227,8 +228,8 @@ public abstract class PanelView extends FrameLayout { pointerIndex = 0; mTrackingPointer = event.getPointerId(pointerIndex); } - final float y = event.getY(pointerIndex); final float x = event.getX(pointerIndex); + final float y = event.getY(pointerIndex); if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { mGestureWaitForTouchSlop = mExpandedHeight == 0f; @@ -245,9 +246,9 @@ public abstract class PanelView extends FrameLayout { mPanelClosedOnDown = mExpandedHeight == 0.0f; mHasLayoutedSinceDown = false; mUpdateFlingOnLayout = false; + mMotionAborted = false; mPeekTouching = mPanelClosedOnDown; mTouchAboveFalsingThreshold = false; - mDozingOnDown = isDozing(); if (mVelocityTracker == null) { initVelocityTracker(); } @@ -278,7 +279,13 @@ public abstract class PanelView extends FrameLayout { mInitialTouchX = newX; } break; - + case MotionEvent.ACTION_POINTER_DOWN: + if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) { + mMotionAborted = true; + endMotionEvent(event, x, y, true /* forceCancel */); + return false; + } + break; case MotionEvent.ACTION_MOVE: float h = y - mInitialTouchY; @@ -320,26 +327,35 @@ public abstract class PanelView extends FrameLayout { case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: - mTrackingPointer = -1; trackMovement(event); - if ((mTracking && mTouchSlopExceeded) - || Math.abs(x - mInitialTouchX) > mTouchSlop - || Math.abs(y - mInitialTouchY) > mTouchSlop - || event.getActionMasked() == MotionEvent.ACTION_CANCEL) { - float vel = 0f; - float vectorVel = 0f; - if (mVelocityTracker != null) { - mVelocityTracker.computeCurrentVelocity(1000); - vel = mVelocityTracker.getYVelocity(); - vectorVel = (float) Math.hypot( - mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity()); - } - boolean expand = flingExpands(vel, vectorVel) - || event.getActionMasked() == MotionEvent.ACTION_CANCEL; - onTrackingStopped(expand); - DozeLog.traceFling(expand, mTouchAboveFalsingThreshold, - mStatusBar.isFalsingThresholdNeeded(), - mStatusBar.isScreenOnComingFromTouch()); + endMotionEvent(event, x, y, false /* forceCancel */); + break; + } + return !waitForTouchSlop || mTracking; + } + + private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) { + mTrackingPointer = -1; + if ((mTracking && mTouchSlopExceeded) + || Math.abs(x - mInitialTouchX) > mTouchSlop + || Math.abs(y - mInitialTouchY) > mTouchSlop + || event.getActionMasked() == MotionEvent.ACTION_CANCEL + || forceCancel) { + float vel = 0f; + float vectorVel = 0f; + if (mVelocityTracker != null) { + mVelocityTracker.computeCurrentVelocity(1000); + vel = mVelocityTracker.getYVelocity(); + vectorVel = (float) Math.hypot( + mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity()); + } + boolean expand = flingExpands(vel, vectorVel) + || event.getActionMasked() == MotionEvent.ACTION_CANCEL + || forceCancel; + onTrackingStopped(expand); + DozeLog.traceFling(expand, mTouchAboveFalsingThreshold, + mStatusBar.isFalsingThresholdNeeded(), + mStatusBar.isScreenOnComingFromTouch()); // Log collapse gesture if on lock screen. if (!expand && mStatusBar.getBarState() == StatusBarState.KEYGUARD) { float displayDensity = mStatusBar.getDisplayDensity(); @@ -349,24 +365,21 @@ public abstract class PanelView extends FrameLayout { EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_UP_UNLOCK, heightDp, velocityDp); } - fling(vel, expand); - mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown; - if (mUpdateFlingOnLayout) { - mUpdateFlingVelocity = vel; - } - } else { - boolean expands = onEmptySpaceClick(mInitialTouchX); - onTrackingStopped(expands); - } + fling(vel, expand); + mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown; + if (mUpdateFlingOnLayout) { + mUpdateFlingVelocity = vel; + } + } else { + boolean expands = onEmptySpaceClick(mInitialTouchX); + onTrackingStopped(expands); + } - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); - mVelocityTracker = null; - } - mPeekTouching = false; - break; + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; } - return !waitForTouchSlop || mTracking; + mPeekTouching = false; } private int getFalsingThreshold() { @@ -391,7 +404,8 @@ public abstract class PanelView extends FrameLayout { @Override public boolean onInterceptTouchEvent(MotionEvent event) { - if (mInstantExpanding) { + if (mInstantExpanding + || (mMotionAborted && event.getActionMasked() != MotionEvent.ACTION_DOWN)) { return false; } @@ -427,11 +441,11 @@ public abstract class PanelView extends FrameLayout { mTouchStartedInEmptyArea = !isInContentBounds(x, y); mTouchSlopExceeded = false; mJustPeeked = false; + mMotionAborted = false; mPanelClosedOnDown = mExpandedHeight == 0.0f; mHasLayoutedSinceDown = false; mUpdateFlingOnLayout = false; mTouchAboveFalsingThreshold = false; - mDozingOnDown = isDozing(); initVelocityTracker(); trackMovement(event); break; @@ -445,7 +459,15 @@ public abstract class PanelView extends FrameLayout { mInitialTouchY = event.getY(newIndex); } break; - + case MotionEvent.ACTION_POINTER_DOWN: + if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) { + mMotionAborted = true; + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + } + break; case MotionEvent.ACTION_MOVE: final float h = y - mInitialTouchY; trackMovement(event); @@ -464,6 +486,10 @@ public abstract class PanelView extends FrameLayout { break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } break; } return false; @@ -942,35 +968,14 @@ public abstract class PanelView extends FrameLayout { } } - private final Runnable mPostCollapseRunnable = new Runnable() { + protected final Runnable mPostCollapseRunnable = new Runnable() { @Override public void run() { collapse(false /* delayed */); } }; - private boolean onMiddleClicked() { - switch (mStatusBar.getBarState()) { - case StatusBarState.KEYGUARD: - if (!mDozingOnDown) { - EventLogTags.writeSysuiLockscreenGesture( - EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_TAP_UNLOCK_HINT, - 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */); - startUnlockHintAnimation(); - } - return true; - case StatusBarState.SHADE_LOCKED: - mStatusBar.goToKeyguard(); - return true; - case StatusBarState.SHADE: - // This gets called in the middle of the touch handling, where the state is still - // that we are tracking the panel. Collapse the panel after this is done. - post(mPostCollapseRunnable); - return false; - default: - return true; - } - } + protected abstract boolean onMiddleClicked(); protected abstract void onEdgeClicked(boolean right); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index f227107..ad78f6a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -32,7 +32,6 @@ import static com.android.systemui.statusbar.phone.BarTransitions.MODE_WARNING; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.animation.TimeInterpolator; import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityManagerNative; @@ -82,14 +81,12 @@ import android.provider.Settings; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.StatusBarNotification; -import android.text.TextUtils; import android.util.ArraySet; import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; import android.view.Display; import android.view.Gravity; -import android.view.HardwareCanvas; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -97,23 +94,16 @@ import android.view.VelocityTracker; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; -import android.view.ViewPropertyAnimator; import android.view.ViewStub; -import android.view.ViewTreeObserver; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityEvent; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.AccelerateInterpolator; -import android.view.animation.Animation; -import android.view.animation.AnimationUtils; -import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; import android.view.animation.PathInterpolator; -import android.widget.FrameLayout; import android.widget.ImageView; -import android.widget.LinearLayout; import android.widget.TextView; import com.android.internal.statusbar.StatusBarIcon; @@ -123,14 +113,14 @@ import com.android.systemui.BatteryMeterView; import com.android.systemui.DemoMode; import com.android.systemui.EventLogConstants; import com.android.systemui.EventLogTags; -import com.android.systemui.FontSizeUtils; import com.android.systemui.R; import com.android.systemui.doze.DozeHost; import com.android.systemui.doze.DozeLog; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.qs.QSPanel; -import com.android.systemui.recent.ScreenPinningRequest; +import com.android.systemui.recents.ScreenPinningRequest; import com.android.systemui.statusbar.ActivatableNotificationView; +import com.android.systemui.assist.AssistGestureManager; import com.android.systemui.statusbar.BackDropView; import com.android.systemui.statusbar.BaseStatusBar; import com.android.systemui.statusbar.CommandQueue; @@ -172,8 +162,7 @@ import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout.OnChildLocationsChangedListener; -import com.android.systemui.statusbar.stack.StackScrollAlgorithm; -import com.android.systemui.statusbar.stack.StackScrollState.ViewState; +import com.android.systemui.statusbar.stack.StackViewState; import com.android.systemui.volume.VolumeComponent; import java.io.FileDescriptor; @@ -181,6 +170,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -199,9 +189,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, // additional instrumentation for testing purposes; intended to be left on during development public static final boolean CHATTY = DEBUG; - public static final String ACTION_STATUSBAR_START - = "com.android.internal.policy.statusbar.START"; - public static final boolean SHOW_LOCKSCREEN_MEDIA_ARTWORK = true; private static final int MSG_OPEN_NOTIFICATION_PANEL = 1000; @@ -215,9 +202,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, private static final boolean CLOSE_PANEL_WHEN_EMPTIED = true; - private static final int NOTIFICATION_PRIORITY_MULTIPLIER = 10; // see NotificationManagerService - private static final int HIDE_ICONS_BELOW_SCORE = Notification.PRIORITY_LOW * NOTIFICATION_PRIORITY_MULTIPLIER; - private static final int STATUS_OR_NAV_TRANSIENT = View.STATUS_BAR_TRANSIENT | View.NAVIGATION_BAR_TRANSIENT; private static final long AUTOHIDE_TIMEOUT_MS = 3000; @@ -264,8 +248,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, AccessibilityController mAccessibilityController; int mNaturalBarHeight = -1; - int mIconSize = -1; - int mIconHPadding = -1; + Display mDisplay; Point mCurrentDisplaySize = new Point(); @@ -281,34 +264,14 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, int mPixelFormat; Object mQueueLock = new Object(); - // viewgroup containing the normal contents of the statusbar - LinearLayout mStatusBarContents; - - // right-hand icons - LinearLayout mSystemIconArea; - LinearLayout mSystemIcons; - - // left-hand icons - LinearLayout mStatusIcons; - LinearLayout mStatusIconsKeyguard; - - // the icons themselves - IconMerger mNotificationIcons; - View mNotificationIconArea; - - // [+> - View mMoreIcon; + StatusBarIconController mIconController; // expanded notifications NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window View mExpandedContents; - int mNotificationPanelGravity; - int mNotificationPanelMarginBottomPx; - float mNotificationPanelMinHeightFrac; TextView mNotificationPanelDebugText; // settings - View mFlipSettingsView; private QSPanel mQSPanel; // top bar @@ -325,16 +288,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, int mKeyguardMaxNotificationCount; - // carrier/wifi label - private TextView mCarrierLabel; - private boolean mCarrierLabelVisible = false; - private int mCarrierLabelHeight; - private int mStatusBarHeaderHeight; - - private boolean mShowCarrierInPanel = false; - - // position - int[] mPositionTmp = new int[2]; boolean mExpandedVisible; private int mNavigationBarWindowState = WINDOW_STATE_SHOWING; @@ -342,12 +295,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, // the tracker view int mTrackingPosition; // the position of the top of the tracking view. - // ticker - private boolean mTickerEnabled; - private Ticker mTicker; - private View mTickerView; - private boolean mTicking; - // Tracking finger for opening/closing. int mEdgeBorder; // corresponds to R.dimen.status_bar_edge_ignore boolean mTracking; @@ -374,6 +321,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, private int mNavigationIconHints = 0; private HandlerThread mHandlerThread; + private AssistGestureManager mAssistGestureManager; + // ensure quick settings is disabled until the current user makes it through the setup wizard private boolean mUserSetup = false; private ContentObserver mUserSetupObserver = new ContentObserver(new Handler()) { @@ -411,7 +360,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, if (!mUseHeadsUp) { Log.d(TAG, "dismissing any existing heads up notification on disable event"); setHeadsUpVisibility(false); - mHeadsUpNotificationView.release(); + mHeadsUpNotificationView.releaseImmediately(); removeHeadsUpView(); } else { addHeadsUpView(); @@ -442,7 +391,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, private boolean mDozing; private boolean mScrimSrcModeEnabled; - private Interpolator mLinearOutSlowIn; private Interpolator mLinearInterpolator = new LinearInterpolator(); private Interpolator mBackdropInterpolator = new AccelerateDecelerateInterpolator(); public static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f); @@ -498,10 +446,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, // Fingerprint (as computed by getLoggingFingerprint() of the last logged state. private int mLastLoggedStateFingerprint; - private static final int VISIBLE_LOCATIONS = ViewState.LOCATION_FIRST_CARD - | ViewState.LOCATION_TOP_STACK_PEEKING - | ViewState.LOCATION_MAIN_AREA - | ViewState.LOCATION_BOTTOM_STACK_PEEKING; + private static final int VISIBLE_LOCATIONS = StackViewState.LOCATION_FIRST_CARD + | StackViewState.LOCATION_TOP_STACK_PEEKING + | StackViewState.LOCATION_MAIN_AREA + | StackViewState.LOCATION_BOTTOM_STACK_PEEKING; private final OnChildLocationsChangedListener mNotificationLocationsChangedListener = new OnChildLocationsChangedListener() { @@ -577,6 +525,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, goToLockedShade(null); } }; + private HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>> mTmpChildOrderMap + = new HashMap<>(); @Override public void start() { @@ -585,6 +535,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, updateDisplaySize(); mScrimSrcModeEnabled = mContext.getResources().getBoolean( R.bool.config_status_bar_scrim_behind_use_src); + super.start(); // calls createAndAddWindows() mMediaSessionManager @@ -633,8 +584,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, updateDisplaySize(); // populates mDisplayMetrics updateResources(); - mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size); - mStatusBarWindow = (StatusBarWindowView) View.inflate(context, R.layout.super_status_bar, null); mStatusBarWindow.mService = this; @@ -662,7 +611,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, if (!ActivityManager.isHighEndGfx()) { mStatusBarWindow.setBackground(null); - mNotificationPanel.setBackground(new FastColorDrawable(context.getResources().getColor( + mNotificationPanel.setBackground(new FastColorDrawable(context.getColor( R.color.notification_panel_solid_background))); } if (ENABLE_HEADS_UP) { @@ -692,8 +641,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, new NavigationBarView.OnVerticalChangedListener() { @Override public void onVerticalChanged(boolean isVertical) { - if (mSearchPanelView != null) { - mSearchPanelView.setHorizontal(isVertical); + if (mAssistGestureManager != null) { + mAssistGestureManager.onConfigurationChanged(); } mNotificationPanel.setQsScrimEnabled(!isVertical); } @@ -712,19 +661,12 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, // figure out which pixel-format to use for the status bar. mPixelFormat = PixelFormat.OPAQUE; - mSystemIconArea = (LinearLayout) mStatusBarView.findViewById(R.id.system_icon_area); - mSystemIcons = (LinearLayout) mStatusBarView.findViewById(R.id.system_icons); - mStatusIcons = (LinearLayout)mStatusBarView.findViewById(R.id.statusIcons); - mNotificationIconArea = mStatusBarView.findViewById(R.id.notification_icon_area_inner); - mNotificationIcons = (IconMerger)mStatusBarView.findViewById(R.id.notificationIcons); - mMoreIcon = mStatusBarView.findViewById(R.id.moreIcon); - mNotificationIcons.setOverflowIndicator(mMoreIcon); - mStatusBarContents = (LinearLayout)mStatusBarView.findViewById(R.id.status_bar_contents); - mStackScroller = (NotificationStackScrollLayout) mStatusBarWindow.findViewById( R.id.notification_stack_scroller); mStackScroller.setLongPressListener(getNotificationLongClicker()); mStackScroller.setPhoneStatusBar(this); + mStackScroller.setGroupManager(mGroupManager); + mGroupManager.setOnGroupChangeListener(mStackScroller); mKeyguardIconOverflowContainer = (NotificationOverflowContainer) LayoutInflater.from(mContext).inflate( @@ -764,7 +706,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mHeader = (StatusBarHeaderView) mStatusBarWindow.findViewById(R.id.header); mHeader.setActivityStarter(this); mKeyguardStatusBar = (KeyguardStatusBarView) mStatusBarWindow.findViewById(R.id.keyguard_header); - mStatusIconsKeyguard = (LinearLayout) mKeyguardStatusBar.findViewById(R.id.statusIcons); mKeyguardStatusView = mStatusBarWindow.findViewById(R.id.keyguard_status_view); mKeyguardBottomArea = (KeyguardBottomAreaView) mStatusBarWindow.findViewById(R.id.keyguard_bottom_area); @@ -774,23 +715,14 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, R.id.keyguard_indication_text)); mKeyguardBottomArea.setKeyguardIndicationController(mKeyguardIndicationController); - mTickerEnabled = res.getBoolean(R.bool.enable_ticker); - if (mTickerEnabled) { - final ViewStub tickerStub = (ViewStub) mStatusBarView.findViewById(R.id.ticker_stub); - if (tickerStub != null) { - mTickerView = tickerStub.inflate(); - mTicker = new MyTicker(context, mStatusBarView); - - TickerView tickerView = (TickerView) mStatusBarView.findViewById(R.id.tickerText); - tickerView.mTicker = mTicker; - } - } - mEdgeBorder = res.getDimensionPixelSize(R.dimen.status_bar_edge_ignore); // set the inital view visibility setAreThereNotifications(); + mIconController = new StatusBarIconController( + mContext, mStatusBarView, mKeyguardStatusBar, this); + // Background thread for any controllers that need it. mHandlerThread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND); mHandlerThread.start(); @@ -849,27 +781,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, }); } - mCarrierLabel = (TextView)mStatusBarWindow.findViewById(R.id.carrier_label); - mShowCarrierInPanel = (mCarrierLabel != null); - if (DEBUG) Log.v(TAG, "carrierlabel=" + mCarrierLabel + " show=" + mShowCarrierInPanel); - if (mShowCarrierInPanel) { - mCarrierLabel.setVisibility(mCarrierLabelVisible ? View.VISIBLE : View.INVISIBLE); - - mNetworkController.addCarrierLabel(new NetworkControllerImpl.CarrierLabelListener() { - @Override - public void setCarrierLabel(String label) { - mCarrierLabel.setText(label); - if (mNetworkController.hasMobileDataFeature()) { - if (TextUtils.isEmpty(label)) { - mCarrierLabel.setVisibility(View.GONE); - } else { - mCarrierLabel.setVisibility(View.VISIBLE); - } - } - } - }); - } - mFlashlightController = new FlashlightController(mContext); mKeyguardBottomArea.setFlashlightController(mFlashlightController); mKeyguardBottomArea.setPhoneStatusBar(this); @@ -922,6 +833,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mBroadcastReceiver.onReceive(mContext, new Intent(pm.isScreenOn() ? Intent.ACTION_SCREEN_ON : Intent.ACTION_SCREEN_OFF)); + mAssistGestureManager = new AssistGestureManager(this, context); + // receive broadcasts IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); @@ -936,7 +849,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, // listen for USER_SETUP_COMPLETE setting (per-user) resetUserSetupObserver(); - startGlyphRasterizeHack(); return mStatusBarView; } @@ -948,9 +860,20 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, final ArrayList<View> viewsToHide = new ArrayList<View>(numChildren); for (int i = 0; i < numChildren; i++) { final View child = mStackScroller.getChildAt(i); - if (mStackScroller.canChildBeDismissed(child)) { - if (child.getVisibility() == View.VISIBLE) { - viewsToHide.add(child); + if (child instanceof ExpandableNotificationRow) { + if (mStackScroller.canChildBeDismissed(child)) { + if (child.getVisibility() == View.VISIBLE) { + viewsToHide.add(child); + } + } + ExpandableNotificationRow row = (ExpandableNotificationRow) child; + List<ExpandableNotificationRow> children = row.getNotificationChildren(); + if (row.areChildrenExpanded() && children != null) { + for (ExpandableNotificationRow childRow : children) { + if (childRow.getVisibility() == View.VISIBLE) { + viewsToHide.add(childRow); + } + } } } } @@ -1007,30 +930,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } } - /** - * Hack to improve glyph rasterization for scaled text views. - */ - private void startGlyphRasterizeHack() { - mStatusBarView.getViewTreeObserver().addOnPreDrawListener( - new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - if (mDrawCount == 1) { - mStatusBarView.getViewTreeObserver().removeOnPreDrawListener(this); - HardwareCanvas.setProperty("extraRasterBucket", - Float.toString(StackScrollAlgorithm.DIMMED_SCALE)); - HardwareCanvas.setProperty("extraRasterBucket", Float.toString( - mContext.getResources().getDimensionPixelSize( - R.dimen.qs_time_collapsed_size) - / mContext.getResources().getDimensionPixelSize( - R.dimen.qs_time_expanded_size))); - } - mDrawCount++; - return true; - } - }); - } - @Override protected void setZenMode(int mode) { super.setZenMode(mode); @@ -1055,60 +954,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, return mStatusBarWindow; } - @Override - protected WindowManager.LayoutParams getSearchLayoutParams(LayoutParams layoutParams) { - boolean opaque = false; - WindowManager.LayoutParams lp = new WindowManager.LayoutParams( - LayoutParams.MATCH_PARENT, - LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, - WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN - | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM - | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, - (opaque ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT)); - if (ActivityManager.isHighEndGfx()) { - lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; - } - lp.gravity = Gravity.BOTTOM | Gravity.START; - lp.setTitle("SearchPanel"); - lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED - | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; - return lp; - } - - @Override - protected void updateSearchPanel() { - super.updateSearchPanel(); - if (mNavigationBarView != null) { - mNavigationBarView.setDelegateView(mSearchPanelView); - } - } - - @Override - public void showSearchPanel() { - super.showSearchPanel(); - mHandler.removeCallbacks(mShowSearchPanel); - - // we want to freeze the sysui state wherever it is - mSearchPanelView.setSystemUiVisibility(mSystemUiVisibility); - - if (mNavigationBarView != null) { - WindowManager.LayoutParams lp = - (android.view.WindowManager.LayoutParams) mNavigationBarView.getLayoutParams(); - lp.flags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; - mWindowManager.updateViewLayout(mNavigationBarView, lp); - } - } - - @Override - public void hideSearchPanel() { - super.hideSearchPanel(); - if (mNavigationBarView != null) { - WindowManager.LayoutParams lp = - (android.view.WindowManager.LayoutParams) mNavigationBarView.getLayoutParams(); - lp.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; - mWindowManager.updateViewLayout(mNavigationBarView, lp); - } + public void invokeAssistGesture(boolean vibrate) { + mHandler.removeCallbacks(mInvokeAssist); + mAssistGestureManager.onGestureInvoked(vibrate); } public int getStatusBarHeight() { @@ -1138,30 +986,33 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, }; private int mShowSearchHoldoff = 0; - private Runnable mShowSearchPanel = new Runnable() { + private Runnable mInvokeAssist = new Runnable() { public void run() { - showSearchPanel(); + invokeAssistGesture(true /* vibrate */); awakenDreams(); + if (mNavigationBarView != null) { + mNavigationBarView.getHomeButton().abortCurrentGesture(); + } } }; View.OnTouchListener mHomeActionListener = new View.OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { - switch(event.getAction()) { + switch (event.getAction()) { case MotionEvent.ACTION_DOWN: - if (!shouldDisableNavbarGestures()) { - mHandler.removeCallbacks(mShowSearchPanel); - mHandler.postDelayed(mShowSearchPanel, mShowSearchHoldoff); - } - break; + if (!shouldDisableNavbarGestures()) { + mHandler.removeCallbacks(mInvokeAssist); + mHandler.postDelayed(mInvokeAssist, mShowSearchHoldoff); + } + break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - mHandler.removeCallbacks(mShowSearchPanel); - awakenDreams(); - break; - } - return false; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + mHandler.removeCallbacks(mInvokeAssist); + awakenDreams(); + break; + } + return false; } }; @@ -1185,7 +1036,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mNavigationBarView.getBackButton().setLongClickable(true); mNavigationBarView.getBackButton().setOnLongClickListener(mLongPressBackRecentsListener); mNavigationBarView.getHomeButton().setOnTouchListener(mHomeActionListener); - updateSearchPanel(); + mAssistGestureManager.onConfigurationChanged(); } // For small-screen devices (read: phones) that lack hardware navigation buttons @@ -1258,49 +1109,17 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mWindowManager.removeView(mHeadsUpNotificationView); } - public void refreshAllStatusBarIcons() { - refreshAllIconsForLayout(mStatusIcons); - refreshAllIconsForLayout(mStatusIconsKeyguard); - refreshAllIconsForLayout(mNotificationIcons); - } - - private void refreshAllIconsForLayout(LinearLayout ll) { - final int count = ll.getChildCount(); - for (int n = 0; n < count; n++) { - View child = ll.getChildAt(n); - if (child instanceof StatusBarIconView) { - ((StatusBarIconView) child).updateDrawable(); - } - } - } - public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) { - if (SPEW) Log.d(TAG, "addIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex - + " icon=" + icon); - StatusBarIconView view = new StatusBarIconView(mContext, slot, null); - view.set(icon); - mStatusIcons.addView(view, viewIndex, new LinearLayout.LayoutParams( - LayoutParams.WRAP_CONTENT, mIconSize)); - view = new StatusBarIconView(mContext, slot, null); - view.set(icon); - mStatusIconsKeyguard.addView(view, viewIndex, new LinearLayout.LayoutParams( - LayoutParams.WRAP_CONTENT, mIconSize)); + mIconController.addSystemIcon(slot, index, viewIndex, icon); } public void updateIcon(String slot, int index, int viewIndex, StatusBarIcon old, StatusBarIcon icon) { - if (SPEW) Log.d(TAG, "updateIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex - + " old=" + old + " icon=" + icon); - StatusBarIconView view = (StatusBarIconView) mStatusIcons.getChildAt(viewIndex); - view.set(icon); - view = (StatusBarIconView) mStatusIconsKeyguard.getChildAt(viewIndex); - view.set(icon); + mIconController.updateSystemIcon(slot, index, viewIndex, old, icon); } public void removeIcon(String slot, int index, int viewIndex) { - if (SPEW) Log.d(TAG, "removeIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex); - mStatusIcons.removeViewAt(viewIndex); - mStatusIconsKeyguard.removeViewAt(viewIndex); + mIconController.removeSystemIcon(slot, index, viewIndex); } public UserHandle getCurrentUserHandle() { @@ -1308,11 +1127,19 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } @Override - public void addNotification(StatusBarNotification notification, RankingMap ranking) { + public void addNotification(StatusBarNotification notification, RankingMap ranking, + Entry oldEntry) { if (DEBUG) Log.d(TAG, "addNotification key=" + notification.getKey()); if (mUseHeadsUp && shouldInterrupt(notification)) { if (DEBUG) Log.d(TAG, "launching notification in heads up mode"); - Entry interruptionCandidate = new Entry(notification, null); + Entry interruptionCandidate = oldEntry; + if (interruptionCandidate == null) { + final StatusBarIconView iconView = createIcon(notification); + if (iconView == null) { + return; + } + interruptionCandidate = new Entry(notification, iconView); + } ViewGroup holder = mHeadsUpNotificationView.getHolder(); if (inflateViewsForHeadsUp(interruptionCandidate, holder)) { // 1. Populate mHeadsUpNotificationView @@ -1341,58 +1168,22 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, notification.getNotification().fullScreenIntent.send(); } catch (PendingIntent.CanceledException e) { } - } else { - // usual case: status bar visible & not immersive - - // show the ticker if there isn't already a heads up - if (mHeadsUpNotificationView.getEntry() == null) { - tick(notification, true); - } } addNotificationViews(shadeEntry, ranking); // Recalculate the position of the sliding windows and the titles. setAreThereNotifications(); - updateExpandedViewPos(EXPANDED_LEAVE_ALONE); } - public void displayNotificationFromHeadsUp(StatusBarNotification notification) { - NotificationData.Entry shadeEntry = createNotificationViews(notification); - if (shadeEntry == null) { - return; - } + public void displayNotificationFromHeadsUp(Entry shadeEntry) { + + // The notification comes from the headsup, let's inflate the normal layout again + inflateViews(shadeEntry, mStackScroller); shadeEntry.setInterruption(); + shadeEntry.row.setHeadsUp(false); addNotificationViews(shadeEntry, null); // Recalculate the position of the sliding windows and the titles. setAreThereNotifications(); - updateExpandedViewPos(EXPANDED_LEAVE_ALONE); - } - - @Override - public void resetHeadsUpDecayTimer() { - mHandler.removeMessages(MSG_DECAY_HEADS_UP); - if (mUseHeadsUp && mHeadsUpNotificationDecay > 0 - && mHeadsUpNotificationView.isClearable()) { - mHandler.sendEmptyMessageDelayed(MSG_DECAY_HEADS_UP, mHeadsUpNotificationDecay); - } - } - - @Override - public void scheduleHeadsUpOpen() { - mHandler.removeMessages(MSG_SHOW_HEADS_UP); - mHandler.sendEmptyMessage(MSG_SHOW_HEADS_UP); - } - - @Override - public void scheduleHeadsUpClose() { - mHandler.removeMessages(MSG_HIDE_HEADS_UP); - mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP); - } - - @Override - public void scheduleHeadsUpEscalation() { - mHandler.removeMessages(MSG_ESCALATE_HEADS_UP); - mHandler.sendEmptyMessage(MSG_ESCALATE_HEADS_UP); } @Override @@ -1403,23 +1194,14 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, @Override public void removeNotification(String key, RankingMap ranking) { - if (ENABLE_HEADS_UP && mHeadsUpNotificationView.getEntry() != null - && key.equals(mHeadsUpNotificationView.getEntry().notification.getKey())) { - mHeadsUpNotificationView.clear(); + if (ENABLE_HEADS_UP) { + mHeadsUpNotificationView.removeNotification(key); } StatusBarNotification old = removeNotificationViews(key, ranking); if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old); if (old != null) { - // Cancel the ticker if it's still running - if (mTickerEnabled) { - mTicker.removeEntry(old); - } - - // Recalculate the position of the sliding windows and the titles. - updateExpandedViewPos(EXPANDED_LEAVE_ALONE); - if (CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications() && !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()) { if (mState == StatusBarState.SHADE) { @@ -1437,7 +1219,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, if (mNavigationBarView != null) { mNavigationBarView.setLayoutDirection(layoutDirection); } - refreshAllStatusBarIcons(); } private void updateShowSearchHoldoff() { @@ -1483,10 +1264,23 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, ent.row.setShowingLegacyBackground(true); } } - toShow.add(ent.row); + if (mGroupManager.isChildInGroupWithSummary(ent.row.getStatusBarNotification())) { + ExpandableNotificationRow summary = mGroupManager.getGroupSummary( + ent.row.getStatusBarNotification()); + List<ExpandableNotificationRow> orderedChildren = + mTmpChildOrderMap.get(summary); + if (orderedChildren == null) { + orderedChildren = new ArrayList<>(); + mTmpChildOrderMap.put(summary, orderedChildren); + } + orderedChildren.add(ent.row); + } else { + toShow.add(ent.row); + } + } - ArrayList<View> toRemove = new ArrayList<View>(); + ArrayList<View> toRemove = new ArrayList<>(); for (int i=0; i< mStackScroller.getChildCount(); i++) { View child = mStackScroller.getChildAt(i); if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) { @@ -1515,17 +1309,22 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, continue; } - if (child == toShow.get(j)) { - // Everything is well, advance both lists. - j++; - continue; + ExpandableNotificationRow targetChild = toShow.get(j); + if (child != targetChild) { + // Oops, wrong notification at this position. Put the right one + // here and advance both lists. + mStackScroller.changeViewPosition(targetChild, i); } - - // Oops, wrong notification at this position. Put the right one - // here and advance both lists. - mStackScroller.changeViewPosition(toShow.get(j), i); j++; + } + + // lets handle the child notifications now + updateNotificationShadeForChildren(); + + // clear the map again for the next usage + mTmpChildOrderMap.clear(); + updateRowStates(); updateSpeedbump(); updateClearAll(); @@ -1540,6 +1339,52 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mShadeUpdates.check(); } + private void updateNotificationShadeForChildren() { + ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>(); + boolean orderChanged = false; + for (int i = 0; i < mStackScroller.getChildCount(); i++) { + View view = mStackScroller.getChildAt(i); + if (!(view instanceof ExpandableNotificationRow)) { + // We don't care about non-notification views. + continue; + } + + ExpandableNotificationRow parent = (ExpandableNotificationRow) view; + List<ExpandableNotificationRow> children = parent.getNotificationChildren(); + List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent); + + // lets first remove all undesired children + if (children != null) { + toRemove.clear(); + for (ExpandableNotificationRow childRow : children) { + if (orderedChildren == null || !orderedChildren.contains(childRow)) { + toRemove.add(childRow); + } + } + for (ExpandableNotificationRow remove : toRemove) { + parent.removeChildNotification(remove); + mStackScroller.notifyGroupChildRemoved(remove); + } + } + + // We now add all the children which are not in there already + for (int childIndex = 0; orderedChildren != null && childIndex < orderedChildren.size(); + childIndex++) { + ExpandableNotificationRow childView = orderedChildren.get(childIndex); + if (children == null || !children.contains(childView)) { + parent.addChildNotification(childView, childIndex); + mStackScroller.notifyGroupChildAdded(childView); + } + } + + // Finally after removing and adding has been beformed we can apply the order. + orderChanged |= parent.applyChildOrder(orderedChildren); + } + if (orderChanged) { + mStackScroller.generateChildOrderChangedEvent(); + } + } + private boolean packageHasVisibilityOverride(String key) { return mNotificationData.getVisibilityOverride(key) != NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE; @@ -1566,6 +1411,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, final int N = activeNotifications.size(); for (int i = 0; i < N; i++) { Entry entry = activeNotifications.get(i); + boolean isChild = !isTopLevelChild(entry); + if (isChild) { + continue; + } if (entry.row.getVisibility() != View.GONE && mNotificationData.isAmbient(entry.key)) { speedbumpIndex = currentIndex; @@ -1576,71 +1425,16 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mStackScroller.updateSpeedBumpIndex(speedbumpIndex); } + public static boolean isTopLevelChild(Entry entry) { + return entry.row.getParent() instanceof NotificationStackScrollLayout; + } + @Override protected void updateNotifications() { - // TODO: Move this into updateNotificationIcons()? - if (mNotificationIcons == null) return; - mNotificationData.filterAndSort(); updateNotificationShade(); - updateNotificationIcons(); - } - - private void updateNotificationIcons() { - final LinearLayout.LayoutParams params - = new LinearLayout.LayoutParams(mIconSize + 2*mIconHPadding, mNaturalBarHeight); - - ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications(); - final int N = activeNotifications.size(); - ArrayList<StatusBarIconView> toShow = new ArrayList<>(N); - - // Filter out notifications with low scores. - for (int i = 0; i < N; i++) { - Entry ent = activeNotifications.get(i); - if (ent.notification.getScore() < HIDE_ICONS_BELOW_SCORE && - !NotificationData.showNotificationEvenIfUnprovisioned(ent.notification)) { - continue; - } - toShow.add(ent.icon); - } - - if (DEBUG) { - Log.d(TAG, "refreshing icons: " + toShow.size() + - " notifications, mNotificationIcons=" + mNotificationIcons); - } - - ArrayList<View> toRemove = new ArrayList<View>(); - for (int i=0; i<mNotificationIcons.getChildCount(); i++) { - View child = mNotificationIcons.getChildAt(i); - if (!toShow.contains(child)) { - toRemove.add(child); - } - } - - final int toRemoveCount = toRemove.size(); - for (int i = 0; i < toRemoveCount; i++) { - mNotificationIcons.removeView(toRemove.get(i)); - } - - for (int i=0; i<toShow.size(); i++) { - View v = toShow.get(i); - if (v.getParent() == null) { - mNotificationIcons.addView(v, i, params); - } - } - - // Resort notification icons - final int childCount = mNotificationIcons.getChildCount(); - for (int i = 0; i < childCount; i++) { - View actual = mNotificationIcons.getChildAt(i); - StatusBarIconView expected = toShow.get(i); - if (actual == expected) { - continue; - } - mNotificationIcons.removeView(expected); - mNotificationIcons.addView(expected, i); - } + mIconController.updateNotificationIcons(mNotificationData); } @Override @@ -1649,53 +1443,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mNotificationPanel.notifyVisibleChildrenChanged(); } - protected void updateCarrierLabelVisibility(boolean force) { - // TODO: Handle this for the notification stack scroller as well - if (!mShowCarrierInPanel) return; - // The idea here is to only show the carrier label when there is enough room to see it, - // i.e. when there aren't enough notifications to fill the panel. - if (SPEW) { - Log.d(TAG, String.format("stackScrollerh=%d scrollh=%d carrierh=%d", - mStackScroller.getHeight(), mStackScroller.getHeight(), - mCarrierLabelHeight)); - } - - // Emergency calls only is shown in the expanded header now. - final boolean emergencyCallsShownElsewhere = true; - final boolean makeVisible = - !(emergencyCallsShownElsewhere && mNetworkController.isEmergencyOnly()) - && mStackScroller.getHeight() < (mNotificationPanel.getHeight() - - mCarrierLabelHeight - mStatusBarHeaderHeight) - && mStackScroller.getVisibility() == View.VISIBLE - && mState != StatusBarState.KEYGUARD; - - if (force || mCarrierLabelVisible != makeVisible) { - mCarrierLabelVisible = makeVisible; - if (DEBUG) { - Log.d(TAG, "making carrier label " + (makeVisible?"visible":"invisible")); - } - mCarrierLabel.animate().cancel(); - if (makeVisible) { - mCarrierLabel.setVisibility(View.VISIBLE); - } - mCarrierLabel.animate() - .alpha(makeVisible ? 1f : 0f) - //.setStartDelay(makeVisible ? 500 : 0) - //.setDuration(makeVisible ? 750 : 100) - .setDuration(150) - .setListener(makeVisible ? null : new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (!mCarrierLabelVisible) { // race - mCarrierLabel.setVisibility(View.INVISIBLE); - mCarrierLabel.setAlpha(0f); - } - } - }) - .start(); - } - } - @Override protected void setAreThereNotifications() { @@ -1728,8 +1475,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } findAndUpdateMediaNotifications(); - - updateCarrierLabelVisibility(false); } public void findAndUpdateMediaNotifications() { @@ -1973,14 +1718,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } } - public void showClock(boolean show) { - if (mStatusBarView == null) return; - View clock = mStatusBarView.findViewById(R.id.clock); - if (clock != null) { - clock.setVisibility(show ? View.VISIBLE : View.GONE); - } - } - private int adjustDisableFlags(int state) { if (!mLaunchTransitionFadingAway && (mExpandedVisible || mBouncerShowing || mWaitingForKeyguardExit)) { @@ -2029,17 +1766,16 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, Log.d(TAG, flagdbg.toString()); if ((diff & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) { - mSystemIconArea.animate().cancel(); if ((state & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) { - animateStatusBarHide(mSystemIconArea, animate); + mIconController.hideSystemIconArea(animate); } else { - animateStatusBarShow(mSystemIconArea, animate); + mIconController.showSystemIconArea(animate); } } if ((diff & StatusBarManager.DISABLE_CLOCK) != 0) { - boolean show = (state & StatusBarManager.DISABLE_CLOCK) == 0; - showClock(show); + boolean visible = (state & StatusBarManager.DISABLE_CLOCK) == 0; + mIconController.setClockVisibility(visible); } if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) { if ((state & StatusBarManager.DISABLE_EXPAND) != 0) { @@ -2063,12 +1799,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { if ((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { - if (mTicking) { - haltTicker(); - } - animateStatusBarHide(mNotificationIconArea, animate); + mIconController.hideNotificationIconArea(animate); } else { - animateStatusBarShow(mNotificationIconArea, animate); + mIconController.showNotificationIconArea(animate); } } @@ -2079,60 +1812,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } } - /** - * Animates {@code v}, a view that is part of the status bar, out. - */ - private void animateStatusBarHide(final View v, boolean animate) { - v.animate().cancel(); - if (!animate) { - v.setAlpha(0f); - v.setVisibility(View.INVISIBLE); - return; - } - v.animate() - .alpha(0f) - .setDuration(160) - .setStartDelay(0) - .setInterpolator(ALPHA_OUT) - .withEndAction(new Runnable() { - @Override - public void run() { - v.setVisibility(View.INVISIBLE); - } - }); - } - - /** - * Animates {@code v}, a view that is part of the status bar, in. - */ - private void animateStatusBarShow(View v, boolean animate) { - v.animate().cancel(); - v.setVisibility(View.VISIBLE); - if (!animate) { - v.setAlpha(1f); - return; - } - v.animate() - .alpha(1f) - .setDuration(320) - .setInterpolator(ALPHA_IN) - .setStartDelay(50) - - // We need to clean up any pending end action from animateStatusBarHide if we call - // both hide and show in the same frame before the animation actually gets started. - // cancel() doesn't really remove the end action. - .withEndAction(null); - - // Synchronize the motion with the Keyguard fading if necessary. - if (mKeyguardFadingAway) { - v.animate() - .setDuration(mKeyguardFadingAwayDuration) - .setInterpolator(mLinearOutSlowIn) - .setStartDelay(mKeyguardFadingAwayDelay) - .start(); - } - } - @Override protected BaseStatusBar.H createHandler() { return new PhoneStatusBar.H(); @@ -2143,10 +1822,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, startActivityDismissingKeyguard(intent, false, dismissShade); } - public ScrimController getScrimController() { - return mScrimController; - } - public void setQsExpanded(boolean expanded) { mStatusBarWindowManager.setQsExpanded(expanded); } @@ -2213,16 +1888,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, case MSG_SHOW_HEADS_UP: setHeadsUpVisibility(true); break; - case MSG_DECAY_HEADS_UP: - mHeadsUpNotificationView.release(); - setHeadsUpVisibility(false); - break; - case MSG_HIDE_HEADS_UP: - mHeadsUpNotificationView.release(); - setHeadsUpVisibility(false); - break; case MSG_ESCALATE_HEADS_UP: escalateHeadsUp(); + case MSG_HIDE_HEADS_UP: + mHeadsUpNotificationView.releaseImmediately(); setHeadsUpVisibility(false); break; case MSG_LAUNCH_TRANSITION_TIMEOUT: @@ -2232,11 +1901,41 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } } + @Override + public void scheduleHeadsUpDecay(long delay) { + mHandler.removeMessages(MSG_HIDE_HEADS_UP); + if (mHeadsUpNotificationView.isClearable()) { + mHandler.sendEmptyMessageDelayed(MSG_HIDE_HEADS_UP, delay); + } + } + + @Override + public void scheduleHeadsUpOpen() { + mHandler.removeMessages(MSG_HIDE_HEADS_UP); + mHandler.removeMessages(MSG_SHOW_HEADS_UP); + mHandler.sendEmptyMessage(MSG_SHOW_HEADS_UP); + } + + @Override + public void scheduleHeadsUpClose() { + mHandler.removeMessages(MSG_HIDE_HEADS_UP); + if (mHeadsUpNotificationView.getVisibility() != View.GONE) { + mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP); + } + } + + @Override + public void scheduleHeadsUpEscalation() { + mHandler.removeMessages(MSG_HIDE_HEADS_UP); + mHandler.removeMessages(MSG_ESCALATE_HEADS_UP); + mHandler.sendEmptyMessage(MSG_ESCALATE_HEADS_UP); + } + /** if the interrupting notification had a fullscreen intent, fire it now. */ private void escalateHeadsUp() { if (mHeadsUpNotificationView.getEntry() != null) { final StatusBarNotification sbn = mHeadsUpNotificationView.getEntry().notification; - mHeadsUpNotificationView.release(); + mHeadsUpNotificationView.releaseImmediately(); final Notification notification = sbn.getNotification(); if (notification.fullScreenIntent != null) { if (DEBUG) @@ -2251,14 +1950,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } } - View.OnFocusChangeListener mFocusChangeListener = new View.OnFocusChangeListener() { - public void onFocusChange(View v, boolean hasFocus) { - // Because 'v' is a ViewGroup, all its children will be (un)selected - // too, which allows marqueeing to work. - v.setSelected(hasFocus); - } - }; - boolean panelsEnabled() { return (mDisabled & StatusBarManager.DISABLE_EXPAND) == 0; } @@ -2273,10 +1964,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, if (mNavigationBarView != null) mNavigationBarView.setSlippery(true); - updateCarrierLabelVisibility(true); - - updateExpandedViewPos(EXPANDED_LEAVE_ALONE); - // Expand the window to encompass the full screen in anticipation of the drag. // This is only possible to do atomically because the status bar is at the top of the screen! mStatusBarWindowManager.setStatusBarExpanded(true); @@ -2326,11 +2013,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } } - if ((flags & CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL) == 0) { - mHandler.removeMessages(MSG_CLOSE_SEARCH_PANEL); - mHandler.sendEmptyMessage(MSG_CLOSE_SEARCH_PANEL); - } - if (mStatusBarWindow != null) { // release focus immediately to kick off focus change transition mStatusBarWindowManager.setStatusBarFocusable(false); @@ -2348,50 +2030,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mPostCollapseRunnables.clear(); } - public ViewPropertyAnimator setVisibilityWhenDone( - final ViewPropertyAnimator a, final View v, final int vis) { - a.setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - v.setVisibility(vis); - a.setListener(null); // oneshot - } - }); - return a; - } - - public Animator setVisibilityWhenDone( - final Animator a, final View v, final int vis) { - a.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - v.setVisibility(vis); - } - }); - return a; - } - - public Animator interpolator(TimeInterpolator ti, Animator a) { - a.setInterpolator(ti); - return a; - } - - public Animator startDelay(int d, Animator a) { - a.setStartDelay(d); - return a; - } - - public Animator start(Animator a) { - a.start(); - return a; - } - - final TimeInterpolator mAccelerateInterpolator = new AccelerateInterpolator(); - final TimeInterpolator mDecelerateInterpolator = new DecelerateInterpolator(); - final int FLIP_DURATION_OUT = 125; - final int FLIP_DURATION_IN = 225; - final int FLIP_DURATION = (FLIP_DURATION_IN + FLIP_DURATION_OUT); - Animator mScrollViewAnim, mClearButtonAnim; @Override @@ -2439,9 +2077,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mStatusBarView.collapseAllPanels(/*animate=*/ false); // reset things to their proper state - if (mScrollViewAnim != null) mScrollViewAnim.cancel(); - if (mClearButtonAnim != null) mClearButtonAnim.cancel(); - mStackScroller.setVisibility(View.VISIBLE); mNotificationPanel.setVisibility(View.GONE); @@ -2587,9 +2222,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, final boolean lightsOut = (vis & View.SYSTEM_UI_FLAG_LOW_PROFILE) != 0; if (lightsOut) { animateCollapsePanels(); - if (mTicking) { - haltTicker(); - } } setAreThereNotifications(); @@ -2634,6 +2266,14 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mSystemUiVisibility &= ~View.NAVIGATION_BAR_UNHIDE; } + if ((diff & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0 || sbModeChanged) { + boolean isTransparentBar = (mStatusBarMode == MODE_TRANSPARENT + || mStatusBarMode == MODE_LIGHTS_OUT_TRANSPARENT); + boolean allowLight = isTransparentBar && !mBatteryController.isPowerSave(); + boolean light = (vis & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0; + + mIconController.setIconsDark(allowLight && light); + } // restore the recents bit if (wasRecentsVisible) { mSystemUiVisibility |= View.RECENT_APPS_VISIBLE; @@ -2805,90 +2445,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, setNavigationIconHints(flags); } - @Override - protected void tick(StatusBarNotification n, boolean firstTime) { - if (!mTickerEnabled) return; - - // no ticking in lights-out mode - if (!areLightsOn()) return; - - // no ticking in Setup - if (!isDeviceProvisioned()) return; - - // not for you - if (!isNotificationForCurrentProfiles(n)) return; - - // Show the ticker if one is requested. Also don't do this - // until status bar window is attached to the window manager, - // because... well, what's the point otherwise? And trying to - // run a ticker without being attached will crash! - if (n.getNotification().tickerText != null && mStatusBarWindow != null - && mStatusBarWindow.getWindowToken() != null) { - if (0 == (mDisabled & (StatusBarManager.DISABLE_NOTIFICATION_ICONS - | StatusBarManager.DISABLE_NOTIFICATION_TICKER))) { - mTicker.addEntry(n); - } - } - } - - private class MyTicker extends Ticker { - MyTicker(Context context, View sb) { - super(context, sb); - if (!mTickerEnabled) { - Log.w(TAG, "MyTicker instantiated with mTickerEnabled=false", new Throwable()); - } - } - - @Override - public void tickerStarting() { - if (!mTickerEnabled) return; - mTicking = true; - mStatusBarContents.setVisibility(View.GONE); - mTickerView.setVisibility(View.VISIBLE); - mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.push_up_in, null)); - mStatusBarContents.startAnimation(loadAnim(com.android.internal.R.anim.push_up_out, null)); - } - - @Override - public void tickerDone() { - if (!mTickerEnabled) return; - mStatusBarContents.setVisibility(View.VISIBLE); - mTickerView.setVisibility(View.GONE); - mStatusBarContents.startAnimation(loadAnim(com.android.internal.R.anim.push_down_in, null)); - mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.push_down_out, - mTickingDoneListener)); - } - - public void tickerHalting() { - if (!mTickerEnabled) return; - if (mStatusBarContents.getVisibility() != View.VISIBLE) { - mStatusBarContents.setVisibility(View.VISIBLE); - mStatusBarContents - .startAnimation(loadAnim(com.android.internal.R.anim.fade_in, null)); - } - mTickerView.setVisibility(View.GONE); - // we do not animate the ticker away at this point, just get rid of it (b/6992707) - } - } - - Animation.AnimationListener mTickingDoneListener = new Animation.AnimationListener() {; - public void onAnimationEnd(Animation animation) { - mTicking = false; - } - public void onAnimationRepeat(Animation animation) { - } - public void onAnimationStart(Animation animation) { - } - }; - - private Animation loadAnim(int id, Animation.AnimationListener listener) { - Animation anim = AnimationUtils.loadAnimation(mContext, id); - if (listener != null) { - anim.setAnimationListener(listener); - } - return anim; - } - public static String viewInfo(View v) { return "[(" + v.getLeft() + "," + v.getTop() + ")(" + v.getRight() + "," + v.getBottom() + ") " + v.getWidth() + "x" + v.getHeight() + "]"; @@ -2899,11 +2455,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, pw.println("Current Status Bar state:"); pw.println(" mExpandedVisible=" + mExpandedVisible + ", mTrackingPosition=" + mTrackingPosition); - pw.println(" mTickerEnabled=" + mTickerEnabled); - if (mTickerEnabled) { - pw.println(" mTicking=" + mTicking); - pw.println(" mTickerView: " + viewInfo(mTickerView)); - } pw.println(" mTracking=" + mTracking); pw.println(" mDisplayMetrics=" + mDisplayMetrics); pw.println(" mStackScroller: " + viewInfo(mStackScroller)); @@ -2972,12 +2523,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mNotificationData.dump(pw, " "); } - int N = mStatusIcons.getChildCount(); - pw.println(" system icons: " + N); - for (int i=0; i<N; i++) { - StatusBarIconView ic = (StatusBarIconView) mStatusIcons.getChildAt(i); - pw.println(" [" + i + "] icon=" + ic); - } + mIconController.dump(pw); if (false) { pw.println("see the logcat for a dump of the views we have created."); @@ -3020,6 +2566,12 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, if (mSecurityController != null) { mSecurityController.dump(fd, pw, args); } + if (mHeadsUpNotificationView != null) { + mHeadsUpNotificationView.dump(fd, pw, args); + } else { + pw.println(" mHeadsUpNotificationView: null"); + } + pw.println("SharedPreferences:"); for (Map.Entry<String, ?> entry : mContext.getSharedPreferences(mContext.getPackageName(), Context.MODE_PRIVATE).getAll().entrySet()) { @@ -3049,25 +2601,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight()); } - static final float saturate(float a) { - return a < 0f ? 0f : (a > 1f ? 1f : a); - } - - @Override - public void updateExpandedViewPos(int thingy) { - if (SPEW) Log.v(TAG, "updateExpandedViewPos"); - - // on larger devices, the notification panel is propped open a bit - mNotificationPanel.setMinimumHeight( - (int)(mNotificationPanelMinHeightFrac * mCurrentDisplaySize.y)); - - FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mNotificationPanel.getLayoutParams(); - lp.gravity = mNotificationPanelGravity; - mNotificationPanel.setLayoutParams(lp); - - updateCarrierLabelVisibility(false); - } - // called by makeStatusbar and also by PhoneStatusBarView void updateDisplaySize() { mDisplay.getMetrics(mDisplayMetrics); @@ -3195,11 +2728,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, updateDisplaySize(); // populates mDisplayMetrics updateResources(); - updateClockSize(); repositionNavigationBar(); - updateExpandedViewPos(EXPANDED_LEAVE_ALONE); updateShowSearchHoldoff(); updateRowStates(); + mIconController.updateResources(); mScreenPinningRequest.onConfigurationChanged(); } @@ -3225,8 +2757,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mUserSetupObserver.onChange(false); mContext.getContentResolver().registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE), true, - mUserSetupObserver, - mCurrentUserId); + mUserSetupObserver, mCurrentUserId); } private void setHeadsUpVisibility(boolean vis) { @@ -3238,10 +2769,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mHeadsUpNotificationView.setVisibility(vis ? View.VISIBLE : View.GONE); } - public void onHeadsUpDismissed() { - mHeadsUpNotificationView.dismiss(); - } - /** * Reload some of our resources when the configuration changes. * @@ -3256,8 +2783,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } loadDimens(); - mLinearOutSlowIn = AnimationUtils.loadInterpolator( - mContext, android.R.interpolator.linear_out_slow_in); if (mNotificationPanel != null) { mNotificationPanel.updateResources(); @@ -3270,47 +2795,14 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } } - private void updateClockSize() { - if (mStatusBarView == null) return; - TextView clock = (TextView) mStatusBarView.findViewById(R.id.clock); - if (clock != null) { - FontSizeUtils.updateFontSize(clock, R.dimen.status_bar_clock_size); - } - } protected void loadDimens() { final Resources res = mContext.getResources(); mNaturalBarHeight = res.getDimensionPixelSize( com.android.internal.R.dimen.status_bar_height); - int newIconSize = res.getDimensionPixelSize( - com.android.internal.R.dimen.status_bar_icon_size); - int newIconHPadding = res.getDimensionPixelSize( - R.dimen.status_bar_icon_padding); - - if (newIconHPadding != mIconHPadding || newIconSize != mIconSize) { -// Log.d(TAG, "size=" + newIconSize + " padding=" + newIconHPadding); - mIconHPadding = newIconHPadding; - mIconSize = newIconSize; - //reloadAllNotificationIcons(); // reload the tray - } - mEdgeBorder = res.getDimensionPixelSize(R.dimen.status_bar_edge_ignore); - mNotificationPanelGravity = res.getInteger(R.integer.notification_panel_layout_gravity); - if (mNotificationPanelGravity <= 0) { - mNotificationPanelGravity = Gravity.START | Gravity.TOP; - } - - mCarrierLabelHeight = res.getDimensionPixelSize(R.dimen.carrier_label_height); - mStatusBarHeaderHeight = res.getDimensionPixelSize(R.dimen.status_bar_header_height); - - mNotificationPanelMinHeightFrac = res.getFraction(R.dimen.notification_panel_min_height_frac, 1, 1); - if (mNotificationPanelMinHeightFrac < 0f || mNotificationPanelMinHeightFrac > 1f) { - mNotificationPanelMinHeightFrac = 0f; - } - - mHeadsUpNotificationDecay = res.getInteger(R.integer.heads_up_notification_decay); mRowMinHeight = res.getDimensionPixelSize(R.dimen.notification_min_height); mRowMaxHeight = res.getDimensionPixelSize(R.dimen.notification_max_height); @@ -3443,14 +2935,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, }; @Override - protected void haltTicker() { - if (mTickerEnabled) { - mTicker.halt(); - } - } - - @Override - protected boolean shouldDisableNavbarGestures() { + public boolean shouldDisableNavbarGestures() { return !isDeviceProvisioned() || mExpandedVisible || (mDisabled & StatusBarManager.DISABLE_SEARCH) != 0; @@ -3486,7 +2971,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } @Override - public void setColorFilter(ColorFilter cf) { + public void setColorFilter(ColorFilter colorFilter) { } @Override @@ -3519,11 +3004,11 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mHandlerThread = null; } mContext.unregisterReceiver(mBroadcastReceiver); + mAssistGestureManager.destroy(); } private boolean mDemoModeAllowed; private boolean mDemoMode; - private DemoStatusIcons mDemoStatusIcons; @Override public void dispatchDemoCommand(String command, Bundle args) { @@ -3552,10 +3037,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, dispatchDemoCommandToView(command, args, R.id.battery); } if (modeChange || command.equals(COMMAND_STATUS)) { - if (mDemoStatusIcons == null) { - mDemoStatusIcons = new DemoStatusIcons(mStatusIcons, mIconSize); - } - mDemoStatusIcons.dispatchDemoCommand(command, args); + mIconController.dispatchDemoCommand(command, args); + } if (mNetworkController != null && (modeChange || command.equals(COMMAND_NETWORK))) { mNetworkController.dispatchDemoCommand(command, args); @@ -3625,7 +3108,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mLeaveOpenOnKeyguardHide = false; if (mDraggedDownRow != null) { mDraggedDownRow.setUserLocked(false); - mDraggedDownRow.notifyHeightChanged(); + mDraggedDownRow.notifyHeightChanged(false /* needsAnimation */); mDraggedDownRow = null; } } @@ -3675,6 +3158,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mLaunchTransitionFadingAway = false; } }); + mIconController.appTransitionStarting(SystemClock.uptimeMillis(), + StatusBarIconController.DEFAULT_TINT_ANIMATION_DURATION); } }; if (mNotificationPanel.isLaunchTransitionRunning()) { @@ -3742,16 +3227,31 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } /** + * Notifies the status bar that Keyguard is going away very soon. + */ + public void keyguardGoingAway() { + + // Treat Keyguard exit animation as an app transition to achieve nice transition for status + // bar. + mIconController.appTransitionPending(); + } + + /** * Notifies the status bar the Keyguard is fading away with the specified timings. * - * @param delay the animation delay in miliseconds + * @param startTime the start time of the animations in uptime millis + * @param delay the precalculated animation delay in miliseconds * @param fadeoutDuration the duration of the exit animation, in milliseconds */ - public void setKeyguardFadingAway(long delay, long fadeoutDuration) { + public void setKeyguardFadingAway(long startTime, long delay, long fadeoutDuration) { mKeyguardFadingAway = true; mKeyguardFadingAwayDelay = delay; mKeyguardFadingAwayDuration = fadeoutDuration; mWaitingForKeyguardExit = false; + mIconController.appTransitionStarting( + startTime + fadeoutDuration + - StatusBarIconController.DEFAULT_TINT_ANIMATION_DURATION, + StatusBarIconController.DEFAULT_TINT_ANIMATION_DURATION); disable(mDisabledUnmodified, true /* animate */); } @@ -3767,8 +3267,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } private void updatePublicMode() { - setLockscreenPublicMode(mStatusBarKeyguardViewManager.isShowing() - && mStatusBarKeyguardViewManager.isSecure(mCurrentUserId)); + setLockscreenPublicMode( + mStatusBarKeyguardViewManager.isShowing() && mStatusBarKeyguardViewManager + .isSecure(mCurrentUserId)); } private void updateKeyguardState(boolean goingToFullShade, boolean fromShadeLocked) { @@ -3792,7 +3293,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, updateStackScrollerState(goingToFullShade); updateNotifications(); checkBarModes(); - updateCarrierLabelVisibility(false); updateMediaMetaData(false); mKeyguardMonitor.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(), mStatusBarKeyguardViewManager.isSecure()); @@ -3913,6 +3413,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } } mState = state; + mGroupManager.setStatusBarState(state); mStatusBarWindowManager.setStatusBarState(state); } @@ -4046,13 +3547,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } } - /** - * @return a ViewGroup that spans the entire panel which contains the quick settings - */ - public ViewGroup getQuickSettingsOverlayParent() { - return mNotificationPanel; - } - public long getKeyguardFadingAwayDelay() { return mKeyguardFadingAwayDelay; } @@ -4061,14 +3555,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, return mKeyguardFadingAwayDuration; } - public LinearLayout getSystemIcons() { - return mSystemIcons; - } - - public LinearLayout getSystemIconArea() { - return mSystemIconArea; - } - @Override public void setBouncerShowing(boolean bouncerShowing) { super.setBouncerShowing(bouncerShowing); @@ -4211,6 +3697,31 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } } + @Override + public void appTransitionPending() { + + // Use own timings when Keyguard is going away, see keyguardGoingAway and + // setKeyguardFadingAway + if (!mKeyguardFadingAway) { + mIconController.appTransitionPending(); + } + } + + @Override + public void appTransitionCancelled() { + mIconController.appTransitionCancelled(); + } + + @Override + public void appTransitionStarting(long startTime, long duration) { + + // Use own timings when Keyguard is going away, see keyguardGoingAway and + // setKeyguardFadingAway + if (!mKeyguardFadingAway) { + mIconController.appTransitionStarting(startTime, duration); + } + } + private final class ShadeUpdates { private final ArraySet<String> mVisibleNotifications = new ArraySet<String>(); private final ArraySet<String> mNewVisibleNotifications = new ArraySet<String>(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java index 5c254a26..ac93ced 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java @@ -33,6 +33,7 @@ import android.util.Log; import com.android.internal.telephony.IccCardConstants; import com.android.internal.telephony.TelephonyIntents; import com.android.systemui.R; +import com.android.systemui.qs.tiles.DndTile; import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.CastController.CastDevice; import com.android.systemui.statusbar.policy.HotspotController; @@ -46,16 +47,12 @@ public class PhoneStatusBarPolicy { private static final String TAG = "PhoneStatusBarPolicy"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - private static final boolean SHOW_SYNC_ICON = false; - - private static final String SLOT_SYNC_ACTIVE = "sync_active"; private static final String SLOT_CAST = "cast"; private static final String SLOT_HOTSPOT = "hotspot"; private static final String SLOT_BLUETOOTH = "bluetooth"; private static final String SLOT_TTY = "tty"; private static final String SLOT_ZEN = "zen"; private static final String SLOT_VOLUME = "volume"; - private static final String SLOT_CDMA_ERI = "cdma_eri"; private static final String SLOT_ALARM_CLOCK = "alarm_clock"; private final Context mContext; @@ -83,9 +80,6 @@ public class PhoneStatusBarPolicy { if (action.equals(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)) { updateAlarm(); } - else if (action.equals(Intent.ACTION_SYNC_STATE_CHANGED)) { - updateSyncState(intent); - } else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED) || action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) { updateBluetooth(); @@ -115,7 +109,6 @@ public class PhoneStatusBarPolicy { // listen for broadcasts IntentFilter filter = new IntentFilter(); filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED); - filter.addAction(Intent.ACTION_SYNC_STATE_CHANGED); filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION); filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); @@ -129,10 +122,6 @@ public class PhoneStatusBarPolicy { mService.setIcon(SLOT_TTY, R.drawable.stat_sys_tty_mode, 0, null); mService.setIconVisibility(SLOT_TTY, false); - // Cdma Roaming Indicator, ERI - mService.setIcon(SLOT_CDMA_ERI, R.drawable.stat_sys_roaming_cdma_0, 0, null); - mService.setIconVisibility(SLOT_CDMA_ERI, false); - // bluetooth status updateBluetooth(); @@ -140,11 +129,6 @@ public class PhoneStatusBarPolicy { mService.setIcon(SLOT_ALARM_CLOCK, R.drawable.stat_sys_alarm, 0, null); mService.setIconVisibility(SLOT_ALARM_CLOCK, false); - // Sync state - mService.setIcon(SLOT_SYNC_ACTIVE, R.drawable.stat_sys_sync, 0, null); - mService.setIconVisibility(SLOT_SYNC_ACTIVE, false); - // "sync_failing" is obsolete: b/1297963 - // zen mService.setIcon(SLOT_ZEN, R.drawable.stat_sys_zen_important, 0, null); mService.setIconVisibility(SLOT_ZEN, false); @@ -172,16 +156,10 @@ public class PhoneStatusBarPolicy { private void updateAlarm() { AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); - boolean alarmSet = alarmManager.getNextAlarmClock(UserHandle.USER_CURRENT) != null; + boolean alarmSet = alarmManager.getNextAlarmClock(UserHandle.USER_CURRENT) != null; mService.setIconVisibility(SLOT_ALARM_CLOCK, alarmSet); } - private final void updateSyncState(Intent intent) { - if (!SHOW_SYNC_ICON) return; - boolean isActive = intent.getBooleanExtra("active", false); - mService.setIconVisibility(SLOT_SYNC_ACTIVE, isActive); - } - private final void updateSimState(Intent intent) { String stateExtra = intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE); if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(stateExtra)) { @@ -221,7 +199,11 @@ public class PhoneStatusBarPolicy { int volumeIconId = 0; String volumeDescription = null; - if (mZen == Global.ZEN_MODE_NO_INTERRUPTIONS) { + if (DndTile.isVisible(mContext) || DndTile.isCombinedIcon(mContext)) { + zenVisible = mZen != Global.ZEN_MODE_OFF; + zenIconId = R.drawable.stat_sys_dnd; + zenDescription = mContext.getString(R.string.quick_settings_dnd_label); + } else if (mZen == Global.ZEN_MODE_NO_INTERRUPTIONS) { zenVisible = true; zenIconId = R.drawable.stat_sys_zen_none; zenDescription = mContext.getString(R.string.zen_no_interruptions); @@ -231,7 +213,12 @@ public class PhoneStatusBarPolicy { zenDescription = mContext.getString(R.string.zen_important_interruptions); } - if (mZen != Global.ZEN_MODE_NO_INTERRUPTIONS && + if (DndTile.isVisible(mContext) + && audioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_SILENT) { + volumeVisible = true; + volumeIconId = R.drawable.stat_sys_ringer_silent; + volumeDescription = mContext.getString(R.string.accessibility_ringer_silent); + } else if (mZen != Global.ZEN_MODE_NO_INTERRUPTIONS && mZen != Global.ZEN_MODE_ALARMS && audioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_VIBRATE) { volumeVisible = true; volumeIconId = R.drawable.stat_sys_ringer_vibrate; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java index 7cbf13f..aa499ad 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -77,8 +77,8 @@ public class PhoneStatusBarView extends PanelBar { } @Override - public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) { - if (super.onRequestSendAccessibilityEvent(child, event)) { + public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) { + if (super.onRequestSendAccessibilityEventInternal(child, event)) { // The status bar is very small so augment the view that the user is touching // with the content of the status bar a whole. This way an accessibility service // may announce the current item as well as the entire content if appropriate. @@ -177,6 +177,5 @@ public class PhoneStatusBarView extends PanelBar { public void panelExpansionChanged(PanelView panel, float frac, boolean expanded) { super.panelExpansionChanged(panel, frac, expanded); mScrimController.setPanelExpansion(frac); - mBar.updateCarrierLabelVisibility(false); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java index 45a1386..954eb10 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java @@ -41,6 +41,7 @@ import com.android.systemui.qs.tiles.IntentTile; import com.android.systemui.qs.tiles.LocationTile; import com.android.systemui.qs.tiles.RotationLockTile; import com.android.systemui.qs.tiles.WifiTile; +import com.android.systemui.qs.tiles.DndTile; import com.android.systemui.settings.CurrentUserTracker; import com.android.systemui.statusbar.policy.BluetoothController; import com.android.systemui.statusbar.policy.CastController; @@ -256,6 +257,7 @@ public class QSTileHost implements QSTile.Host { else if (tileSpec.equals("inversion")) return new ColorInversionTile(this); else if (tileSpec.equals("cell")) return new CellularTile(this); else if (tileSpec.equals("airplane")) return new AirplaneModeTile(this); + else if (tileSpec.equals("dnd")) return new DndTile(this); else if (tileSpec.equals("rotation")) return new RotationLockTile(this); else if (tileSpec.equals("flashlight")) return new FlashlightTile(this); else if (tileSpec.equals("location")) return new LocationTile(this); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java new file mode 100644 index 0000000..45da297 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java @@ -0,0 +1,417 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.phone; + +import android.animation.ArgbEvaluator; +import android.animation.ValueAnimator; +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.Color; +import android.os.Bundle; +import android.os.Handler; +import android.os.SystemClock; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.internal.statusbar.StatusBarIcon; +import com.android.internal.util.NotificationColorUtil; +import com.android.systemui.BatteryMeterView; +import com.android.systemui.FontSizeUtils; +import com.android.systemui.R; +import com.android.systemui.statusbar.NotificationData; +import com.android.systemui.statusbar.SignalClusterView; +import com.android.systemui.statusbar.StatusBarIconView; + +import java.io.PrintWriter; +import java.util.ArrayList; + +/** + * Controls everything regarding the icons in the status bar and on Keyguard, including, but not + * limited to: notification icons, signal cluster, additional status icons, and clock in the status + * bar. + */ +public class StatusBarIconController { + + public static final long DEFAULT_TINT_ANIMATION_DURATION = 120; + + private Context mContext; + private PhoneStatusBar mPhoneStatusBar; + private Interpolator mLinearOutSlowIn; + private Interpolator mFastOutSlowIn; + private DemoStatusIcons mDemoStatusIcons; + private NotificationColorUtil mNotificationColorUtil; + + private LinearLayout mSystemIconArea; + private LinearLayout mStatusIcons; + private SignalClusterView mSignalCluster; + private LinearLayout mStatusIconsKeyguard; + private IconMerger mNotificationIcons; + private View mNotificationIconArea; + private ImageView mMoreIcon; + private BatteryMeterView mBatteryMeterView; + private TextView mClock; + + private int mIconSize; + private int mIconHPadding; + + private int mIconTint = Color.WHITE; + private float mDarkIntensity; + + private boolean mTransitionPending; + private boolean mTintChangePending; + private float mPendingDarkIntensity; + private ValueAnimator mTintAnimator; + + private int mDarkModeIconColorSingleTone; + private int mLightModeIconColorSingleTone; + + private final Handler mHandler; + private boolean mTransitionDeferring; + private long mTransitionDeferringStartTime; + private long mTransitionDeferringDuration; + + private final Runnable mTransitionDeferringDoneRunnable = new Runnable() { + @Override + public void run() { + mTransitionDeferring = false; + } + }; + + public StatusBarIconController(Context context, View statusBar, View keyguardStatusBar, + PhoneStatusBar phoneStatusBar) { + mContext = context; + mPhoneStatusBar = phoneStatusBar; + mNotificationColorUtil = NotificationColorUtil.getInstance(context); + mSystemIconArea = (LinearLayout) statusBar.findViewById(R.id.system_icon_area); + mStatusIcons = (LinearLayout) statusBar.findViewById(R.id.statusIcons); + mSignalCluster = (SignalClusterView) statusBar.findViewById(R.id.signal_cluster); + mNotificationIconArea = statusBar.findViewById(R.id.notification_icon_area_inner); + mNotificationIcons = (IconMerger) statusBar.findViewById(R.id.notificationIcons); + mMoreIcon = (ImageView) statusBar.findViewById(R.id.moreIcon); + mNotificationIcons.setOverflowIndicator(mMoreIcon); + mStatusIconsKeyguard = (LinearLayout) keyguardStatusBar.findViewById(R.id.statusIcons); + mBatteryMeterView = (BatteryMeterView) statusBar.findViewById(R.id.battery); + mClock = (TextView) statusBar.findViewById(R.id.clock); + mLinearOutSlowIn = AnimationUtils.loadInterpolator(mContext, + android.R.interpolator.linear_out_slow_in); + mFastOutSlowIn = AnimationUtils.loadInterpolator(mContext, + android.R.interpolator.fast_out_slow_in); + mDarkModeIconColorSingleTone = context.getColor(R.color.dark_mode_icon_color_single_tone); + mLightModeIconColorSingleTone = context.getColor(R.color.light_mode_icon_color_single_tone); + mHandler = new Handler(); + updateResources(); + } + + public void updateResources() { + mIconSize = mContext.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.status_bar_icon_size); + mIconHPadding = mContext.getResources().getDimensionPixelSize( + R.dimen.status_bar_icon_padding); + FontSizeUtils.updateFontSize(mClock, R.dimen.status_bar_clock_size); + } + + public void addSystemIcon(String slot, int index, int viewIndex, StatusBarIcon icon) { + StatusBarIconView view = new StatusBarIconView(mContext, slot, null); + view.set(icon); + mStatusIcons.addView(view, viewIndex, new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize)); + view = new StatusBarIconView(mContext, slot, null); + view.set(icon); + mStatusIconsKeyguard.addView(view, viewIndex, new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize)); + applyIconTint(); + } + + public void updateSystemIcon(String slot, int index, int viewIndex, + StatusBarIcon old, StatusBarIcon icon) { + StatusBarIconView view = (StatusBarIconView) mStatusIcons.getChildAt(viewIndex); + view.set(icon); + view = (StatusBarIconView) mStatusIconsKeyguard.getChildAt(viewIndex); + view.set(icon); + applyIconTint(); + } + + public void removeSystemIcon(String slot, int index, int viewIndex) { + mStatusIcons.removeViewAt(viewIndex); + mStatusIconsKeyguard.removeViewAt(viewIndex); + } + + public void updateNotificationIcons(NotificationData notificationData) { + final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( + mIconSize + 2*mIconHPadding, mPhoneStatusBar.getStatusBarHeight()); + + ArrayList<NotificationData.Entry> activeNotifications = + notificationData.getActiveNotifications(); + final int N = activeNotifications.size(); + ArrayList<StatusBarIconView> toShow = new ArrayList<>(N); + + // Filter out ambient notifications and notification children. + for (int i = 0; i < N; i++) { + NotificationData.Entry ent = activeNotifications.get(i); + if (notificationData.isAmbient(ent.key) + && !NotificationData.showNotificationEvenIfUnprovisioned(ent.notification)) { + continue; + } + if (!PhoneStatusBar.isTopLevelChild(ent)) { + continue; + } + toShow.add(ent.icon); + } + + ArrayList<View> toRemove = new ArrayList<>(); + for (int i=0; i<mNotificationIcons.getChildCount(); i++) { + View child = mNotificationIcons.getChildAt(i); + if (!toShow.contains(child)) { + toRemove.add(child); + } + } + + final int toRemoveCount = toRemove.size(); + for (int i = 0; i < toRemoveCount; i++) { + mNotificationIcons.removeView(toRemove.get(i)); + } + + for (int i=0; i<toShow.size(); i++) { + View v = toShow.get(i); + if (v.getParent() == null) { + mNotificationIcons.addView(v, i, params); + } + } + + // Resort notification icons + final int childCount = mNotificationIcons.getChildCount(); + for (int i = 0; i < childCount; i++) { + View actual = mNotificationIcons.getChildAt(i); + StatusBarIconView expected = toShow.get(i); + if (actual == expected) { + continue; + } + mNotificationIcons.removeView(expected); + mNotificationIcons.addView(expected, i); + } + + applyNotificationIconsTint(); + } + + public void hideSystemIconArea(boolean animate) { + animateHide(mSystemIconArea, animate); + } + + public void showSystemIconArea(boolean animate) { + animateShow(mSystemIconArea, animate); + } + + public void hideNotificationIconArea(boolean animate) { + animateHide(mNotificationIconArea, animate); + } + + public void showNotificationIconArea(boolean animate) { + animateShow(mNotificationIconArea, animate); + } + + public void setClockVisibility(boolean visible) { + mClock.setVisibility(visible ? View.VISIBLE : View.GONE); + } + + public void dump(PrintWriter pw) { + int N = mStatusIcons.getChildCount(); + pw.println(" system icons: " + N); + for (int i=0; i<N; i++) { + StatusBarIconView ic = (StatusBarIconView) mStatusIcons.getChildAt(i); + pw.println(" [" + i + "] icon=" + ic); + } + } + + public void dispatchDemoCommand(String command, Bundle args) { + if (mDemoStatusIcons == null) { + mDemoStatusIcons = new DemoStatusIcons(mStatusIcons, mIconSize); + } + mDemoStatusIcons.dispatchDemoCommand(command, args); + } + + /** + * Hides a view. + */ + private void animateHide(final View v, boolean animate) { + v.animate().cancel(); + if (!animate) { + v.setAlpha(0f); + v.setVisibility(View.INVISIBLE); + return; + } + v.animate() + .alpha(0f) + .setDuration(160) + .setStartDelay(0) + .setInterpolator(PhoneStatusBar.ALPHA_OUT) + .withEndAction(new Runnable() { + @Override + public void run() { + v.setVisibility(View.INVISIBLE); + } + }); + } + + /** + * Shows a view, and synchronizes the animation with Keyguard exit animations, if applicable. + */ + private void animateShow(View v, boolean animate) { + v.animate().cancel(); + v.setVisibility(View.VISIBLE); + if (!animate) { + v.setAlpha(1f); + return; + } + v.animate() + .alpha(1f) + .setDuration(320) + .setInterpolator(PhoneStatusBar.ALPHA_IN) + .setStartDelay(50) + + // We need to clean up any pending end action from animateHide if we call + // both hide and show in the same frame before the animation actually gets started. + // cancel() doesn't really remove the end action. + .withEndAction(null); + + // Synchronize the motion with the Keyguard fading if necessary. + if (mPhoneStatusBar.isKeyguardFadingAway()) { + v.animate() + .setDuration(mPhoneStatusBar.getKeyguardFadingAwayDuration()) + .setInterpolator(mLinearOutSlowIn) + .setStartDelay(mPhoneStatusBar.getKeyguardFadingAwayDelay()) + .start(); + } + } + + public void setIconsDark(boolean dark) { + if (mTransitionPending) { + deferIconTintChange(dark ? 1.0f : 0.0f); + } else if (mTransitionDeferring) { + animateIconTint(dark ? 1.0f : 0.0f, + Math.max(0, mTransitionDeferringStartTime - SystemClock.uptimeMillis()), + mTransitionDeferringDuration); + } else { + animateIconTint(dark ? 1.0f : 0.0f, 0 /* delay */, DEFAULT_TINT_ANIMATION_DURATION); + } + } + + private void animateIconTint(float targetDarkIntensity, long delay, + long duration) { + if (mTintAnimator != null) { + mTintAnimator.cancel(); + } + if (mDarkIntensity == targetDarkIntensity) { + return; + } + mTintAnimator = ValueAnimator.ofFloat(mDarkIntensity, targetDarkIntensity); + mTintAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + setIconTintInternal((Float) animation.getAnimatedValue()); + } + }); + mTintAnimator.setDuration(duration); + mTintAnimator.setStartDelay(delay); + mTintAnimator.setInterpolator(mFastOutSlowIn); + mTintAnimator.start(); + } + + private void setIconTintInternal(float darkIntensity) { + mDarkIntensity = darkIntensity; + mIconTint = (int) ArgbEvaluator.getInstance().evaluate(darkIntensity, + mLightModeIconColorSingleTone, mDarkModeIconColorSingleTone); + applyIconTint(); + } + + private void deferIconTintChange(float darkIntensity) { + if (mTintChangePending && darkIntensity == mPendingDarkIntensity) { + return; + } + mTintChangePending = true; + mPendingDarkIntensity = darkIntensity; + } + + private void applyIconTint() { + for (int i = 0; i < mStatusIcons.getChildCount(); i++) { + StatusBarIconView v = (StatusBarIconView) mStatusIcons.getChildAt(i); + v.setImageTintList(ColorStateList.valueOf(mIconTint)); + } + mSignalCluster.setIconTint(mIconTint, mDarkIntensity); + mMoreIcon.setImageTintList(ColorStateList.valueOf(mIconTint)); + mBatteryMeterView.setDarkIntensity(mDarkIntensity); + mClock.setTextColor(mIconTint); + applyNotificationIconsTint(); + } + + private void applyNotificationIconsTint() { + for (int i = 0; i < mNotificationIcons.getChildCount(); i++) { + StatusBarIconView v = (StatusBarIconView) mNotificationIcons.getChildAt(i); + boolean isPreL = Boolean.TRUE.equals(v.getTag(R.id.icon_is_pre_L)); + boolean colorize = !isPreL || isGrayscale(v); + if (colorize) { + v.setImageTintList(ColorStateList.valueOf(mIconTint)); + } + } + } + + private boolean isGrayscale(StatusBarIconView v) { + Object isGrayscale = v.getTag(R.id.icon_is_grayscale); + if (isGrayscale != null) { + return Boolean.TRUE.equals(isGrayscale); + } + boolean grayscale = mNotificationColorUtil.isGrayscaleIcon(v.getDrawable()); + v.setTag(R.id.icon_is_grayscale, grayscale); + return grayscale; + } + + public void appTransitionPending() { + mTransitionPending = true; + } + + public void appTransitionCancelled() { + if (mTransitionPending && mTintChangePending) { + mTintChangePending = false; + animateIconTint(mPendingDarkIntensity, 0 /* delay */, DEFAULT_TINT_ANIMATION_DURATION); + } + mTransitionPending = false; + } + + public void appTransitionStarting(long startTime, long duration) { + if (mTransitionPending && mTintChangePending) { + mTintChangePending = false; + animateIconTint(mPendingDarkIntensity, + Math.max(0, startTime - SystemClock.uptimeMillis()), + duration); + + } else if (mTransitionPending) { + + // If we don't have a pending tint change yet, the change might come in the future until + // startTime is reached. + mTransitionDeferring = true; + mTransitionDeferringStartTime = startTime; + mTransitionDeferringDuration = duration; + mHandler.removeCallbacks(mTransitionDeferringDoneRunnable); + mHandler.postAtTime(mTransitionDeferringDoneRunnable, startTime); + } + mTransitionPending = false; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 1724e70..6369d5e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -31,6 +31,7 @@ import com.android.internal.policy.IKeyguardShowCallback; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.ViewMediatorCallback; +import com.android.systemui.statusbar.CommandQueue; import static com.android.keyguard.KeyguardHostView.OnDismissAction; @@ -187,10 +188,6 @@ public class StatusBarKeyguardViewManager { mStatusBarWindowManager.setKeyguardNeedsInput(needsInput); } - public void updateUserActivityTimeout() { - mStatusBarWindowManager.setKeyguardUserActivityTimeout(mBouncer.getUserActivityTimeout()); - } - public void setOccluded(boolean occluded) { if (occluded && !mOccluded && mShowing) { if (mPhoneStatusBar.isInLaunchTransition()) { @@ -261,7 +258,7 @@ public class StatusBarKeyguardViewManager { } }); } else { - mPhoneStatusBar.setKeyguardFadingAway(delay, fadeoutDuration); + mPhoneStatusBar.setKeyguardFadingAway(startTime, delay, fadeoutDuration); boolean staying = mPhoneStatusBar.hideKeyguard(); if (!staying) { mStatusBarWindowManager.setKeyguardFadingAway(true); @@ -439,4 +436,12 @@ public class StatusBarKeyguardViewManager { public boolean isInputRestricted() { return mViewMediatorCallback.isInputRestricted(); } + + public void keyguardGoingAway() { + mPhoneStatusBar.keyguardGoingAway(); + } + + public void animateCollapsePanels() { + mPhoneStatusBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java index 0dbdca1..63bbf97 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java @@ -27,6 +27,7 @@ import android.view.ViewGroup; import android.view.WindowManager; import com.android.keyguard.R; +import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.statusbar.BaseStatusBar; import com.android.systemui.statusbar.StatusBarState; @@ -114,7 +115,8 @@ public class StatusBarWindowManager { private void applyFocusableFlag(State state) { if (state.isKeyguardShowingAndNotOccluded() && state.keyguardNeedsInput - && state.bouncerShowing) { + && state.bouncerShowing + || BaseStatusBar.ENABLE_REMOTE_INPUT && state.statusBarExpanded) { mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; } else if (state.isKeyguardShowingAndNotOccluded() || state.statusBarFocusable) { @@ -144,7 +146,7 @@ public class StatusBarWindowManager { if (state.isKeyguardShowingAndNotOccluded() && state.statusBarState == StatusBarState.KEYGUARD && !state.qsExpanded) { - mLpChanged.userActivityTimeout = state.keyguardUserActivityTimeout; + mLpChanged.userActivityTimeout = KeyguardViewMediator.AWAKE_INTERVAL_DEFAULT_MS; } else { mLpChanged.userActivityTimeout = -1; } @@ -201,11 +203,6 @@ public class StatusBarWindowManager { apply(mCurrentState); } - public void setKeyguardUserActivityTimeout(long timeout) { - mCurrentState.keyguardUserActivityTimeout = timeout; - apply(mCurrentState); - } - public void setBouncerShowing(boolean showing) { mCurrentState.bouncerShowing = showing; apply(mCurrentState); @@ -235,7 +232,6 @@ public class StatusBarWindowManager { boolean keyguardNeedsInput; boolean statusBarExpanded; boolean statusBarFocusable; - long keyguardUserActivityTimeout; boolean bouncerShowing; boolean keyguardFadingAway; boolean qsExpanded; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/Ticker.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/Ticker.java deleted file mode 100644 index a6ce288..0000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/Ticker.java +++ /dev/null @@ -1,305 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.phone; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.drawable.Drawable; -import android.os.Handler; -import android.service.notification.StatusBarNotification; -import android.text.Layout.Alignment; -import android.text.StaticLayout; -import android.text.TextPaint; -import android.view.View; -import android.view.animation.AnimationUtils; -import android.widget.ImageSwitcher; -import android.widget.TextSwitcher; -import android.widget.TextView; - -import com.android.internal.statusbar.StatusBarIcon; -import com.android.systemui.R; -import com.android.systemui.statusbar.StatusBarIconView; - -import java.util.ArrayList; - -public abstract class Ticker { - private static final int TICKER_SEGMENT_DELAY = 3000; - - private Context mContext; - private Handler mHandler = new Handler(); - private ArrayList<Segment> mSegments = new ArrayList(); - private TextPaint mPaint; - private View mTickerView; - private ImageSwitcher mIconSwitcher; - private TextSwitcher mTextSwitcher; - private float mIconScale; - - public static boolean isGraphicOrEmoji(char c) { - int gc = Character.getType(c); - return gc != Character.CONTROL - && gc != Character.FORMAT - && gc != Character.UNASSIGNED - && gc != Character.LINE_SEPARATOR - && gc != Character.PARAGRAPH_SEPARATOR - && gc != Character.SPACE_SEPARATOR; - } - - private final class Segment { - StatusBarNotification notification; - Drawable icon; - CharSequence text; - int current; - int next; - boolean first; - - StaticLayout getLayout(CharSequence substr) { - int w = mTextSwitcher.getWidth() - mTextSwitcher.getPaddingLeft() - - mTextSwitcher.getPaddingRight(); - return new StaticLayout(substr, mPaint, w, Alignment.ALIGN_NORMAL, 1, 0, true); - } - - CharSequence rtrim(CharSequence substr, int start, int end) { - while (end > start && !isGraphicOrEmoji(substr.charAt(end-1))) { - end--; - } - if (end > start) { - return substr.subSequence(start, end); - } - return null; - } - - /** returns null if there is no more text */ - CharSequence getText() { - if (this.current > this.text.length()) { - return null; - } - CharSequence substr = this.text.subSequence(this.current, this.text.length()); - StaticLayout l = getLayout(substr); - int lineCount = l.getLineCount(); - if (lineCount > 0) { - int start = l.getLineStart(0); - int end = l.getLineEnd(0); - this.next = this.current + end; - return rtrim(substr, start, end); - } else { - throw new RuntimeException("lineCount=" + lineCount + " current=" + current + - " text=" + text); - } - } - - /** returns null if there is no more text */ - CharSequence advance() { - this.first = false; - int index = this.next; - final int len = this.text.length(); - while (index < len && !isGraphicOrEmoji(this.text.charAt(index))) { - index++; - } - if (index >= len) { - return null; - } - - CharSequence substr = this.text.subSequence(index, this.text.length()); - StaticLayout l = getLayout(substr); - final int lineCount = l.getLineCount(); - int i; - for (i=0; i<lineCount; i++) { - int start = l.getLineStart(i); - int end = l.getLineEnd(i); - if (i == lineCount-1) { - this.next = len; - } else { - this.next = index + l.getLineStart(i+1); - } - CharSequence result = rtrim(substr, start, end); - if (result != null) { - this.current = index + start; - return result; - } - } - this.current = len; - return null; - } - - Segment(StatusBarNotification n, Drawable icon, CharSequence text) { - this.notification = n; - this.icon = icon; - this.text = text; - int index = 0; - final int len = text.length(); - while (index < len && !isGraphicOrEmoji(text.charAt(index))) { - index++; - } - this.current = index; - this.next = index; - this.first = true; - } - }; - - public Ticker(Context context, View sb) { - mContext = context; - final Resources res = context.getResources(); - final int outerBounds = res.getDimensionPixelSize(R.dimen.status_bar_icon_size); - final int imageBounds = res.getDimensionPixelSize(R.dimen.status_bar_icon_drawing_size); - mIconScale = (float)imageBounds / (float)outerBounds; - - mTickerView = sb.findViewById(R.id.ticker); - - mIconSwitcher = (ImageSwitcher)sb.findViewById(R.id.tickerIcon); - mIconSwitcher.setInAnimation( - AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_in)); - mIconSwitcher.setOutAnimation( - AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_out)); - mIconSwitcher.setScaleX(mIconScale); - mIconSwitcher.setScaleY(mIconScale); - - mTextSwitcher = (TextSwitcher)sb.findViewById(R.id.tickerText); - mTextSwitcher.setInAnimation( - AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_in)); - mTextSwitcher.setOutAnimation( - AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_out)); - - // Copy the paint style of one of the TextSwitchers children to use later for measuring - TextView text = (TextView)mTextSwitcher.getChildAt(0); - mPaint = text.getPaint(); - } - - - public void addEntry(StatusBarNotification n) { - int initialCount = mSegments.size(); - - // If what's being displayed has the same text and icon, just drop it - // (which will let the current one finish, this happens when apps do - // a notification storm). - if (initialCount > 0) { - final Segment seg = mSegments.get(0); - if (n.getPackageName().equals(seg.notification.getPackageName()) - && n.getNotification().icon == seg.notification.getNotification().icon - && n.getNotification().iconLevel == seg.notification.getNotification().iconLevel - && charSequencesEqual(seg.notification.getNotification().tickerText, - n.getNotification().tickerText)) { - return; - } - } - - final Drawable icon = StatusBarIconView.getIcon(mContext, - new StatusBarIcon(n.getPackageName(), n.getUser(), n.getNotification().icon, n.getNotification().iconLevel, 0, - n.getNotification().tickerText)); - final CharSequence text = n.getNotification().tickerText; - final Segment newSegment = new Segment(n, icon, text); - - // If there's already a notification schedule for this package and id, remove it. - for (int i=0; i<mSegments.size(); i++) { - Segment seg = mSegments.get(i); - if (n.getId() == seg.notification.getId() && n.getPackageName().equals(seg.notification.getPackageName())) { - // just update that one to use this new data instead - mSegments.remove(i--); // restart iteration here - } - } - - mSegments.add(newSegment); - - if (initialCount == 0 && mSegments.size() > 0) { - Segment seg = mSegments.get(0); - seg.first = false; - - mIconSwitcher.setAnimateFirstView(false); - mIconSwitcher.reset(); - mIconSwitcher.setImageDrawable(seg.icon); - - mTextSwitcher.setAnimateFirstView(false); - mTextSwitcher.reset(); - mTextSwitcher.setText(seg.getText()); - - tickerStarting(); - scheduleAdvance(); - } - } - - private static boolean charSequencesEqual(CharSequence a, CharSequence b) { - if (a.length() != b.length()) { - return false; - } - - int length = a.length(); - for (int i = 0; i < length; i++) { - if (a.charAt(i) != b.charAt(i)) { - return false; - } - } - return true; - } - - public void removeEntry(StatusBarNotification n) { - for (int i=mSegments.size()-1; i>=0; i--) { - Segment seg = mSegments.get(i); - if (n.getId() == seg.notification.getId() && n.getPackageName().equals(seg.notification.getPackageName())) { - mSegments.remove(i); - } - } - } - - public void halt() { - mHandler.removeCallbacks(mAdvanceTicker); - mSegments.clear(); - tickerHalting(); - } - - public void reflowText() { - if (mSegments.size() > 0) { - Segment seg = mSegments.get(0); - CharSequence text = seg.getText(); - mTextSwitcher.setCurrentText(text); - } - } - - private Runnable mAdvanceTicker = new Runnable() { - public void run() { - while (mSegments.size() > 0) { - Segment seg = mSegments.get(0); - - if (seg.first) { - // this makes the icon slide in for the first one for a given - // notification even if there are two notifications with the - // same icon in a row - mIconSwitcher.setImageDrawable(seg.icon); - } - CharSequence text = seg.advance(); - if (text == null) { - mSegments.remove(0); - continue; - } - mTextSwitcher.setText(text); - - scheduleAdvance(); - break; - } - if (mSegments.size() == 0) { - tickerDone(); - } - } - }; - - private void scheduleAdvance() { - mHandler.postDelayed(mAdvanceTicker, TICKER_SEGMENT_DELAY); - } - - public abstract void tickerStarting(); - public abstract void tickerDone(); - public abstract void tickerHalting(); -} - diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TickerView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/TickerView.java deleted file mode 100644 index bf13751..0000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TickerView.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.phone; - -import android.content.Context; -import android.util.AttributeSet; -import android.widget.TextSwitcher; - - -public class TickerView extends TextSwitcher -{ - Ticker mTicker; - - public TickerView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - if (mTicker != null) mTicker.reflowText(); - } - - public void setTicker(Ticker t) { - mTicker = t; - } -} - diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TrustDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/TrustDrawable.java index b89aa8f..56c1e10 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TrustDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/TrustDrawable.java @@ -120,7 +120,7 @@ public class TrustDrawable extends Drawable { } @Override - public void setColorFilter(ColorFilter cf) { + public void setColorFilter(ColorFilter colorFilter) { throw new UnsupportedOperationException("not implemented"); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java index 5ef345b..65cd268 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java @@ -125,7 +125,7 @@ public class UnlockMethodCache { } @Override - public void onFingerprintRecognized(int userId) { + public void onFingerprintAuthenticated(int userId) { update(false /* updateAlways */); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/VelocityTrackerFactory.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/VelocityTrackerFactory.java index 4f43b4d..e153b85 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/VelocityTrackerFactory.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/VelocityTrackerFactory.java @@ -20,8 +20,6 @@ import android.content.Context; import com.android.systemui.R; -import static android.util.Pools.SynchronizedPool; - /** * A class to generate {@link VelocityTrackerInterface}, depending on the configuration. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java index ad4c211..18983ff 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java @@ -17,35 +17,25 @@ package com.android.systemui.statusbar.policy; import android.app.ActivityManager; -import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; -import android.net.wifi.ScanResult; -import android.net.wifi.WifiConfiguration; -import android.net.wifi.WifiConfiguration.KeyMgmt; -import android.net.wifi.WifiInfo; -import android.net.wifi.WifiManager; import android.net.wifi.WifiManager.ActionListener; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; -import android.text.TextUtils; -import android.util.ArrayMap; -import android.util.ArraySet; import android.util.Log; +import com.android.settingslib.wifi.AccessPoint; +import com.android.settingslib.wifi.WifiTracker; +import com.android.settingslib.wifi.WifiTracker.WifiListener; import com.android.systemui.R; +import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; import java.util.List; - -// TODO: Unify this logic with platform settings (see WifiSettings and AccessPoint). There is a -// fair amount of complexity here in statuses and logic beyond just connected/disconnected. -public class AccessPointControllerImpl implements NetworkController.AccessPointController { +public class AccessPointControllerImpl + implements NetworkController.AccessPointController, WifiListener { private static final String TAG = "AccessPointController"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); @@ -63,25 +53,18 @@ public class AccessPointControllerImpl implements NetworkController.AccessPointC private final Context mContext; private final ArrayList<AccessPointCallback> mCallbacks = new ArrayList<AccessPointCallback>(); - private final WifiManager mWifiManager; + private final WifiTracker mWifiTracker; private final UserManager mUserManager; - private final Receiver mReceiver = new Receiver(); - private NetworkControllerImpl mNetworkController; - private boolean mScanning; private int mCurrentUser; public AccessPointControllerImpl(Context context) { mContext = context; - mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + mWifiTracker = new WifiTracker(context, this, false, true); mCurrentUser = ActivityManager.getCurrentUser(); } - void setNetworkController(NetworkControllerImpl networkController) { - mNetworkController = networkController; - } - public boolean canConfigWifi() { return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, new UserHandle(mCurrentUser)); @@ -96,7 +79,9 @@ public class AccessPointControllerImpl implements NetworkController.AccessPointC if (callback == null || mCallbacks.contains(callback)) return; if (DEBUG) Log.d(TAG, "addCallback " + callback); mCallbacks.add(callback); - mReceiver.setListening(!mCallbacks.isEmpty()); + if (mCallbacks.size() == 1) { + mWifiTracker.startTracking(); + } } @Override @@ -104,37 +89,40 @@ public class AccessPointControllerImpl implements NetworkController.AccessPointC if (callback == null) return; if (DEBUG) Log.d(TAG, "removeCallback " + callback); mCallbacks.remove(callback); - mReceiver.setListening(!mCallbacks.isEmpty()); + if (mCallbacks.isEmpty()) { + mWifiTracker.stopTracking(); + } } @Override public void scanForAccessPoints() { - if (mScanning) return; if (DEBUG) Log.d(TAG, "scan!"); - mScanning = mWifiManager.startScan(); - // Grab current networks immediately while we wait for scan. - updateAccessPoints(); + mWifiTracker.forceScan(); + } + + @Override + public int getIcon(AccessPoint ap) { + int level = ap.getLevel(); + return ICONS[level >= 0 ? level : 0]; } public boolean connect(AccessPoint ap) { if (ap == null) return false; - if (DEBUG) Log.d(TAG, "connect networkId=" + ap.networkId); - if (ap.networkId < 0) { + if (DEBUG) Log.d(TAG, "connect networkId=" + ap.getConfig().networkId); + if (ap.isSaved()) { + mWifiTracker.getManager().connect(ap.getConfig().networkId, mConnectListener); + } else { // Unknown network, need to add it. - if (ap.hasSecurity) { + if (ap.getSecurity() != AccessPoint.SECURITY_NONE) { Intent intent = new Intent(Settings.ACTION_WIFI_SETTINGS); - intent.putExtra(EXTRA_START_CONNECT_SSID, ap.ssid); + intent.putExtra(EXTRA_START_CONNECT_SSID, ap.getSsid()); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); fireSettingsIntentCallback(intent); return true; } else { - WifiConfiguration config = new WifiConfiguration(); - config.SSID = "\"" + ap.ssid + "\""; - config.allowedKeyManagement.set(KeyMgmt.NONE); - mWifiManager.connect(config, mConnectListener); + ap.generateOpenNetworkConfig(); + mWifiTracker.getManager().connect(ap.getConfig(), mConnectListener); } - } else { - mWifiManager.connect(ap.networkId, mConnectListener); } return false; } @@ -145,76 +133,28 @@ public class AccessPointControllerImpl implements NetworkController.AccessPointC } } - private void fireAcccessPointsCallback(AccessPoint[] aps) { + private void fireAcccessPointsCallback(List<AccessPoint> aps) { for (AccessPointCallback callback : mCallbacks) { callback.onAccessPointsChanged(aps); } } - private static String trimDoubleQuotes(String v) { - return v != null && v.length() >= 2 && v.charAt(0) == '\"' - && v.charAt(v.length() - 1) == '\"' ? v.substring(1, v.length() - 1) : v; + public void dump(PrintWriter pw) { + mWifiTracker.dump(pw); } - private int getConnectedNetworkId(WifiInfo wifiInfo) { - return wifiInfo != null ? wifiInfo.getNetworkId() : AccessPoint.NO_NETWORK; + @Override + public void onWifiStateChanged(int state) { } - private ArrayMap<String, WifiConfiguration> getConfiguredNetworksBySsid() { - final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks(); - if (configs == null || configs.size() == 0) return ArrayMap.EMPTY; - final ArrayMap<String, WifiConfiguration> rt = new ArrayMap<String, WifiConfiguration>(); - for (WifiConfiguration config : configs) { - rt.put(trimDoubleQuotes(config.SSID), config); - } - return rt; + @Override + public void onConnectedChanged() { + fireAcccessPointsCallback(mWifiTracker.getAccessPoints()); } - private void updateAccessPoints() { - final WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); - final int connectedNetworkId = getConnectedNetworkId(wifiInfo); - if (DEBUG) Log.d(TAG, "connectedNetworkId: " + connectedNetworkId); - final List<ScanResult> scanResults = mWifiManager.getScanResults(); - final ArrayMap<String, WifiConfiguration> configured = getConfiguredNetworksBySsid(); - if (DEBUG) Log.d(TAG, "scanResults: " + scanResults); - final List<AccessPoint> aps = new ArrayList<AccessPoint>(scanResults.size()); - final ArraySet<String> ssids = new ArraySet<String>(); - for (ScanResult scanResult : scanResults) { - if (scanResult == null) { - continue; - } - final String ssid = scanResult.SSID; - if (TextUtils.isEmpty(ssid) || ssids.contains(ssid)) continue; - ssids.add(ssid); - final WifiConfiguration config = configured.get(ssid); - final int level = WifiManager.calculateSignalLevel(scanResult.level, ICONS.length); - final AccessPoint ap = new AccessPoint(); - ap.isConfigured = config != null; - ap.networkId = config != null ? config.networkId : AccessPoint.NO_NETWORK; - ap.ssid = ssid; - // Connected if either: - // -The network ID in the active WifiInfo matches this network's ID. - // -The network is ephemeral (no configuration) but the SSID matches. - ap.isConnected = (ap.networkId != AccessPoint.NO_NETWORK - && ap.networkId == connectedNetworkId) || - (ap.networkId == WifiConfiguration.INVALID_NETWORK_ID && wifiInfo != null && - ap.ssid.equals(trimDoubleQuotes(wifiInfo.getSSID()))); - if (ap.isConnected && mNetworkController != null) { - // Ensure we have the connected network's RSSI. - ap.level = mNetworkController.getConnectedWifiLevel(); - } else { - ap.level = level; - } - ap.iconId = ICONS[ap.level]; - // Based on Settings AccessPoint#getSecurity, keep up to date - // with better methods of determining no security or not. - ap.hasSecurity = scanResult.capabilities.contains("WEP") - || scanResult.capabilities.contains("PSK") - || scanResult.capabilities.contains("EAP"); - aps.add(ap); - } - Collections.sort(aps, mByStrength); - fireAcccessPointsCallback(aps.toArray(new AccessPoint[aps.size()])); + @Override + public void onAccessPointsChanged() { + fireAcccessPointsCallback(mWifiTracker.getAccessPoints()); } private final ActionListener mConnectListener = new ActionListener() { @@ -228,49 +168,4 @@ public class AccessPointControllerImpl implements NetworkController.AccessPointC if (DEBUG) Log.d(TAG, "connect failure reason=" + reason); } }; - - private final Comparator<AccessPoint> mByStrength = new Comparator<AccessPoint> () { - @Override - public int compare(AccessPoint lhs, AccessPoint rhs) { - return -Integer.compare(score(lhs), score(rhs)); - } - - private int score(AccessPoint ap) { - return ap.level + (ap.isConnected ? 20 : 0) + (ap.isConfigured ? 10 : 0); - } - }; - - private final class Receiver extends BroadcastReceiver { - private boolean mRegistered; - - public void setListening(boolean listening) { - if (listening && !mRegistered) { - if (DEBUG) Log.d(TAG, "Registering receiver"); - final IntentFilter filter = new IntentFilter(); - filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); - filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); - filter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION); - filter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION); - filter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION); - filter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION); - filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); - filter.addAction(WifiManager.RSSI_CHANGED_ACTION); - mContext.registerReceiver(this, filter); - mRegistered = true; - } else if (!listening && mRegistered) { - if (DEBUG) Log.d(TAG, "Unregistering receiver"); - mContext.unregisterReceiver(this); - mRegistered = false; - } - } - - @Override - public void onReceive(Context context, Intent intent) { - if (DEBUG) Log.d(TAG, "onReceive " + intent.getAction()); - if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(intent.getAction())) { - updateAccessPoints(); - mScanning = false; - } - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityController.java index 89ed787..cc431dd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityController.java @@ -17,7 +17,6 @@ package com.android.systemui.statusbar.policy; import android.content.Context; -import android.util.Log; import android.view.accessibility.AccessibilityManager; import java.io.FileDescriptor; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java index 49693f5fe..cbe4c4d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java @@ -16,7 +16,9 @@ package com.android.systemui.statusbar.policy; -import java.util.Set; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; + +import java.util.Collection; public interface BluetoothController { void addStateChangedCallback(Callback callback); @@ -28,36 +30,12 @@ public interface BluetoothController { boolean isBluetoothConnecting(); String getLastDeviceName(); void setBluetoothEnabled(boolean enabled); - Set<PairedDevice> getPairedDevices(); - void connect(PairedDevice device); - void disconnect(PairedDevice device); + Collection<CachedBluetoothDevice> getDevices(); + void connect(CachedBluetoothDevice device); + void disconnect(CachedBluetoothDevice device); public interface Callback { void onBluetoothStateChange(boolean enabled, boolean connecting); - void onBluetoothPairedDevicesChanged(); - } - - public static final class PairedDevice implements Comparable<PairedDevice> { - public static int STATE_DISCONNECTED = 0; - public static int STATE_CONNECTING = 1; - public static int STATE_CONNECTED = 2; - public static int STATE_DISCONNECTING = 3; - - public String id; - public String name; - public int state = STATE_DISCONNECTED; - public Object tag; - - public static String stateToString(int state) { - if (state == STATE_DISCONNECTED) return "STATE_DISCONNECTED"; - if (state == STATE_CONNECTING) return "STATE_CONNECTING"; - if (state == STATE_CONNECTED) return "STATE_CONNECTED"; - if (state == STATE_DISCONNECTING) return "STATE_DISCONNECTING"; - return "UNKNOWN"; - } - - public int compareTo(PairedDevice another) { - return name.compareTo(another.name); - } + void onBluetoothDevicesChanged(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java index 894f82a..8d4f302 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java @@ -16,138 +16,57 @@ package com.android.systemui.statusbar.policy; -import static android.bluetooth.BluetoothAdapter.ERROR; -import static com.android.systemui.statusbar.policy.BluetoothUtil.connectionStateToString; -import static com.android.systemui.statusbar.policy.BluetoothUtil.deviceToString; -import static com.android.systemui.statusbar.policy.BluetoothUtil.profileToString; -import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidToProfile; -import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidToString; -import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidsToString; - -import android.bluetooth.BluetoothA2dp; -import android.bluetooth.BluetoothA2dpSink; import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothHeadset; -import android.bluetooth.BluetoothHeadsetClient; -import android.bluetooth.BluetoothInputDevice; -import android.bluetooth.BluetoothManager; -import android.bluetooth.BluetoothMap; -import android.bluetooth.BluetoothPan; -import android.bluetooth.BluetoothProfile; -import android.bluetooth.BluetoothProfile.ServiceListener; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.Handler; import android.os.Looper; -import android.os.Message; -import android.os.ParcelUuid; -import android.util.ArrayMap; import android.util.Log; -import android.util.SparseArray; -import com.android.systemui.statusbar.policy.BluetoothUtil.Profile; +import com.android.settingslib.bluetooth.BluetoothCallback; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.LocalBluetoothManager; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Set; -import java.util.TreeSet; +import java.util.Collection; -public class BluetoothControllerImpl implements BluetoothController { +public class BluetoothControllerImpl implements BluetoothController, BluetoothCallback, + CachedBluetoothDevice.Callback { private static final String TAG = "BluetoothController"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - // This controls the order in which we check the states. Since a device can only have - // one state on screen, but can have multiple profiles, the later states override the - // value of earlier states. So if a device has a profile in CONNECTING and one in - // CONNECTED, it will show as CONNECTED, theoretically this shouldn't really happen often, - // but seemed worth noting. - private static final int[] CONNECTION_STATES = { - BluetoothProfile.STATE_DISCONNECTED, - BluetoothProfile.STATE_DISCONNECTING, - BluetoothProfile.STATE_CONNECTING, - BluetoothProfile.STATE_CONNECTED, - }; - // Update all the BT device states. - private static final int MSG_UPDATE_CONNECTION_STATES = 1; - // Update just one BT device. - private static final int MSG_UPDATE_SINGLE_CONNECTION_STATE = 2; - // Update whether devices are bonded or not. - private static final int MSG_UPDATE_BONDED_DEVICES = 3; - - private static final int MSG_ADD_PROFILE = 4; - private static final int MSG_REM_PROFILE = 5; - - private final Context mContext; - private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>(); - private final BluetoothAdapter mAdapter; - private final Receiver mReceiver = new Receiver(); - private final ArrayMap<BluetoothDevice, DeviceInfo> mDeviceInfo = new ArrayMap<>(); - private final SparseArray<BluetoothProfile> mProfiles = new SparseArray<>(); - private final H mHandler; + private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>(); + private final LocalBluetoothManager mLocalBluetoothManager; private boolean mEnabled; private boolean mConnecting; - private BluetoothDevice mLastDevice; + private CachedBluetoothDevice mLastDevice; public BluetoothControllerImpl(Context context, Looper bgLooper) { - mContext = context; - mHandler = new H(bgLooper); - - final BluetoothManager bluetoothManager = - (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE); - mAdapter = bluetoothManager.getAdapter(); - if (mAdapter == null) { - Log.w(TAG, "Default BT adapter not found"); - return; + mLocalBluetoothManager = LocalBluetoothManager.getInstance(context, null); + if (mLocalBluetoothManager != null) { + mLocalBluetoothManager.getEventManager().registerCallback(this); + onBluetoothStateChanged( + mLocalBluetoothManager.getBluetoothAdapter().getBluetoothState()); } - - mReceiver.register(); - setAdapterState(mAdapter.getState()); - updateBondedDevices(); - bindAllProfiles(); } public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("BluetoothController state:"); - pw.print(" mAdapter="); pw.println(mAdapter); + pw.print(" mLocalBluetoothManager="); pw.println(mLocalBluetoothManager); pw.print(" mEnabled="); pw.println(mEnabled); pw.print(" mConnecting="); pw.println(mConnecting); pw.print(" mLastDevice="); pw.println(mLastDevice); pw.print(" mCallbacks.size="); pw.println(mCallbacks.size()); - pw.print(" mProfiles="); pw.println(profilesToString(mProfiles)); - pw.print(" mDeviceInfo.size="); pw.println(mDeviceInfo.size()); - for (int i = 0; i < mDeviceInfo.size(); i++) { - final BluetoothDevice device = mDeviceInfo.keyAt(i); - final DeviceInfo info = mDeviceInfo.valueAt(i); - pw.print(" "); pw.print(deviceToString(device)); - pw.print('('); pw.print(uuidsToString(device)); pw.print(')'); - pw.print(" "); pw.println(infoToString(info)); + pw.println(" Bluetooth Devices:"); + for (CachedBluetoothDevice device : + mLocalBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy()) { + pw.println(" " + getDeviceString(device)); } } - private static String infoToString(DeviceInfo info) { - return info == null ? null : ("connectionState=" + - connectionStateToString(CONNECTION_STATES[info.connectionStateIndex]) - + ",bonded=" + info.bonded + ",profiles=" - + profilesToString(info.connectedProfiles)); - } - - private static String profilesToString(SparseArray<?> profiles) { - final int N = profiles.size(); - final StringBuffer buffer = new StringBuffer(); - buffer.append('['); - for (int i = 0; i < N; i++) { - if (i != 0) { - buffer.append(','); - } - buffer.append(BluetoothUtil.profileToString(profiles.keyAt(i))); - } - buffer.append(']'); - return buffer.toString(); + private String getDeviceString(CachedBluetoothDevice device) { + return device.getName() + " " + device.getBondState() + " " + device.isConnected(); } public void addStateChangedCallback(Callback cb) { @@ -162,411 +81,126 @@ public class BluetoothControllerImpl implements BluetoothController { @Override public boolean isBluetoothEnabled() { - return mAdapter != null && mAdapter.isEnabled(); + return mEnabled; } @Override public boolean isBluetoothConnected() { - return mAdapter != null - && mAdapter.getConnectionState() == BluetoothAdapter.STATE_CONNECTED; + return mLocalBluetoothManager != null + && mLocalBluetoothManager.getBluetoothAdapter().getConnectionState() + == BluetoothAdapter.STATE_CONNECTED; } @Override public boolean isBluetoothConnecting() { - return mAdapter != null - && mAdapter.getConnectionState() == BluetoothAdapter.STATE_CONNECTING; + return mConnecting; } @Override public void setBluetoothEnabled(boolean enabled) { - if (mAdapter != null) { - if (enabled) { - mAdapter.enable(); - } else { - mAdapter.disable(); - } + if (mLocalBluetoothManager != null) { + mLocalBluetoothManager.getBluetoothAdapter().setBluetoothEnabled(enabled); } } @Override public boolean isBluetoothSupported() { - return mAdapter != null; + return mLocalBluetoothManager != null; } @Override - public Set<PairedDevice> getPairedDevices() { - final Set<PairedDevice> rt = new TreeSet<>(); - for (int i = 0; i < mDeviceInfo.size(); i++) { - final BluetoothDevice device = mDeviceInfo.keyAt(i); - final DeviceInfo info = mDeviceInfo.valueAt(i); - if (!info.bonded) continue; - final PairedDevice paired = new PairedDevice(); - paired.id = device.getAddress(); - paired.tag = device; - paired.name = device.getAliasName(); - paired.state = connectionStateToPairedDeviceState(info.connectionStateIndex); - rt.add(paired); - } - return rt; - } - - private static int connectionStateToPairedDeviceState(int index) { - int state = CONNECTION_STATES[index]; - if (state == BluetoothAdapter.STATE_CONNECTED) return PairedDevice.STATE_CONNECTED; - if (state == BluetoothAdapter.STATE_CONNECTING) return PairedDevice.STATE_CONNECTING; - if (state == BluetoothAdapter.STATE_DISCONNECTING) return PairedDevice.STATE_DISCONNECTING; - return PairedDevice.STATE_DISCONNECTED; + public void connect(final CachedBluetoothDevice device) { + if (mLocalBluetoothManager == null || device == null) return; + device.connect(true); } @Override - public void connect(final PairedDevice pd) { - connect(pd, true); - } - - @Override - public void disconnect(PairedDevice pd) { - connect(pd, false); - } - - private void connect(PairedDevice pd, final boolean connect) { - if (mAdapter == null || pd == null || pd.tag == null) return; - final BluetoothDevice device = (BluetoothDevice) pd.tag; - final DeviceInfo info = mDeviceInfo.get(device); - final String action = connect ? "connect" : "disconnect"; - if (DEBUG) Log.d(TAG, action + " " + deviceToString(device)); - final ParcelUuid[] uuids = device.getUuids(); - if (uuids == null) { - Log.w(TAG, "No uuids returned, aborting " + action + " for " + deviceToString(device)); - return; - } - SparseArray<Boolean> profiles = new SparseArray<>(); - if (connect) { - // When connecting add every profile we can recognize by uuid. - for (ParcelUuid uuid : uuids) { - final int profile = uuidToProfile(uuid); - if (profile == 0) { - Log.w(TAG, "Device " + deviceToString(device) + " has an unsupported uuid: " - + uuidToString(uuid)); - continue; - } - final boolean connected = info.connectedProfiles.get(profile, false); - if (!connected) { - profiles.put(profile, true); - } - } - } else { - // When disconnecting, just add every profile we know they are connected to. - profiles = info.connectedProfiles; - } - for (int i = 0; i < profiles.size(); i++) { - final int profile = profiles.keyAt(i); - if (mProfiles.indexOfKey(profile) >= 0) { - final Profile p = BluetoothUtil.getProfile(mProfiles.get(profile)); - final boolean ok = connect ? p.connect(device) : p.disconnect(device); - if (DEBUG) Log.d(TAG, action + " " + profileToString(profile) + " " - + (ok ? "succeeded" : "failed")); - } else { - Log.w(TAG, "Unable get get Profile for " + profileToString(profile)); - } - } + public void disconnect(CachedBluetoothDevice device) { + if (mLocalBluetoothManager == null || device == null) return; + device.disconnect(); } @Override public String getLastDeviceName() { - return mLastDevice != null ? mLastDevice.getAliasName() : null; + return mLastDevice != null ? mLastDevice.getName() : null; } - private void updateBondedDevices() { - mHandler.removeMessages(MSG_UPDATE_BONDED_DEVICES); - mHandler.sendEmptyMessage(MSG_UPDATE_BONDED_DEVICES); - } - - private void updateConnectionStates() { - mHandler.removeMessages(MSG_UPDATE_CONNECTION_STATES); - mHandler.removeMessages(MSG_UPDATE_SINGLE_CONNECTION_STATE); - mHandler.sendEmptyMessage(MSG_UPDATE_CONNECTION_STATES); + @Override + public Collection<CachedBluetoothDevice> getDevices() { + return mLocalBluetoothManager != null + ? mLocalBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy() + : null; } - private void updateConnectionState(BluetoothDevice device, int profile, int state) { - if (mHandler.hasMessages(MSG_UPDATE_CONNECTION_STATES)) { - // If we are about to update all the devices, then we don't need to update this one. - return; + private void firePairedDevicesChanged() { + for (Callback cb : mCallbacks) { + cb.onBluetoothDevicesChanged(); } - mHandler.obtainMessage(MSG_UPDATE_SINGLE_CONNECTION_STATE, profile, state, device) - .sendToTarget(); } - private void handleUpdateBondedDevices() { - if (mAdapter == null) return; - final Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices(); - for (DeviceInfo info : mDeviceInfo.values()) { - info.bonded = false; - } - int bondedCount = 0; - BluetoothDevice lastBonded = null; - if (bondedDevices != null) { - for (BluetoothDevice bondedDevice : bondedDevices) { - final boolean bonded = bondedDevice.getBondState() != BluetoothDevice.BOND_NONE; - updateInfo(bondedDevice).bonded = bonded; - if (bonded) { - bondedCount++; - lastBonded = bondedDevice; - } - } - } - if (mLastDevice == null && bondedCount == 1) { - mLastDevice = lastBonded; + private void fireStateChange() { + for (Callback cb : mCallbacks) { + fireStateChange(cb); } - updateConnectionStates(); - firePairedDevicesChanged(); } - private void handleUpdateConnectionStates() { - final int N = mDeviceInfo.size(); - for (int i = 0; i < N; i++) { - BluetoothDevice device = mDeviceInfo.keyAt(i); - DeviceInfo info = updateInfo(device); - info.connectionStateIndex = 0; - info.connectedProfiles.clear(); - for (int j = 0; j < mProfiles.size(); j++) { - int state = mProfiles.valueAt(j).getConnectionState(device); - handleUpdateConnectionState(device, mProfiles.keyAt(j), state); - } - } - handleConnectionChange(); - firePairedDevicesChanged(); + private void fireStateChange(Callback cb) { + cb.onBluetoothStateChange(mEnabled, mConnecting); } - private void handleUpdateConnectionState(BluetoothDevice device, int profile, int state) { - if (DEBUG) Log.d(TAG, "updateConnectionState " + BluetoothUtil.deviceToString(device) - + " " + BluetoothUtil.profileToString(profile) - + " " + BluetoothUtil.connectionStateToString(state)); - DeviceInfo info = updateInfo(device); - int stateIndex = 0; - for (int i = 0; i < CONNECTION_STATES.length; i++) { - if (CONNECTION_STATES[i] == state) { - stateIndex = i; - break; - } - } - info.profileStates.put(profile, stateIndex); - - info.connectionStateIndex = 0; - final int N = info.profileStates.size(); - for (int i = 0; i < N; i++) { - if (info.profileStates.valueAt(i) > info.connectionStateIndex) { - info.connectionStateIndex = info.profileStates.valueAt(i); - } - } - if (state == BluetoothProfile.STATE_CONNECTED) { - info.connectedProfiles.put(profile, true); - } else { - info.connectedProfiles.remove(profile); + private void updateConnected() { + if (mLastDevice != null && mLastDevice.isConnected()) { + // Our current device is still valid. + return; } - } - - private void handleConnectionChange() { - // If we are no longer connected to the current device, see if we are connected to - // something else, so we don't display a name we aren't connected to. - if (mLastDevice != null && - CONNECTION_STATES[mDeviceInfo.get(mLastDevice).connectionStateIndex] - != BluetoothProfile.STATE_CONNECTED) { - // Make sure we don't keep this device while it isn't connected. - mLastDevice = null; - // Look for anything else connected. - final int size = mDeviceInfo.size(); - for (int i = 0; i < size; i++) { - BluetoothDevice device = mDeviceInfo.keyAt(i); - DeviceInfo info = mDeviceInfo.valueAt(i); - if (CONNECTION_STATES[info.connectionStateIndex] - == BluetoothProfile.STATE_CONNECTED) { - mLastDevice = device; - break; - } + for (CachedBluetoothDevice device : getDevices()) { + if (device.isConnected()) { + mLastDevice = device; } } } - private void bindAllProfiles() { - // Note: This needs to contain all of the types that can be returned by BluetoothUtil - // otherwise we can't find the profiles we need when we connect/disconnect. - mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP); - mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP_SINK); - mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.AVRCP_CONTROLLER); - mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEADSET); - mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEADSET_CLIENT); - mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.INPUT_DEVICE); - mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.MAP); - mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.PAN); - // Note Health is not in this list because health devices aren't 'connected'. - // If profiles are expanded to use more than just connection state and connect/disconnect - // then it should be added. - } - - private void firePairedDevicesChanged() { - for (Callback cb : mCallbacks) { - cb.onBluetoothPairedDevicesChanged(); - } - } - - private void setAdapterState(int adapterState) { - final boolean enabled = adapterState == BluetoothAdapter.STATE_ON; - if (mEnabled == enabled) return; - mEnabled = enabled; - fireStateChange(); - } - - private void setConnecting(boolean connecting) { - if (mConnecting == connecting) return; - mConnecting = connecting; + @Override + public void onBluetoothStateChanged(int bluetoothState) { + mEnabled = bluetoothState == BluetoothAdapter.STATE_ON; fireStateChange(); } - private void fireStateChange() { - for (Callback cb : mCallbacks) { - fireStateChange(cb); - } + @Override + public void onScanningStateChanged(boolean started) { + // Don't care. } - private void fireStateChange(Callback cb) { - cb.onBluetoothStateChange(mEnabled, mConnecting); + @Override + public void onDeviceAdded(CachedBluetoothDevice cachedDevice) { + cachedDevice.registerCallback(this); + updateConnected(); + firePairedDevicesChanged(); } - private static int getProfileFromAction(String action) { - if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(action)) { - return BluetoothProfile.A2DP; - } else if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(action)) { - return BluetoothProfile.HEADSET; - } else if (BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED.equals(action)) { - return BluetoothProfile.A2DP_SINK; - } else if (BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) { - return BluetoothProfile.HEADSET_CLIENT; - } else if (BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED.equals(action)) { - return BluetoothProfile.INPUT_DEVICE; - } else if (BluetoothMap.ACTION_CONNECTION_STATE_CHANGED.equals(action)) { - return BluetoothProfile.MAP; - } else if (BluetoothPan.ACTION_CONNECTION_STATE_CHANGED.equals(action)) { - return BluetoothProfile.PAN; - } - if (DEBUG) Log.d(TAG, "Unknown action " + action); - return -1; + @Override + public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) { + updateConnected(); + firePairedDevicesChanged(); } - private final ServiceListener mProfileListener = new ServiceListener() { - @Override - public void onServiceDisconnected(int profile) { - if (DEBUG) Log.d(TAG, "Disconnected from " + BluetoothUtil.profileToString(profile)); - // We lost a profile, don't do any updates until it gets removed. - mHandler.removeMessages(MSG_UPDATE_CONNECTION_STATES); - mHandler.removeMessages(MSG_UPDATE_SINGLE_CONNECTION_STATE); - mHandler.obtainMessage(MSG_REM_PROFILE, profile, 0).sendToTarget(); - } - - @Override - public void onServiceConnected(int profile, BluetoothProfile proxy) { - if (DEBUG) Log.d(TAG, "Connected to " + BluetoothUtil.profileToString(profile)); - mHandler.obtainMessage(MSG_ADD_PROFILE, profile, 0, proxy).sendToTarget(); - } - }; - - private final class Receiver extends BroadcastReceiver { - public void register() { - final IntentFilter filter = new IntentFilter(); - filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); - filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED); - filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); - filter.addAction(BluetoothDevice.ACTION_ALIAS_CHANGED); - filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); - filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); - filter.addAction(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED); - filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED); - filter.addAction(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED); - filter.addAction(BluetoothMap.ACTION_CONNECTION_STATE_CHANGED); - filter.addAction(BluetoothPan.ACTION_CONNECTION_STATE_CHANGED); - mContext.registerReceiver(this, filter); - } - - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - - if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { - setAdapterState(intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, ERROR)); - updateBondedDevices(); - if (DEBUG) Log.d(TAG, "ACTION_STATE_CHANGED " + mEnabled); - } else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) { - updateInfo(device); - final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, - ERROR); - mLastDevice = device; - if (DEBUG) Log.d(TAG, "ACTION_CONNECTION_STATE_CHANGED " - + connectionStateToString(state) + " " + deviceToString(device)); - setConnecting(state == BluetoothAdapter.STATE_CONNECTING); - } else if (action.equals(BluetoothDevice.ACTION_ALIAS_CHANGED)) { - updateInfo(device); - mLastDevice = device; - } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) { - if (DEBUG) Log.d(TAG, "ACTION_BOND_STATE_CHANGED " + device); - updateBondedDevices(); - } else { - int profile = getProfileFromAction(intent.getAction()); - int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); - if (DEBUG) Log.d(TAG, "ACTION_CONNECTION_STATE_CHANGE " - + BluetoothUtil.profileToString(profile) - + " " + BluetoothUtil.connectionStateToString(state)); - if ((profile != -1) && (state != -1)) { - updateConnectionState(device, profile, state); - } - } - } + @Override + public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) { + updateConnected(); + firePairedDevicesChanged(); } - private DeviceInfo updateInfo(BluetoothDevice device) { - DeviceInfo info = mDeviceInfo.get(device); - info = info != null ? info : new DeviceInfo(); - mDeviceInfo.put(device, info); - return info; + @Override + public void onDeviceAttributesChanged() { + updateConnected(); + firePairedDevicesChanged(); } - private class H extends Handler { - public H(Looper l) { - super(l); - } - - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_UPDATE_CONNECTION_STATES: - handleUpdateConnectionStates(); - firePairedDevicesChanged(); - break; - case MSG_UPDATE_SINGLE_CONNECTION_STATE: - handleUpdateConnectionState((BluetoothDevice) msg.obj, msg.arg1, msg.arg2); - handleConnectionChange(); - firePairedDevicesChanged(); - break; - case MSG_UPDATE_BONDED_DEVICES: - handleUpdateBondedDevices(); - firePairedDevicesChanged(); - break; - case MSG_ADD_PROFILE: - mProfiles.put(msg.arg1, (BluetoothProfile) msg.obj); - handleUpdateConnectionStates(); - firePairedDevicesChanged(); - break; - case MSG_REM_PROFILE: - mProfiles.remove(msg.arg1); - handleUpdateConnectionStates(); - firePairedDevicesChanged(); - break; - } - }; - }; - - private static class DeviceInfo { - int connectionStateIndex = 0; - boolean bonded; // per getBondedDevices - SparseArray<Boolean> connectedProfiles = new SparseArray<>(); - SparseArray<Integer> profileStates = new SparseArray<>(); + @Override + public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) { + mConnecting = state == BluetoothAdapter.STATE_CONNECTING; + mLastDevice = cachedDevice; + updateConnected(); + fireStateChange(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothUtil.java deleted file mode 100644 index ed8ac2c..0000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothUtil.java +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.policy; - -import android.bluetooth.BluetoothA2dp; -import android.bluetooth.BluetoothA2dpSink; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothHeadset; -import android.bluetooth.BluetoothHeadsetClient; -import android.bluetooth.BluetoothInputDevice; -import android.bluetooth.BluetoothMap; -import android.bluetooth.BluetoothPan; -import android.bluetooth.BluetoothProfile; -import android.bluetooth.BluetoothUuid; -import android.os.ParcelUuid; -import android.text.TextUtils; - -public class BluetoothUtil { - - public static String profileToString(int profile) { - if (profile == BluetoothProfile.HEADSET) return "HEADSET"; - if (profile == BluetoothProfile.A2DP) return "A2DP"; - if (profile == BluetoothProfile.AVRCP_CONTROLLER) return "AVRCP_CONTROLLER"; - if (profile == BluetoothProfile.PAN) return "PAN"; - if (profile == BluetoothProfile.INPUT_DEVICE) return "INPUT_DEVICE"; - if (profile == BluetoothProfile.MAP) return "MAP"; - return "UNKNOWN(" + profile + ")"; - } - - public static String profileStateToString(int state) { - if (state == BluetoothProfile.STATE_CONNECTED) return "STATE_CONNECTED"; - if (state == BluetoothProfile.STATE_CONNECTING) return "STATE_CONNECTING"; - if (state == BluetoothProfile.STATE_DISCONNECTED) return "STATE_DISCONNECTED"; - if (state == BluetoothProfile.STATE_DISCONNECTING) return "STATE_DISCONNECTING"; - return "STATE_UNKNOWN"; - } - - public static String uuidToString(ParcelUuid uuid) { - if (BluetoothUuid.AudioSink.equals(uuid)) return "AudioSink"; - if (BluetoothUuid.AudioSource.equals(uuid)) return "AudioSource"; - if (BluetoothUuid.AdvAudioDist.equals(uuid)) return "AdvAudioDist"; - if (BluetoothUuid.HSP.equals(uuid)) return "HSP"; - if (BluetoothUuid.HSP_AG.equals(uuid)) return "HSP_AG"; - if (BluetoothUuid.Handsfree.equals(uuid)) return "Handsfree"; - if (BluetoothUuid.Handsfree_AG.equals(uuid)) return "Handsfree_AG"; - if (BluetoothUuid.AvrcpController.equals(uuid)) return "AvrcpController"; - if (BluetoothUuid.AvrcpTarget.equals(uuid)) return "AvrcpTarget"; - if (BluetoothUuid.ObexObjectPush.equals(uuid)) return "ObexObjectPush"; - if (BluetoothUuid.Hid.equals(uuid)) return "Hid"; - if (BluetoothUuid.Hogp.equals(uuid)) return "Hogp"; - if (BluetoothUuid.PANU.equals(uuid)) return "PANU"; - if (BluetoothUuid.NAP.equals(uuid)) return "NAP"; - if (BluetoothUuid.BNEP.equals(uuid)) return "BNEP"; - if (BluetoothUuid.PBAP_PSE.equals(uuid)) return "PBAP_PSE"; - if (BluetoothUuid.MAP.equals(uuid)) return "MAP"; - if (BluetoothUuid.MNS.equals(uuid)) return "MNS"; - if (BluetoothUuid.MAS.equals(uuid)) return "MAS"; - return uuid != null ? uuid.toString() : null; - } - - public static String connectionStateToString(int connectionState) { - if (connectionState == BluetoothAdapter.STATE_DISCONNECTED) return "STATE_DISCONNECTED"; - if (connectionState == BluetoothAdapter.STATE_CONNECTED) return "STATE_CONNECTED"; - if (connectionState == BluetoothAdapter.STATE_DISCONNECTING) return "STATE_DISCONNECTING"; - if (connectionState == BluetoothAdapter.STATE_CONNECTING) return "STATE_CONNECTING"; - return "ERROR"; - } - - public static String deviceToString(BluetoothDevice device) { - return device == null ? null : (device.getAddress() + '[' + device.getAliasName() + ']'); - } - - public static String uuidsToString(BluetoothDevice device) { - if (device == null) return null; - final ParcelUuid[] ids = device.getUuids(); - if (ids == null) return null; - final String[] tokens = new String[ids.length]; - for (int i = 0; i < tokens.length; i++) { - tokens[i] = uuidToString(ids[i]); - } - return TextUtils.join(",", tokens); - } - - public static int uuidToProfile(ParcelUuid uuid) { - if (BluetoothUuid.AudioSink.equals(uuid)) return BluetoothProfile.A2DP; - if (BluetoothUuid.AdvAudioDist.equals(uuid)) return BluetoothProfile.A2DP; - - if (BluetoothUuid.HSP.equals(uuid)) return BluetoothProfile.HEADSET; - if (BluetoothUuid.Handsfree.equals(uuid)) return BluetoothProfile.HEADSET; - - if (BluetoothUuid.MAP.equals(uuid)) return BluetoothProfile.MAP; - if (BluetoothUuid.MNS.equals(uuid)) return BluetoothProfile.MAP; - if (BluetoothUuid.MAS.equals(uuid)) return BluetoothProfile.MAP; - - if (BluetoothUuid.AvrcpController.equals(uuid)) return BluetoothProfile.AVRCP_CONTROLLER; - - if (BluetoothUuid.Hid.equals(uuid)) return BluetoothProfile.INPUT_DEVICE; - if (BluetoothUuid.Hogp.equals(uuid)) return BluetoothProfile.INPUT_DEVICE; - - if (BluetoothUuid.NAP.equals(uuid)) return BluetoothProfile.PAN; - - return 0; - } - - public static Profile getProfile(BluetoothProfile p) { - if (p instanceof BluetoothA2dp) return newProfile((BluetoothA2dp) p); - if (p instanceof BluetoothHeadset) return newProfile((BluetoothHeadset) p); - if (p instanceof BluetoothA2dpSink) return newProfile((BluetoothA2dpSink) p); - if (p instanceof BluetoothHeadsetClient) return newProfile((BluetoothHeadsetClient) p); - if (p instanceof BluetoothInputDevice) return newProfile((BluetoothInputDevice) p); - if (p instanceof BluetoothMap) return newProfile((BluetoothMap) p); - if (p instanceof BluetoothPan) return newProfile((BluetoothPan) p); - return null; - } - - private static Profile newProfile(final BluetoothA2dp a2dp) { - return new Profile() { - @Override - public boolean connect(BluetoothDevice device) { - return a2dp.connect(device); - } - - @Override - public boolean disconnect(BluetoothDevice device) { - return a2dp.disconnect(device); - } - }; - } - - private static Profile newProfile(final BluetoothHeadset headset) { - return new Profile() { - @Override - public boolean connect(BluetoothDevice device) { - return headset.connect(device); - } - - @Override - public boolean disconnect(BluetoothDevice device) { - return headset.disconnect(device); - } - }; - } - - private static Profile newProfile(final BluetoothA2dpSink sink) { - return new Profile() { - @Override - public boolean connect(BluetoothDevice device) { - return sink.connect(device); - } - - @Override - public boolean disconnect(BluetoothDevice device) { - return sink.disconnect(device); - } - }; - } - - private static Profile newProfile(final BluetoothHeadsetClient client) { - return new Profile() { - @Override - public boolean connect(BluetoothDevice device) { - return client.connect(device); - } - - @Override - public boolean disconnect(BluetoothDevice device) { - return client.disconnect(device); - } - }; - } - - private static Profile newProfile(final BluetoothInputDevice input) { - return new Profile() { - @Override - public boolean connect(BluetoothDevice device) { - return input.connect(device); - } - - @Override - public boolean disconnect(BluetoothDevice device) { - return input.disconnect(device); - } - }; - } - - private static Profile newProfile(final BluetoothMap map) { - return new Profile() { - @Override - public boolean connect(BluetoothDevice device) { - return map.connect(device); - } - - @Override - public boolean disconnect(BluetoothDevice device) { - return map.disconnect(device); - } - }; - } - - private static Profile newProfile(final BluetoothPan pan) { - return new Profile() { - @Override - public boolean connect(BluetoothDevice device) { - return pan.connect(device); - } - - @Override - public boolean disconnect(BluetoothDevice device) { - return pan.disconnect(device); - } - }; - } - - // common abstraction for supported profiles - public interface Profile { - boolean connect(BluetoothDevice device); - boolean disconnect(BluetoothDevice device); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java index 33f7aff..cd1914c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java @@ -17,21 +17,14 @@ package com.android.systemui.statusbar.policy; import android.content.Context; -import android.graphics.SurfaceTexture; import android.hardware.camera2.CameraAccessException; -import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraCharacteristics; -import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraManager; -import android.hardware.camera2.CameraMetadata; -import android.hardware.camera2.CaptureRequest; import android.os.Handler; import android.os.HandlerThread; import android.os.Process; -import android.os.SystemProperties; +import android.text.TextUtils; import android.util.Log; -import android.util.Size; -import android.view.Surface; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -45,7 +38,7 @@ public class FlashlightController { private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final int DISPATCH_ERROR = 0; - private static final int DISPATCH_OFF = 1; + private static final int DISPATCH_CHANGED = 1; private static final int DISPATCH_AVAILABILITY_CHANGED = 2; private final CameraManager mCameraManager; @@ -58,52 +51,50 @@ public class FlashlightController { /** Lock on {@code this} when accessing */ private boolean mFlashlightEnabled; - private String mCameraId; - private boolean mCameraAvailable; - private CameraDevice mCameraDevice; - private CaptureRequest mFlashlightRequest; - private CameraCaptureSession mSession; - private SurfaceTexture mSurfaceTexture; - private Surface mSurface; + private final String mCameraId; + private boolean mTorchAvailable; public FlashlightController(Context mContext) { mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE); - initialize(); - } - public void initialize() { + String cameraId = null; try { - mCameraId = getCameraId(); + cameraId = getCameraId(); } catch (Throwable e) { Log.e(TAG, "Couldn't initialize.", e); return; + } finally { + mCameraId = cameraId; } if (mCameraId != null) { ensureHandler(); - mCameraManager.registerAvailabilityCallback(mAvailabilityCallback, mHandler); + mCameraManager.registerTorchCallback(mTorchCallback, mHandler); } } - public synchronized void setFlashlight(boolean enabled) { - if (mFlashlightEnabled != enabled) { - mFlashlightEnabled = enabled; - postUpdateFlashlight(); - } - } - - public void killFlashlight() { - boolean enabled; + public void setFlashlight(boolean enabled) { + boolean pendingError = false; synchronized (this) { - enabled = mFlashlightEnabled; + if (mFlashlightEnabled != enabled) { + mFlashlightEnabled = enabled; + try { + mCameraManager.setTorchMode(mCameraId, enabled); + } catch (CameraAccessException e) { + Log.e(TAG, "Couldn't set torch mode", e); + mFlashlightEnabled = false; + pendingError = true; + } + } } - if (enabled) { - mHandler.post(mKillFlashlightRunnable); + dispatchModeChanged(mFlashlightEnabled); + if (pendingError) { + dispatchError(); } } public synchronized boolean isAvailable() { - return mCameraAvailable; + return mTorchAvailable; } public void addListener(FlashlightListener l) { @@ -127,42 +118,6 @@ public class FlashlightController { } } - private void startDevice() throws CameraAccessException { - mCameraManager.openCamera(getCameraId(), mCameraListener, mHandler); - } - - private void startSession() throws CameraAccessException { - mSurfaceTexture = new SurfaceTexture(false); - Size size = getSmallestSize(mCameraDevice.getId()); - mSurfaceTexture.setDefaultBufferSize(size.getWidth(), size.getHeight()); - mSurface = new Surface(mSurfaceTexture); - ArrayList<Surface> outputs = new ArrayList<>(1); - outputs.add(mSurface); - mCameraDevice.createCaptureSession(outputs, mSessionListener, mHandler); - } - - private Size getSmallestSize(String cameraId) throws CameraAccessException { - Size[] outputSizes = mCameraManager.getCameraCharacteristics(cameraId) - .get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) - .getOutputSizes(SurfaceTexture.class); - if (outputSizes == null || outputSizes.length == 0) { - throw new IllegalStateException( - "Camera " + cameraId + "doesn't support any outputSize."); - } - Size chosen = outputSizes[0]; - for (Size s : outputSizes) { - if (chosen.getWidth() >= s.getWidth() && chosen.getHeight() >= s.getHeight()) { - chosen = s; - } - } - return chosen; - } - - private void postUpdateFlashlight() { - ensureHandler(); - mHandler.post(mUpdateFlashlightRunnable); - } - private String getCameraId() throws CameraAccessException { String[] ids = mCameraManager.getCameraIdList(); for (String id : ids) { @@ -177,70 +132,12 @@ public class FlashlightController { return null; } - private void updateFlashlight(boolean forceDisable) { - try { - boolean enabled; - synchronized (this) { - enabled = mFlashlightEnabled && !forceDisable; - } - if (enabled) { - if (mCameraDevice == null) { - startDevice(); - return; - } - if (mSession == null) { - startSession(); - return; - } - if (mFlashlightRequest == null) { - CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest( - CameraDevice.TEMPLATE_PREVIEW); - builder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_TORCH); - builder.addTarget(mSurface); - CaptureRequest request = builder.build(); - mSession.capture(request, null, mHandler); - mFlashlightRequest = request; - } - } else { - if (mCameraDevice != null) { - mCameraDevice.close(); - teardown(); - } - } - - } catch (CameraAccessException|IllegalStateException|UnsupportedOperationException e) { - Log.e(TAG, "Error in updateFlashlight", e); - handleError(); - } - } - - private void teardown() { - mCameraDevice = null; - mSession = null; - mFlashlightRequest = null; - if (mSurface != null) { - mSurface.release(); - mSurfaceTexture.release(); - } - mSurface = null; - mSurfaceTexture = null; - } - - private void handleError() { - synchronized (this) { - mFlashlightEnabled = false; - } - dispatchError(); - dispatchOff(); - updateFlashlight(true /* forceDisable */); - } - - private void dispatchOff() { - dispatchListeners(DISPATCH_OFF, false /* argument (ignored) */); + private void dispatchModeChanged(boolean enabled) { + dispatchListeners(DISPATCH_CHANGED, enabled); } private void dispatchError() { - dispatchListeners(DISPATCH_ERROR, false /* argument (ignored) */); + dispatchListeners(DISPATCH_CHANGED, false /* argument (ignored) */); } private void dispatchAvailabilityChanged(boolean available) { @@ -256,8 +153,8 @@ public class FlashlightController { if (l != null) { if (message == DISPATCH_ERROR) { l.onFlashlightError(); - } else if (message == DISPATCH_OFF) { - l.onFlashlightOff(); + } else if (message == DISPATCH_CHANGED) { + l.onFlashlightChanged(argument); } else if (message == DISPATCH_AVAILABILITY_CHANGED) { l.onFlashlightAvailabilityChanged(argument); } @@ -280,106 +177,57 @@ public class FlashlightController { } } - private final CameraDevice.StateListener mCameraListener = new CameraDevice.StateListener() { - @Override - public void onOpened(CameraDevice camera) { - mCameraDevice = camera; - postUpdateFlashlight(); - } - - @Override - public void onDisconnected(CameraDevice camera) { - if (mCameraDevice == camera) { - dispatchOff(); - teardown(); - } - } - - @Override - public void onError(CameraDevice camera, int error) { - Log.e(TAG, "Camera error: camera=" + camera + " error=" + error); - if (camera == mCameraDevice || mCameraDevice == null) { - handleError(); - } - } - }; + private final CameraManager.TorchCallback mTorchCallback = + new CameraManager.TorchCallback() { - private final CameraCaptureSession.StateListener mSessionListener = - new CameraCaptureSession.StateListener() { @Override - public void onConfigured(CameraCaptureSession session) { - if (session.getDevice() == mCameraDevice) { - mSession = session; - } else { - session.close(); - } - postUpdateFlashlight(); - } - - @Override - public void onConfigureFailed(CameraCaptureSession session) { - Log.e(TAG, "Configure failed."); - if (mSession == null || mSession == session) { - handleError(); - } - } - }; - - private final Runnable mUpdateFlashlightRunnable = new Runnable() { - @Override - public void run() { - updateFlashlight(false /* forceDisable */); - } - }; - - private final Runnable mKillFlashlightRunnable = new Runnable() { - @Override - public void run() { - synchronized (this) { - mFlashlightEnabled = false; + public void onTorchModeUnavailable(String cameraId) { + if (TextUtils.equals(cameraId, mCameraId)) { + setCameraAvailable(false); } - updateFlashlight(true /* forceDisable */); - dispatchOff(); } - }; - private final CameraManager.AvailabilityCallback mAvailabilityCallback = - new CameraManager.AvailabilityCallback() { @Override - public void onCameraAvailable(String cameraId) { - if (DEBUG) Log.d(TAG, "onCameraAvailable(" + cameraId + ")"); - if (cameraId.equals(mCameraId)) { + public void onTorchModeChanged(String cameraId, boolean enabled) { + if (TextUtils.equals(cameraId, mCameraId)) { setCameraAvailable(true); - } - } - - @Override - public void onCameraUnavailable(String cameraId) { - if (DEBUG) Log.d(TAG, "onCameraUnavailable(" + cameraId + ")"); - if (cameraId.equals(mCameraId)) { - setCameraAvailable(false); + setTorchMode(enabled); } } private void setCameraAvailable(boolean available) { boolean changed; synchronized (FlashlightController.this) { - changed = mCameraAvailable != available; - mCameraAvailable = available; + changed = mTorchAvailable != available; + mTorchAvailable = available; } if (changed) { if (DEBUG) Log.d(TAG, "dispatchAvailabilityChanged(" + available + ")"); dispatchAvailabilityChanged(available); } } + + private void setTorchMode(boolean enabled) { + boolean changed; + synchronized (FlashlightController.this) { + changed = mFlashlightEnabled != enabled; + mFlashlightEnabled = enabled; + } + if (changed) { + if (DEBUG) Log.d(TAG, "dispatchModeChanged(" + enabled + ")"); + dispatchModeChanged(enabled); + } + } }; public interface FlashlightListener { /** - * Called when the flashlight turns off unexpectedly. + * Called when the flashlight was turned off or on. + * @param enabled true if the flashlight is currently turned on. */ - void onFlashlightOff(); + void onFlashlightChanged(boolean enabled); + /** * Called when there is an error that turns the flashlight off. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java index 2e3e67a..1e40bab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java @@ -36,6 +36,7 @@ import android.view.ViewTreeObserver; import android.view.accessibility.AccessibilityEvent; import android.widget.FrameLayout; +import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.ExpandHelper; import com.android.systemui.Gefingerpoken; import com.android.systemui.R; @@ -44,7 +45,8 @@ import com.android.systemui.statusbar.ExpandableView; import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.phone.PhoneStatusBar; -import java.util.ArrayList; +import java.io.FileDescriptor; +import java.io.PrintWriter; public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper.Callback, ExpandHelper.Callback, ViewTreeObserver.OnComputeInternalInsetsListener { @@ -56,6 +58,9 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper. Rect mTmpRect = new Rect(); int[] mTmpTwoArray = new int[2]; + private final int mHeadsUpNotificationDecay; + private final int mMinimumDisplayTime; + private final int mTouchSensitivityDelay; private final float mMaxAlpha = 1f; private final ArrayMap<String, Long> mSnoozedPackages; @@ -66,6 +71,7 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper. private PhoneStatusBar mBar; + private long mLingerUntilMs; private long mStartTouchTime; private ViewGroup mContentHolder; private int mSnoozeLengthMs; @@ -74,6 +80,14 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper. private NotificationData.Entry mHeadsUp; private int mUser; private String mMostRecentPackageName; + private boolean mTouched; + private Clock mClock; + + public static class Clock { + public long currentTimeMillis() { + return SystemClock.elapsedRealtime(); + } + } public HeadsUpNotificationView(Context context, AttributeSet attrs) { this(context, attrs, 0); @@ -87,6 +101,24 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper. mSnoozedPackages = new ArrayMap<>(); mDefaultSnoozeLengthMs = resources.getInteger(R.integer.heads_up_default_snooze_length_ms); mSnoozeLengthMs = mDefaultSnoozeLengthMs; + mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time); + mHeadsUpNotificationDecay = resources.getInteger(R.integer.heads_up_notification_decay); + mClock = new Clock(); + } + + @VisibleForTesting + public HeadsUpNotificationView(Context context, Clock clock, SwipeHelper swipeHelper, + EdgeSwipeHelper edgeSwipeHelper, int headsUpNotificationDecay, int minimumDisplayTime, + int touchSensitivityDelay, int snoozeLength) { + super(context, null); + mClock = clock; + mSwipeHelper = swipeHelper; + mEdgeSwipeHelper = edgeSwipeHelper; + mMinimumDisplayTime = minimumDisplayTime; + mHeadsUpNotificationDecay = headsUpNotificationDecay; + mTouchSensitivityDelay = touchSensitivityDelay; + mSnoozedPackages = new ArrayMap<>(); + mDefaultSnoozeLengthMs = snoozeLength; } public void updateResources() { @@ -102,88 +134,147 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper. mBar = bar; } + public PhoneStatusBar getBar() { + return mBar; + } + public ViewGroup getHolder() { return mContentHolder; } - public boolean showNotification(NotificationData.Entry headsUp) { - if (mHeadsUp != null && headsUp != null && !mHeadsUp.key.equals(headsUp.key)) { + /** + * Called when posting a new notification to the heads up. + */ + public void showNotification(NotificationData.Entry headsUp) { + if (DEBUG) Log.v(TAG, "showNotification"); + if (mHeadsUp != null) { // bump any previous heads up back to the shade - release(); + releaseImmediately(); + } + mTouched = false; + updateNotification(headsUp, true); + mLingerUntilMs = mClock.currentTimeMillis() + mMinimumDisplayTime; + } + + /** + * Called when updating or posting a notification to the heads up. + */ + public void updateNotification(NotificationData.Entry headsUp, boolean alert) { + if (DEBUG) Log.v(TAG, "updateNotification"); + + if (mHeadsUp == headsUp) { + resetViewForHeadsup(); + // This is an in-place update. Noting more to do. + return; } mHeadsUp = headsUp; + if (mContentHolder != null) { mContentHolder.removeAllViews(); } if (mHeadsUp != null) { mMostRecentPackageName = mHeadsUp.notification.getPackageName(); - mHeadsUp.row.setSystemExpanded(true); - mHeadsUp.row.setSensitive(false); - mHeadsUp.row.setHeadsUp(true); - mHeadsUp.row.setHideSensitive( - false, false /* animated */, 0 /* delay */, 0 /* duration */); - if (mContentHolder == null) { - // too soon! - return false; + if (mHeadsUp.row != null) { + resetViewForHeadsup(); } - mContentHolder.setX(0); - mContentHolder.setVisibility(View.VISIBLE); - mContentHolder.setAlpha(mMaxAlpha); - mContentHolder.addView(mHeadsUp.row); - sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); - mSwipeHelper.snapChild(mContentHolder, 1f); mStartTouchTime = SystemClock.elapsedRealtime() + mTouchSensitivityDelay; + if (mContentHolder != null) { // only null in tests and before we are attached to a window + mContentHolder.setX(0); + mContentHolder.setVisibility(View.VISIBLE); + mContentHolder.setAlpha(mMaxAlpha); + mContentHolder.addView(mHeadsUp.row); + sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); + + mSwipeHelper.snapChild(mContentHolder, 1f); + } mHeadsUp.setInterruption(); - - // 2. Animate mHeadsUpNotificationView in + } + if (alert) { + // Make sure the heads up window is open. mBar.scheduleHeadsUpOpen(); - - // 3. Set alarm to age the notification off - mBar.resetHeadsUpDecayTimer(); + mBar.scheduleHeadsUpDecay(mHeadsUpNotificationDecay); } - return true; } - @Override - protected void onVisibilityChanged(View changedView, int visibility) { - super.onVisibilityChanged(changedView, visibility); - if (changedView.getVisibility() == VISIBLE) { - sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); + private void resetViewForHeadsup() { + if (mHeadsUp.row.areChildrenExpanded()) { + mHeadsUp.row.setChildrenExpanded(false /* expanded */, false /* animated */); + } + mHeadsUp.row.setSystemExpanded(true); + mHeadsUp.row.setSensitive(false); + mHeadsUp.row.setHeadsUp(true); + mHeadsUp.row.setTranslationY(0); + mHeadsUp.row.setTranslationZ(0); + mHeadsUp.row.setHideSensitive( + false, false /* animated */, 0 /* delay */, 0 /* duration */); + } + + /** + * Possibly enter the lingering state by delaying the closing of the window. + * + * @return true if the notification has entered the lingering state. + */ + private boolean startLingering(boolean removed) { + final long now = mClock.currentTimeMillis(); + if (!mTouched && mHeadsUp != null && now < mLingerUntilMs) { + if (removed) { + mHeadsUp = null; + } + mBar.scheduleHeadsUpDecay(mLingerUntilMs - now); + return true; } + return false; } - public boolean isShowing(String key) { - return mHeadsUp != null && mHeadsUp.key.equals(key); + /** + * React to the removal of the notification in the heads up. + */ + public void removeNotification(String key) { + if (DEBUG) Log.v(TAG, "remove"); + if (mHeadsUp == null || !mHeadsUp.key.equals(key)) { + return; + } + if (!startLingering(/* removed */ true)) { + mHeadsUp = null; + releaseImmediately(); + } } - /** Discard the Heads Up notification. */ - public void clear() { - mHeadsUp = null; - mBar.scheduleHeadsUpClose(); + /** + * Ask for any current Heads Up notification to be pushed down into the shade. + */ + public void release() { + if (DEBUG) Log.v(TAG, "release"); + if (!startLingering(/* removed */ false)) { + releaseImmediately(); + } } - /** Respond to dismissal of the Heads Up window. */ - public void dismiss() { - if (mHeadsUp == null) return; - if (mHeadsUp.notification.isClearable()) { - mBar.onNotificationClear(mHeadsUp.notification); - } else { - release(); + /** + * Push any current Heads Up notification down into the shade. + */ + public void releaseImmediately() { + if (DEBUG) Log.v(TAG, "releaseImmediately"); + if (mHeadsUp != null) { + mContentHolder.removeView(mHeadsUp.row); + mBar.displayNotificationFromHeadsUp(mHeadsUp); } mHeadsUp = null; mBar.scheduleHeadsUpClose(); } - /** Push any current Heads Up notification down into the shade. */ - public void release() { - if (mHeadsUp != null) { - mBar.displayNotificationFromHeadsUp(mHeadsUp.notification); + @Override + protected void onVisibilityChanged(View changedView, int visibility) { + super.onVisibilityChanged(changedView, visibility); + if (DEBUG) Log.v(TAG, "onVisibilityChanged: " + visibility); + if (changedView.getVisibility() == VISIBLE) { + mStartTouchTime = mClock.currentTimeMillis() + mTouchSensitivityDelay; + sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); } - mHeadsUp = null; } public boolean isSnoozed(String packageName) { @@ -204,16 +295,15 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper. mSnoozedPackages.put(snoozeKey(mMostRecentPackageName, mUser), SystemClock.elapsedRealtime() + mSnoozeLengthMs); } - releaseAndClose(); + releaseImmediately(); } private static String snoozeKey(String packageName, int user) { return user + "," + packageName; } - public void releaseAndClose() { - release(); - mBar.scheduleHeadsUpClose(); + public boolean isShowing(String key) { + return mHeadsUp != null && mHeadsUp.key.equals(key); } public NotificationData.Entry getEntry() { @@ -226,19 +316,19 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper. // ViewGroup methods - private static final ViewOutlineProvider CONTENT_HOLDER_OUTLINE_PROVIDER = - new ViewOutlineProvider() { - @Override - public void getOutline(View view, Outline outline) { - int outlineLeft = view.getPaddingLeft(); - int outlineTop = view.getPaddingTop(); - - // Apply padding to shadow. - outline.setRect(outlineLeft, outlineTop, - view.getWidth() - outlineLeft - view.getPaddingRight(), - view.getHeight() - outlineTop - view.getPaddingBottom()); - } - }; +private static final ViewOutlineProvider CONTENT_HOLDER_OUTLINE_PROVIDER = + new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + int outlineLeft = view.getPaddingLeft(); + int outlineTop = view.getPaddingTop(); + + // Apply padding to shadow. + outline.setRect(outlineLeft, outlineTop, + view.getWidth() - outlineLeft - view.getPaddingRight(), + view.getHeight() - outlineTop - view.getPaddingBottom()); + } + }; @Override public void onAttachedToWindow() { @@ -246,7 +336,7 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper. float touchSlop = viewConfiguration.getScaledTouchSlop(); mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, getContext()); mSwipeHelper.setMaxSwipeProgress(mMaxAlpha); - mEdgeSwipeHelper = new EdgeSwipeHelper(touchSlop); + mEdgeSwipeHelper = new EdgeSwipeHelper(this, touchSlop); int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height); int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_max_height); @@ -280,6 +370,7 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper. getViewTreeObserver().addOnComputeInternalInsetsListener(this); } + @Override protected void onDetachedFromWindow() { mContext.getContentResolver().unregisterContentObserver(mSettingsObserver); @@ -288,11 +379,13 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper. @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (DEBUG) Log.v(TAG, "onInterceptTouchEvent()"); - if (SystemClock.elapsedRealtime() < mStartTouchTime) { + if (mClock.currentTimeMillis() < mStartTouchTime) { return true; } + mTouched = true; return mEdgeSwipeHelper.onInterceptTouchEvent(ev) || mSwipeHelper.onInterceptTouchEvent(ev) + || mHeadsUp == null // lingering || super.onInterceptTouchEvent(ev); } @@ -314,12 +407,17 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper. @Override public boolean onTouchEvent(MotionEvent ev) { - if (SystemClock.elapsedRealtime() < mStartTouchTime) { + if (mClock.currentTimeMillis() < mStartTouchTime) { return false; } - mBar.resetHeadsUpDecayTimer(); + + final boolean wasRemoved = mHeadsUp == null; + if (!wasRemoved) { + mBar.scheduleHeadsUpDecay(mHeadsUpNotificationDecay); + } return mEdgeSwipeHelper.onTouchEvent(ev) || mSwipeHelper.onTouchEvent(ev) + || wasRemoved || super.onTouchEvent(ev); } @@ -388,7 +486,11 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper. @Override public void onChildDismissed(View v) { Log.v(TAG, "User swiped heads up to dismiss"); - mBar.onHeadsUpDismissed(); + if (mHeadsUp != null && mHeadsUp.notification.isClearable()) { + mBar.onNotificationClear(mHeadsUp.notification); + mHeadsUp = null; + } + releaseImmediately(); } @Override @@ -442,14 +544,39 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper. mUser = user; } - private class EdgeSwipeHelper implements Gefingerpoken { + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("HeadsUpNotificationView state:"); + pw.print(" mTouchSensitivityDelay="); pw.println(mTouchSensitivityDelay); + pw.print(" mSnoozeLengthMs="); pw.println(mSnoozeLengthMs); + pw.print(" mLingerUntilMs="); pw.println(mLingerUntilMs); + pw.print(" mTouched="); pw.println(mTouched); + pw.print(" mMostRecentPackageName="); pw.println(mMostRecentPackageName); + pw.print(" mStartTouchTime="); pw.println(mStartTouchTime); + pw.print(" now="); pw.println(SystemClock.elapsedRealtime()); + pw.print(" mUser="); pw.println(mUser); + if (mHeadsUp == null) { + pw.println(" mHeadsUp=null"); + } else { + pw.print(" mHeadsUp="); pw.println(mHeadsUp.notification.getKey()); + } + int N = mSnoozedPackages.size(); + pw.println(" snoozed packages: " + N); + for (int i = 0; i < N; i++) { + pw.print(" "); pw.print(mSnoozedPackages.valueAt(i)); + pw.print(", "); pw.println(mSnoozedPackages.keyAt(i)); + } + } + + public static class EdgeSwipeHelper implements Gefingerpoken { private static final boolean DEBUG_EDGE_SWIPE = false; private final float mTouchSlop; + private final HeadsUpNotificationView mHeadsUpView; private boolean mConsuming; private float mFirstY; private float mFirstX; - public EdgeSwipeHelper(float touchSlop) { + public EdgeSwipeHelper(HeadsUpNotificationView headsUpView, float touchSlop) { + mHeadsUpView = headsUpView; mTouchSlop = touchSlop; } @@ -469,10 +596,10 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper. final float daX = Math.abs(ev.getX() - mFirstX); final float daY = Math.abs(dY); if (!mConsuming && daX < daY && daY > mTouchSlop) { - snooze(); + mHeadsUpView.snooze(); if (dY > 0) { if (DEBUG_EDGE_SWIPE) Log.d(TAG, "found an open"); - mBar.animateExpandNotificationsPanel(); + mHeadsUpView.getBar().animateExpandNotificationsPanel(); } mConsuming = true; } @@ -480,7 +607,7 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper. case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: - if (DEBUG_EDGE_SWIPE) Log.d(TAG, "action done" ); + if (DEBUG_EDGE_SWIPE) Log.d(TAG, "action done"); mConsuming = false; break; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java index 0863c86..7ca91a5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java @@ -22,7 +22,6 @@ public interface HotspotController { boolean isHotspotEnabled(); boolean isHotspotSupported(); void setHotspotEnabled(boolean enabled); - boolean isProvisioningNeeded(); public interface Callback { void onHotspotChanged(boolean enabled); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java index 5eff5a6..4bfd528 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java @@ -16,45 +16,38 @@ package com.android.systemui.statusbar.policy; -import android.app.ActivityManager; import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.net.ConnectivityManager; import android.net.wifi.WifiManager; -import android.os.SystemProperties; import android.os.UserHandle; -import android.provider.Settings; import android.util.Log; +import com.android.settingslib.TetherUtil; + import java.util.ArrayList; public class HotspotControllerImpl implements HotspotController { private static final String TAG = "HotspotController"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - // Keep these in sync with Settings TetherService.java - public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType"; - public static final String EXTRA_SET_ALARM = "extraSetAlarm"; - public static final String EXTRA_RUN_PROVISION = "extraRunProvision"; - public static final String EXTRA_ENABLE_WIFI_TETHER = "extraEnableWifiTether"; - // Keep this in sync with Settings TetherSettings.java - public static final int WIFI_TETHERING = 0; + private static final Intent TETHER_SERVICE_INTENT = new Intent() + .putExtra(TetherUtil.EXTRA_ADD_TETHER_TYPE, TetherUtil.TETHERING_WIFI) + .putExtra(TetherUtil.EXTRA_SET_ALARM, true) + .putExtra(TetherUtil.EXTRA_RUN_PROVISION, true) + .putExtra(TetherUtil.EXTRA_ENABLE_WIFI_TETHER, true) + .setComponent(TetherUtil.TETHER_SERVICE); private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>(); private final Receiver mReceiver = new Receiver(); private final Context mContext; private final WifiManager mWifiManager; - private final ConnectivityManager mConnectivityManager; public HotspotControllerImpl(Context context) { mContext = context; mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); - mConnectivityManager = (ConnectivityManager) - mContext.getSystemService(Context.CONNECTIVITY_SERVICE); } public void addCallback(Callback callback) { @@ -78,54 +71,17 @@ public class HotspotControllerImpl implements HotspotController { @Override public boolean isHotspotSupported() { - final boolean isSecondaryUser = ActivityManager.getCurrentUser() != UserHandle.USER_OWNER; - return !isSecondaryUser && mConnectivityManager.isTetheringSupported(); - } - - @Override - public boolean isProvisioningNeeded() { - // Keep in sync with other usage of config_mobile_hotspot_provision_app. - // TetherSettings#isProvisioningNeeded and - // ConnectivityManager#enforceTetherChangePermission - String[] provisionApp = mContext.getResources().getStringArray( - com.android.internal.R.array.config_mobile_hotspot_provision_app); - if (SystemProperties.getBoolean("net.tethering.noprovisioning", false) - || provisionApp == null) { - return false; - } - return (provisionApp.length == 2); + return TetherUtil.isTetheringSupported(mContext); } @Override public void setHotspotEnabled(boolean enabled) { final ContentResolver cr = mContext.getContentResolver(); // Call provisioning app which is called when enabling Tethering from Settings - if (enabled) { - if (isProvisioningNeeded()) { - String tetherEnable = mContext.getResources().getString( - com.android.internal.R.string.config_wifi_tether_enable); - Intent intent = new Intent(); - intent.putExtra(EXTRA_ADD_TETHER_TYPE, WIFI_TETHERING); - intent.putExtra(EXTRA_SET_ALARM, true); - intent.putExtra(EXTRA_RUN_PROVISION, true); - intent.putExtra(EXTRA_ENABLE_WIFI_TETHER, true); - intent.setComponent(ComponentName.unflattenFromString(tetherEnable)); - mContext.startServiceAsUser(intent, UserHandle.CURRENT); - } else { - int wifiState = mWifiManager.getWifiState(); - if ((wifiState == WifiManager.WIFI_STATE_ENABLING) || - (wifiState == WifiManager.WIFI_STATE_ENABLED)) { - mWifiManager.setWifiEnabled(false); - Settings.Global.putInt(cr, Settings.Global.WIFI_SAVED_STATE, 1); - } - mWifiManager.setWifiApEnabled(null, true); - } + if (enabled && TetherUtil.isProvisioningNeeded(mContext)) { + mContext.startServiceAsUser(TETHER_SERVICE_INTENT, UserHandle.CURRENT); } else { - mWifiManager.setWifiApEnabled(null, false); - if (Settings.Global.getInt(cr, Settings.Global.WIFI_SAVED_STATE, 0) == 1) { - mWifiManager.setWifiEnabled(true); - Settings.Global.putInt(cr, Settings.Global.WIFI_SAVED_STATE, 0); - } + TetherUtil.setWifiTethering(enabled, mContext); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java index 6998791..3f63b5f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java @@ -26,7 +26,7 @@ import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.drawable.Drawable; -import android.view.HardwareCanvas; +import android.view.DisplayListCanvas; import android.view.RenderNodeAnimator; import android.view.View; import android.view.animation.Interpolator; @@ -106,7 +106,7 @@ public class KeyButtonRipple extends Drawable { public void draw(Canvas canvas) { mSupportHardware = canvas.isHardwareAccelerated(); if (mSupportHardware) { - drawHardware((HardwareCanvas) canvas); + drawHardware((DisplayListCanvas) canvas); } else { drawSoftware(canvas); } @@ -118,7 +118,7 @@ public class KeyButtonRipple extends Drawable { } @Override - public void setColorFilter(ColorFilter cf) { + public void setColorFilter(ColorFilter colorFilter) { // Not supported. } @@ -131,7 +131,7 @@ public class KeyButtonRipple extends Drawable { return getBounds().width() > getBounds().height(); } - private void drawHardware(HardwareCanvas c) { + private void drawHardware(DisplayListCanvas c) { if (mDrawingHardwareGlow) { c.drawRoundRect(mLeftProp, mTopProp, mRightProp, mBottomProp, mRxProp, mRyProp, mPaintProp); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java index b9cc0f9..6bc51fa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java @@ -16,8 +16,6 @@ package com.android.systemui.statusbar.policy; -import android.animation.Animator; -import android.animation.ObjectAnimator; import android.app.ActivityManager; import android.content.Context; import android.content.res.TypedArray; @@ -26,7 +24,6 @@ import android.media.AudioManager; import android.os.Bundle; import android.os.SystemClock; import android.util.AttributeSet; -import android.util.Log; import android.view.HapticFeedbackConstants; import android.view.InputDevice; import android.view.KeyCharacterMap; @@ -45,20 +42,13 @@ import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK; import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK; public class KeyButtonView extends ImageView { - private static final String TAG = "StatusBar.KeyButtonView"; - private static final boolean DEBUG = false; - - // TODO: Get rid of this - public static final float DEFAULT_QUIESCENT_ALPHA = 1f; private long mDownTime; private int mCode; private int mTouchSlop; - private float mDrawingAlpha = 1f; - private float mQuiescentAlpha = DEFAULT_QUIESCENT_ALPHA; private boolean mSupportsLongpress = true; private AudioManager mAudioManager; - private Animator mAnimateToQuiescent = new ObjectAnimator(); + private boolean mGestureAborted; private final Runnable mCheckLongPress = new Runnable() { public void run() { @@ -89,9 +79,6 @@ public class KeyButtonView extends ImageView { mSupportsLongpress = a.getBoolean(R.styleable.KeyButtonView_keyRepeat, true); - - setDrawingAlpha(mQuiescentAlpha); - a.recycle(); setClickable(true); @@ -121,7 +108,7 @@ public class KeyButtonView extends ImageView { } @Override - public boolean performAccessibilityAction(int action, Bundle arguments) { + public boolean performAccessibilityActionInternal(int action, Bundle arguments) { if (action == ACTION_CLICK && mCode != 0) { sendEvent(KeyEvent.ACTION_DOWN, 0, SystemClock.uptimeMillis()); sendEvent(KeyEvent.ACTION_UP, 0); @@ -134,47 +121,21 @@ public class KeyButtonView extends ImageView { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); return true; } - return super.performAccessibilityAction(action, arguments); - } - - public void setQuiescentAlpha(float alpha, boolean animate) { - mAnimateToQuiescent.cancel(); - alpha = Math.min(Math.max(alpha, 0), 1); - if (alpha == mQuiescentAlpha && alpha == mDrawingAlpha) return; - mQuiescentAlpha = alpha; - if (DEBUG) Log.d(TAG, "New quiescent alpha = " + mQuiescentAlpha); - if (animate) { - mAnimateToQuiescent = animateToQuiescent(); - mAnimateToQuiescent.start(); - } else { - setDrawingAlpha(mQuiescentAlpha); - } - } - - private ObjectAnimator animateToQuiescent() { - return ObjectAnimator.ofFloat(this, "drawingAlpha", mQuiescentAlpha); - } - - public float getQuiescentAlpha() { - return mQuiescentAlpha; - } - - public float getDrawingAlpha() { - return mDrawingAlpha; - } - - public void setDrawingAlpha(float x) { - setImageAlpha((int) (x * 255)); - mDrawingAlpha = x; + return super.performAccessibilityActionInternal(action, arguments); } public boolean onTouchEvent(MotionEvent ev) { final int action = ev.getAction(); int x, y; + if (action == MotionEvent.ACTION_DOWN) { + mGestureAborted = false; + } + if (mGestureAborted) { + return false; + } switch (action) { case MotionEvent.ACTION_DOWN: - //Log.d("KeyButtonView", "press"); mDownTime = SystemClock.uptimeMillis(); setPressed(true); if (mCode != 0) { @@ -248,6 +209,11 @@ public class KeyButtonView extends ImageView { InputManager.getInstance().injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); } + + public void abortCurrentGesture() { + setPressed(false); + mGestureAborted = true; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherScrim.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherScrim.java index a5fc2fe..353e07d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherScrim.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherScrim.java @@ -19,7 +19,6 @@ package com.android.systemui.statusbar.policy; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; -import android.graphics.LightingColorFilter; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.RadialGradient; @@ -48,7 +47,7 @@ public class KeyguardUserSwitcherScrim extends Drawable public KeyguardUserSwitcherScrim(View host) { host.addOnLayoutChangeListener(this); - mDarkColor = host.getResources().getColor( + mDarkColor = host.getContext().getColor( R.color.keyguard_user_switcher_background_gradient_color); } @@ -77,7 +76,7 @@ public class KeyguardUserSwitcherScrim extends Drawable } @Override - public void setColorFilter(ColorFilter cf) { + public void setColorFilter(ColorFilter colorFilter) { } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java new file mode 100644 index 0000000..ba938cc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java @@ -0,0 +1,495 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.policy; + +import android.content.Context; +import android.content.Intent; +import android.net.NetworkCapabilities; +import android.telephony.PhoneStateListener; +import android.telephony.ServiceState; +import android.telephony.SignalStrength; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.util.Log; +import android.util.SparseArray; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.TelephonyIntents; +import com.android.internal.telephony.cdma.EriInfo; +import com.android.systemui.R; +import com.android.systemui.statusbar.policy.NetworkController.NetworkSignalChangedCallback; +import com.android.systemui.statusbar.policy.NetworkControllerImpl.Config; +import com.android.systemui.statusbar.policy.NetworkControllerImpl.SignalCluster; + +import java.io.PrintWriter; +import java.util.List; +import java.util.Objects; + + +public class MobileSignalController extends SignalController< + MobileSignalController.MobileState, MobileSignalController.MobileIconGroup> { + private final TelephonyManager mPhone; + private final String mNetworkNameDefault; + private final String mNetworkNameSeparator; + @VisibleForTesting + final PhoneStateListener mPhoneStateListener; + // Save entire info for logging, we only use the id. + private final SubscriptionInfo mSubscriptionInfo; + + // @VisibleForDemoMode + final SparseArray<MobileIconGroup> mNetworkToIconLookup; + + // Since some pieces of the phone state are interdependent we store it locally, + // this could potentially become part of MobileState for simplification/complication + // of code. + private int mDataNetType = TelephonyManager.NETWORK_TYPE_UNKNOWN; + private int mDataState = TelephonyManager.DATA_DISCONNECTED; + private ServiceState mServiceState; + private SignalStrength mSignalStrength; + private MobileIconGroup mDefaultIcons; + private Config mConfig; + + // TODO: Reduce number of vars passed in, if we have the NetworkController, probably don't + // need listener lists anymore. + public MobileSignalController(Context context, Config config, boolean hasMobileData, + TelephonyManager phone, List<NetworkSignalChangedCallback> signalCallbacks, + List<SignalCluster> signalClusters, NetworkControllerImpl networkController, + SubscriptionInfo info) { + super("MobileSignalController(" + info.getSubscriptionId() + ")", context, + NetworkCapabilities.TRANSPORT_CELLULAR, signalCallbacks, signalClusters, + networkController); + mNetworkToIconLookup = new SparseArray<>(); + mConfig = config; + mPhone = phone; + mSubscriptionInfo = info; + mPhoneStateListener = new MobilePhoneStateListener(info.getSubscriptionId()); + mNetworkNameSeparator = getStringIfExists(R.string.status_bar_network_name_separator); + mNetworkNameDefault = getStringIfExists( + com.android.internal.R.string.lockscreen_carrier_default); + + mapIconSets(); + + mLastState.networkName = mCurrentState.networkName = mNetworkNameDefault; + mLastState.enabled = mCurrentState.enabled = hasMobileData; + mLastState.iconGroup = mCurrentState.iconGroup = mDefaultIcons; + // Get initial data sim state. + updateDataSim(); + } + + public void setConfiguration(Config config) { + mConfig = config; + mapIconSets(); + updateTelephony(); + } + + public int getDataContentDescription() { + return getIcons().mDataContentDescription; + } + + public void setAirplaneMode(boolean airplaneMode) { + mCurrentState.airplaneMode = airplaneMode; + notifyListenersIfNecessary(); + } + + public void setInetCondition(int inetCondition, int inetConditionForNetwork) { + // For mobile data, use general inet condition for phone signal indexing, + // and network specific for data indexing (I think this might be a bug, but + // keeping for now). + // TODO: Update with explanation of why. + mCurrentState.inetForNetwork = inetConditionForNetwork; + setInetCondition(inetCondition); + } + + /** + * Start listening for phone state changes. + */ + public void registerListener() { + mPhone.listen(mPhoneStateListener, + PhoneStateListener.LISTEN_SERVICE_STATE + | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS + | PhoneStateListener.LISTEN_CALL_STATE + | PhoneStateListener.LISTEN_DATA_CONNECTION_STATE + | PhoneStateListener.LISTEN_DATA_ACTIVITY); + } + + /** + * Stop listening for phone state changes. + */ + public void unregisterListener() { + mPhone.listen(mPhoneStateListener, 0); + } + + /** + * Produce a mapping of data network types to icon groups for simple and quick use in + * updateTelephony. + */ + private void mapIconSets() { + mNetworkToIconLookup.clear(); + + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_0, TelephonyIcons.THREE_G); + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_A, TelephonyIcons.THREE_G); + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_B, TelephonyIcons.THREE_G); + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EHRPD, TelephonyIcons.THREE_G); + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UMTS, TelephonyIcons.THREE_G); + + if (!mConfig.showAtLeast3G) { + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UNKNOWN, + TelephonyIcons.UNKNOWN); + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EDGE, TelephonyIcons.E); + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_CDMA, TelephonyIcons.ONE_X); + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_1xRTT, TelephonyIcons.ONE_X); + + mDefaultIcons = TelephonyIcons.G; + } else { + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UNKNOWN, + TelephonyIcons.THREE_G); + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EDGE, + TelephonyIcons.THREE_G); + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_CDMA, + TelephonyIcons.THREE_G); + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_1xRTT, + TelephonyIcons.THREE_G); + mDefaultIcons = TelephonyIcons.THREE_G; + } + + MobileIconGroup hGroup = TelephonyIcons.THREE_G; + if (mConfig.hspaDataDistinguishable) { + hGroup = TelephonyIcons.H; + } + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSDPA, hGroup); + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSUPA, hGroup); + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSPA, hGroup); + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSPAP, hGroup); + + if (mConfig.show4gForLte) { + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE, TelephonyIcons.FOUR_G); + } else { + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE, TelephonyIcons.LTE); + } + } + + @Override + public void notifyListeners() { + MobileIconGroup icons = getIcons(); + + String contentDescription = getStringIfExists(getContentDescription()); + String dataContentDescription = getStringIfExists(icons.mDataContentDescription); + + boolean showDataIcon = mCurrentState.dataConnected && mCurrentState.inetForNetwork != 0 + || mCurrentState.iconGroup == TelephonyIcons.ROAMING; + + // Only send data sim callbacks to QS. + if (mCurrentState.dataSim) { + int qsTypeIcon = showDataIcon ? icons.mQsDataType[mCurrentState.inetForNetwork] : 0; + int length = mSignalsChangedCallbacks.size(); + for (int i = 0; i < length; i++) { + mSignalsChangedCallbacks.get(i).onMobileDataSignalChanged(mCurrentState.enabled + && !mCurrentState.isEmergency, + getQsCurrentIconId(), contentDescription, + qsTypeIcon, + mCurrentState.dataConnected && mCurrentState.activityIn, + mCurrentState.dataConnected && mCurrentState.activityOut, + dataContentDescription, + mCurrentState.isEmergency ? null : mCurrentState.networkName, + // Only wide if actually showing something. + icons.mIsWide && qsTypeIcon != 0); + } + } + int typeIcon = showDataIcon ? icons.mDataType : 0; + int signalClustersLength = mSignalClusters.size(); + for (int i = 0; i < signalClustersLength; i++) { + mSignalClusters.get(i).setMobileDataIndicators( + mCurrentState.enabled && !mCurrentState.airplaneMode, + getCurrentIconId(), + typeIcon, + contentDescription, + dataContentDescription, + // Only wide if actually showing something. + icons.mIsWide && typeIcon != 0, + mSubscriptionInfo.getSubscriptionId()); + } + } + + @Override + protected MobileState cleanState() { + return new MobileState(); + } + + private boolean hasService() { + if (mServiceState != null) { + // Consider the device to be in service if either voice or data + // service is available. Some SIM cards are marketed as data-only + // and do not support voice service, and on these SIM cards, we + // want to show signal bars for data service as well as the "no + // service" or "emergency calls only" text that indicates that voice + // is not available. + switch (mServiceState.getVoiceRegState()) { + case ServiceState.STATE_POWER_OFF: + return false; + case ServiceState.STATE_OUT_OF_SERVICE: + case ServiceState.STATE_EMERGENCY_ONLY: + return mServiceState.getDataRegState() == ServiceState.STATE_IN_SERVICE; + default: + return true; + } + } else { + return false; + } + } + + private boolean isCdma() { + return (mSignalStrength != null) && !mSignalStrength.isGsm(); + } + + public boolean isEmergencyOnly() { + return (mServiceState != null && mServiceState.isEmergencyOnly()); + } + + private boolean isRoaming() { + if (isCdma()) { + final int iconMode = mServiceState.getCdmaEriIconMode(); + return mServiceState.getCdmaEriIconIndex() != EriInfo.ROAMING_INDICATOR_OFF + && (iconMode == EriInfo.ROAMING_ICON_MODE_NORMAL + || iconMode == EriInfo.ROAMING_ICON_MODE_FLASH); + } else { + return mServiceState != null && mServiceState.getRoaming(); + } + } + + public void handleBroadcast(Intent intent) { + String action = intent.getAction(); + if (action.equals(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION)) { + updateNetworkName(intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false), + intent.getStringExtra(TelephonyIntents.EXTRA_SPN), + intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false), + intent.getStringExtra(TelephonyIntents.EXTRA_PLMN)); + notifyListenersIfNecessary(); + } else if (action.equals(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)) { + updateDataSim(); + } + } + + private void updateDataSim() { + int defaultDataSub = SubscriptionManager.getDefaultDataSubId(); + if (SubscriptionManager.isValidSubscriptionId(defaultDataSub)) { + mCurrentState.dataSim = defaultDataSub == mSubscriptionInfo.getSubscriptionId(); + } else { + // There doesn't seem to be a data sim selected, however if + // there isn't a MobileSignalController with dataSim set, then + // QS won't get any callbacks and will be blank. Instead + // lets just assume we are the data sim (which will basically + // show one at random) in QS until one is selected. The user + // should pick one soon after, so we shouldn't be in this state + // for long. + mCurrentState.dataSim = true; + } + notifyListenersIfNecessary(); + } + + /** + * Updates the network's name based on incoming spn and plmn. + */ + void updateNetworkName(boolean showSpn, String spn, boolean showPlmn, String plmn) { + if (CHATTY) { + Log.d("CarrierLabel", "updateNetworkName showSpn=" + showSpn + " spn=" + spn + + " showPlmn=" + showPlmn + " plmn=" + plmn); + } + StringBuilder str = new StringBuilder(); + if (showPlmn && plmn != null) { + str.append(plmn); + } + if (showSpn && spn != null) { + if (str.length() != 0) { + str.append(mNetworkNameSeparator); + } + str.append(spn); + } + if (str.length() != 0) { + mCurrentState.networkName = str.toString(); + } else { + mCurrentState.networkName = mNetworkNameDefault; + } + } + + /** + * Updates the current state based on mServiceState, mSignalStrength, mDataNetType, + * mDataState, and mSimState. It should be called any time one of these is updated. + * This will call listeners if necessary. + */ + private final void updateTelephony() { + if (DEBUG) { + Log.d(mTag, "updateTelephonySignalStrength: hasService=" + hasService() + + " ss=" + mSignalStrength); + } + mCurrentState.connected = hasService() && mSignalStrength != null; + if (mCurrentState.connected) { + if (!mSignalStrength.isGsm() && mConfig.alwaysShowCdmaRssi) { + mCurrentState.level = mSignalStrength.getCdmaLevel(); + } else { + mCurrentState.level = mSignalStrength.getLevel(); + } + } + if (mNetworkToIconLookup.indexOfKey(mDataNetType) >= 0) { + mCurrentState.iconGroup = mNetworkToIconLookup.get(mDataNetType); + } else { + mCurrentState.iconGroup = mDefaultIcons; + } + mCurrentState.dataConnected = mCurrentState.connected + && mDataState == TelephonyManager.DATA_CONNECTED; + + if (isRoaming()) { + mCurrentState.iconGroup = TelephonyIcons.ROAMING; + } + if (isEmergencyOnly() != mCurrentState.isEmergency) { + mCurrentState.isEmergency = isEmergencyOnly(); + mNetworkController.recalculateEmergency(); + } + // Fill in the network name if we think we have it. + if (mCurrentState.networkName == mNetworkNameDefault && mServiceState != null + && mServiceState.getOperatorAlphaShort() != null) { + mCurrentState.networkName = mServiceState.getOperatorAlphaShort(); + } + notifyListenersIfNecessary(); + } + + @VisibleForTesting + void setActivity(int activity) { + mCurrentState.activityIn = activity == TelephonyManager.DATA_ACTIVITY_INOUT + || activity == TelephonyManager.DATA_ACTIVITY_IN; + mCurrentState.activityOut = activity == TelephonyManager.DATA_ACTIVITY_INOUT + || activity == TelephonyManager.DATA_ACTIVITY_OUT; + notifyListenersIfNecessary(); + } + + @Override + public void dump(PrintWriter pw) { + super.dump(pw); + pw.println(" mSubscription=" + mSubscriptionInfo + ","); + pw.println(" mServiceState=" + mServiceState + ","); + pw.println(" mSignalStrength=" + mSignalStrength + ","); + pw.println(" mDataState=" + mDataState + ","); + pw.println(" mDataNetType=" + mDataNetType + ","); + } + + class MobilePhoneStateListener extends PhoneStateListener { + public MobilePhoneStateListener(int subId) { + super(subId); + } + + @Override + public void onSignalStrengthsChanged(SignalStrength signalStrength) { + if (DEBUG) { + Log.d(mTag, "onSignalStrengthsChanged signalStrength=" + signalStrength + + ((signalStrength == null) ? "" : (" level=" + signalStrength.getLevel()))); + } + mSignalStrength = signalStrength; + updateTelephony(); + } + + @Override + public void onServiceStateChanged(ServiceState state) { + if (DEBUG) { + Log.d(mTag, "onServiceStateChanged voiceState=" + state.getVoiceRegState() + + " dataState=" + state.getDataRegState()); + } + mServiceState = state; + updateTelephony(); + } + + @Override + public void onDataConnectionStateChanged(int state, int networkType) { + if (DEBUG) { + Log.d(mTag, "onDataConnectionStateChanged: state=" + state + + " type=" + networkType); + } + mDataState = state; + mDataNetType = networkType; + updateTelephony(); + } + + @Override + public void onDataActivity(int direction) { + if (DEBUG) { + Log.d(mTag, "onDataActivity: direction=" + direction); + } + setActivity(direction); + } + }; + + static class MobileIconGroup extends SignalController.IconGroup { + final int mDataContentDescription; // mContentDescriptionDataType + final int mDataType; + final boolean mIsWide; + final int[] mQsDataType; + + public MobileIconGroup(String name, int[][] sbIcons, int[][] qsIcons, int[] contentDesc, + int sbNullState, int qsNullState, int sbDiscState, int qsDiscState, + int discContentDesc, int dataContentDesc, int dataType, boolean isWide, + int[] qsDataType) { + super(name, sbIcons, qsIcons, contentDesc, sbNullState, qsNullState, sbDiscState, + qsDiscState, discContentDesc); + mDataContentDescription = dataContentDesc; + mDataType = dataType; + mIsWide = isWide; + mQsDataType = qsDataType; + } + } + + static class MobileState extends SignalController.State { + String networkName; + boolean dataSim; + boolean dataConnected; + boolean isEmergency; + boolean airplaneMode; + int inetForNetwork; + + @Override + public void copyFrom(State s) { + super.copyFrom(s); + MobileState state = (MobileState) s; + dataSim = state.dataSim; + networkName = state.networkName; + dataConnected = state.dataConnected; + inetForNetwork = state.inetForNetwork; + isEmergency = state.isEmergency; + airplaneMode = state.airplaneMode; + } + + @Override + protected void toString(StringBuilder builder) { + super.toString(builder); + builder.append(','); + builder.append("dataSim=").append(dataSim).append(','); + builder.append("networkName=").append(networkName).append(','); + builder.append("dataConnected=").append(dataConnected).append(','); + builder.append("inetForNetwork=").append(inetForNetwork).append(','); + builder.append("isEmergency=").append(isEmergency).append(','); + builder.append("airplaneMode=").append(airplaneMode); + } + + @Override + public boolean equals(Object o) { + return super.equals(o) + && Objects.equals(((MobileState) o).networkName, networkName) + && ((MobileState) o).dataSim == dataSim + && ((MobileState) o).dataConnected == dataConnected + && ((MobileState) o).isEmergency == isEmergency + && ((MobileState) o).airplaneMode == airplaneMode + && ((MobileState) o).inetForNetwork == inetForNetwork; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java index 3cffc85..9212837 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java @@ -18,6 +18,10 @@ package com.android.systemui.statusbar.policy; import android.content.Intent; +import com.android.settingslib.wifi.AccessPoint; + +import java.util.List; + public interface NetworkController { boolean hasMobileDataFeature(); @@ -50,25 +54,14 @@ public interface NetworkController { void addAccessPointCallback(AccessPointCallback callback); void removeAccessPointCallback(AccessPointCallback callback); void scanForAccessPoints(); + int getIcon(AccessPoint ap); boolean connect(AccessPoint ap); boolean canConfigWifi(); public interface AccessPointCallback { - void onAccessPointsChanged(AccessPoint[] accessPoints); + void onAccessPointsChanged(List<AccessPoint> accessPoints); void onSettingsActivityTriggered(Intent settingsIntent); } - - public static class AccessPoint { - public static final int NO_NETWORK = -1; // see WifiManager - - public int networkId; - public int iconId; - public String ssid; - public boolean isConnected; - public boolean isConfigured; - public boolean hasSecurity; - public int level; // 0 - 5 - } } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index 9a7f21e..bb3eb7a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -17,10 +17,7 @@ package com.android.systemui.statusbar.policy; import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; -import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH; -import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; -import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; -import static android.net.NetworkCapabilities.TRANSPORT_WIFI; + import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -28,34 +25,20 @@ import android.content.IntentFilter; import android.content.res.Resources; import android.net.ConnectivityManager; import android.net.NetworkCapabilities; -import android.net.NetworkInfo; -import android.net.wifi.WifiConfiguration; -import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.os.AsyncTask; import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.Messenger; import android.provider.Settings; -import android.telephony.PhoneStateListener; -import android.telephony.ServiceState; -import android.telephony.SignalStrength; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener; import android.telephony.TelephonyManager; import android.text.TextUtils; -import android.text.format.DateFormat; import android.util.Log; -import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.telephony.IccCardConstants; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.TelephonyIntents; -import com.android.internal.telephony.cdma.EriInfo; -import com.android.internal.util.AsyncChannel; import com.android.systemui.DemoMode; import com.android.systemui.R; @@ -69,7 +52,6 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Objects; /** Platform implementation of the network controller. **/ public class NetworkControllerImpl extends BroadcastReceiver @@ -79,12 +61,6 @@ public class NetworkControllerImpl extends BroadcastReceiver static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); // additional diagnostics, but not logspew static final boolean CHATTY = Log.isLoggable(TAG + ".Chat", Log.DEBUG); - // Save the previous SignalController.States of all SignalControllers for dumps. - static final boolean RECORD_HISTORY = true; - // If RECORD_HISTORY how many to save, must be a power of 2. - static final int HISTORY_SIZE = 16; - - private static final int INET_CONDITION_THRESHOLD = 50; private final Context mContext; private final TelephonyManager mPhone; @@ -106,12 +82,6 @@ public class NetworkControllerImpl extends BroadcastReceiver private final AccessPointControllerImpl mAccessPoints; private final MobileDataControllerImpl mMobileDataController; - // Network types that replace the carrier label if the device does not support mobile data. - private boolean mBluetoothTethered = false; - private boolean mEthernetConnected = false; - - // state of inet connection - private boolean mConnected = false; private boolean mInetCondition; // Used for Logging and demo. // BitSets indicating which network transport types (e.g., TRANSPORT_WIFI, TRANSPORT_MOBILE) are @@ -129,8 +99,6 @@ public class NetworkControllerImpl extends BroadcastReceiver // All the callbacks. private ArrayList<EmergencyListener> mEmergencyListeners = new ArrayList<EmergencyListener>(); - private ArrayList<CarrierLabelListener> mCarrierListeners = - new ArrayList<CarrierLabelListener>(); private ArrayList<SignalCluster> mSignalClusters = new ArrayList<SignalCluster>(); private ArrayList<NetworkSignalChangedCallback> mSignalsChangedCallbacks = new ArrayList<NetworkSignalChangedCallback>(); @@ -188,7 +156,6 @@ public class NetworkControllerImpl extends BroadcastReceiver // AIRPLANE_MODE_CHANGED is sent at boot; we've probably already missed it updateAirplaneMode(true /* force callback */); - mAccessPoints.setNetworkController(this); } private void registerListeners() { @@ -244,11 +211,6 @@ public class NetworkControllerImpl extends BroadcastReceiver listener.setEmergencyCallsOnly(isEmergencyOnly()); } - public void addCarrierLabel(CarrierLabelListener listener) { - mCarrierListeners.add(listener); - refreshCarrierLabel(); - } - private void notifyMobileDataEnabled(boolean enabled) { final int length = mSignalsChangedCallbacks.size(); for (int i = 0; i < length; i++) { @@ -310,9 +272,6 @@ public class NetworkControllerImpl extends BroadcastReceiver for (int i = 0; i < length; i++) { mEmergencyListeners.get(i).setEmergencyCallsOnly(emergencyOnly); } - // If the emergency has a chance to change, then so does the carrier - // label. - refreshCarrierLabel(); } public void addSignalCluster(SignalCluster cluster) { @@ -364,7 +323,6 @@ public class NetworkControllerImpl extends BroadcastReceiver mCurrentUserId = newUserId; mAccessPoints.onUserSwitched(newUserId); updateConnectivity(); - refreshCarrierLabel(); } @Override @@ -376,14 +334,12 @@ public class NetworkControllerImpl extends BroadcastReceiver if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE) || action.equals(ConnectivityManager.INET_CONDITION_ACTION)) { updateConnectivity(); - refreshCarrierLabel(); } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) { mConfig = Config.readConfig(mContext); handleConfigurationChanged(); } else if (action.equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) { refreshLocale(); updateAirplaneMode(false); - refreshCarrierLabel(); } else if (action.equals(TelephonyIntents.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED)) { // We are using different subs now, we might be able to make calls. recalculateEmergency(); @@ -419,7 +375,6 @@ public class NetworkControllerImpl extends BroadcastReceiver mobileSignalController.setConfiguration(mConfig); } refreshLocale(); - refreshCarrierLabel(); } private void updateMobileControllers() { @@ -525,7 +480,6 @@ public class NetworkControllerImpl extends BroadcastReceiver mobileSignalController.setAirplaneMode(mAirplaneMode); } notifyListeners(); - refreshCarrierLabel(); } } @@ -589,10 +543,7 @@ public class NetworkControllerImpl extends BroadcastReceiver Log.d(TAG, "updateConnectivity: mValidatedTransports=" + mValidatedTransports); } - mConnected = !mConnectedTransports.isEmpty(); mInetCondition = !mValidatedTransports.isEmpty(); - mBluetoothTethered = mConnectedTransports.get(TRANSPORT_BLUETOOTH); - mEthernetConnected = mConnectedTransports.get(TRANSPORT_ETHERNET); pushConnectivityToSignals(); } @@ -611,59 +562,6 @@ public class NetworkControllerImpl extends BroadcastReceiver mValidatedTransports.get(mWifiSignalController.getTransportType()) ? 1 : 0); } - /** - * Recalculate and update the carrier label. - */ - void refreshCarrierLabel() { - Context context = mContext; - - WifiSignalController.WifiState wifiState = mWifiSignalController.getState(); - String label = ""; - for (MobileSignalController controller : mMobileSignalControllers.values()) { - label = controller.getLabel(label, mConnected, mHasMobileDataFeature); - } - - // TODO Simplify this ugliness, some of the flows below shouldn't be possible anymore - // but stay for the sake of history. - if (mBluetoothTethered && !mHasMobileDataFeature) { - label = mContext.getString(R.string.bluetooth_tethered); - } - - if (mEthernetConnected && !mHasMobileDataFeature) { - label = context.getString(R.string.ethernet_label); - } - - if (mAirplaneMode && !isEmergencyOnly()) { - // combined values from connected wifi take precedence over airplane mode - if (wifiState.connected && mHasMobileDataFeature) { - // Suppress "No internet connection." from mobile if wifi connected. - label = ""; - } else { - if (!mHasMobileDataFeature) { - label = context.getString( - R.string.status_bar_settings_signal_meter_disconnected); - } - } - } else if (!isMobileDataConnected() && !wifiState.connected && !mBluetoothTethered && - !mEthernetConnected && !mHasMobileDataFeature) { - // Pretty much no connection. - label = context.getString(R.string.status_bar_settings_signal_meter_disconnected); - } - - // for mobile devices, we always show mobile connection info here (SPN/PLMN) - // for other devices, we show whatever network is connected - // This is determined above by references to mHasMobileDataFeature. - int length = mCarrierListeners.size(); - for (int i = 0; i < length; i++) { - mCarrierListeners.get(i).setCarrierLabel(label); - } - } - - private boolean isMobileDataConnected() { - MobileSignalController controller = getDataController(); - return controller != null ? controller.getState().dataConnected : false; - } - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("NetworkController state:"); @@ -671,10 +569,6 @@ public class NetworkControllerImpl extends BroadcastReceiver pw.print(" hasVoiceCallingFeature()="); pw.println(hasVoiceCallingFeature()); - pw.println(" - Bluetooth ----"); - pw.print(" mBtReverseTethered="); - pw.println(mBluetoothTethered); - pw.println(" - connectivity ------"); pw.print(" mConnectedTransports="); pw.println(mConnectedTransports); @@ -691,6 +585,8 @@ public class NetworkControllerImpl extends BroadcastReceiver mobileSignalController.dump(pw); } mWifiSignalController.dump(pw); + + mAccessPoints.dump(pw); } private boolean mDemoMode; @@ -717,7 +613,6 @@ public class NetworkControllerImpl extends BroadcastReceiver mWifiSignalController.resetLastState(); registerListeners(); notifyAllListeners(); - refreshCarrierLabel(); } else if (mDemoMode && command.equals(COMMAND_NETWORK)) { String airplane = args.getString("airplane"); if (airplane != null) { @@ -809,7 +704,6 @@ public class NetworkControllerImpl extends BroadcastReceiver controller.getState().enabled = show; controller.notifyListeners(); } - refreshCarrierLabel(); } } @@ -821,965 +715,6 @@ public class NetworkControllerImpl extends BroadcastReceiver }; }; - // TODO: Move to its own file. - static class WifiSignalController extends - SignalController<WifiSignalController.WifiState, SignalController.IconGroup> { - private final WifiManager mWifiManager; - private final AsyncChannel mWifiChannel; - private final boolean mHasMobileData; - - public WifiSignalController(Context context, boolean hasMobileData, - List<NetworkSignalChangedCallback> signalCallbacks, - List<SignalCluster> signalClusters, NetworkControllerImpl networkController) { - super("WifiSignalController", context, NetworkCapabilities.TRANSPORT_WIFI, - signalCallbacks, signalClusters, networkController); - mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); - mHasMobileData = hasMobileData; - Handler handler = new WifiHandler(); - mWifiChannel = new AsyncChannel(); - Messenger wifiMessenger = mWifiManager.getWifiServiceMessenger(); - if (wifiMessenger != null) { - mWifiChannel.connect(context, handler, wifiMessenger); - } - // WiFi only has one state. - mCurrentState.iconGroup = mLastState.iconGroup = new IconGroup( - "Wi-Fi Icons", - WifiIcons.WIFI_SIGNAL_STRENGTH, - WifiIcons.QS_WIFI_SIGNAL_STRENGTH, - AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH, - WifiIcons.WIFI_NO_NETWORK, - WifiIcons.QS_WIFI_NO_NETWORK, - WifiIcons.WIFI_NO_NETWORK, - WifiIcons.QS_WIFI_NO_NETWORK, - AccessibilityContentDescriptions.WIFI_NO_CONNECTION - ); - } - - @Override - protected WifiState cleanState() { - return new WifiState(); - } - - @Override - public void notifyListeners() { - // only show wifi in the cluster if connected or if wifi-only - boolean wifiVisible = mCurrentState.enabled - && (mCurrentState.connected || !mHasMobileData); - String wifiDesc = wifiVisible ? mCurrentState.ssid : null; - boolean ssidPresent = wifiVisible && mCurrentState.ssid != null; - String contentDescription = getStringIfExists(getContentDescription()); - int length = mSignalsChangedCallbacks.size(); - for (int i = 0; i < length; i++) { - mSignalsChangedCallbacks.get(i).onWifiSignalChanged(mCurrentState.enabled, - mCurrentState.connected, getQsCurrentIconId(), - ssidPresent && mCurrentState.activityIn, - ssidPresent && mCurrentState.activityOut, contentDescription, wifiDesc); - } - - int signalClustersLength = mSignalClusters.size(); - for (int i = 0; i < signalClustersLength; i++) { - mSignalClusters.get(i).setWifiIndicators(wifiVisible, getCurrentIconId(), - contentDescription); - } - } - - /** - * Extract wifi state directly from broadcasts about changes in wifi state. - */ - public void handleBroadcast(Intent intent) { - String action = intent.getAction(); - if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { - mCurrentState.enabled = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, - WifiManager.WIFI_STATE_UNKNOWN) == WifiManager.WIFI_STATE_ENABLED; - } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { - final NetworkInfo networkInfo = (NetworkInfo) - intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); - mCurrentState.connected = networkInfo != null && networkInfo.isConnected(); - // If Connected grab the signal strength and ssid. - if (mCurrentState.connected) { - // try getting it out of the intent first - WifiInfo info = intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO) != null - ? (WifiInfo) intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO) - : mWifiManager.getConnectionInfo(); - if (info != null) { - mCurrentState.ssid = getSsid(info); - } else { - mCurrentState.ssid = null; - } - } else if (!mCurrentState.connected) { - mCurrentState.ssid = null; - } - } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) { - // Default to -200 as its below WifiManager.MIN_RSSI. - mCurrentState.rssi = intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200); - mCurrentState.level = WifiManager.calculateSignalLevel( - mCurrentState.rssi, WifiIcons.WIFI_LEVEL_COUNT); - } - - notifyListenersIfNecessary(); - } - - private String getSsid(WifiInfo info) { - String ssid = info.getSSID(); - if (ssid != null) { - return ssid; - } - // OK, it's not in the connectionInfo; we have to go hunting for it - List<WifiConfiguration> networks = mWifiManager.getConfiguredNetworks(); - int length = networks.size(); - for (int i = 0; i < length; i++) { - if (networks.get(i).networkId == info.getNetworkId()) { - return networks.get(i).SSID; - } - } - return null; - } - - @VisibleForTesting - void setActivity(int wifiActivity) { - mCurrentState.activityIn = wifiActivity == WifiManager.DATA_ACTIVITY_INOUT - || wifiActivity == WifiManager.DATA_ACTIVITY_IN; - mCurrentState.activityOut = wifiActivity == WifiManager.DATA_ACTIVITY_INOUT - || wifiActivity == WifiManager.DATA_ACTIVITY_OUT; - notifyListenersIfNecessary(); - } - - /** - * Handler to receive the data activity on wifi. - */ - class WifiHandler extends Handler { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: - if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { - mWifiChannel.sendMessage(Message.obtain(this, - AsyncChannel.CMD_CHANNEL_FULL_CONNECTION)); - } else { - Log.e(mTag, "Failed to connect to wifi"); - } - break; - case WifiManager.DATA_ACTIVITY_NOTIFICATION: - setActivity(msg.arg1); - break; - default: - // Ignore - break; - } - } - } - - static class WifiState extends SignalController.State { - String ssid; - - @Override - public void copyFrom(State s) { - super.copyFrom(s); - WifiState state = (WifiState) s; - ssid = state.ssid; - } - - @Override - protected void toString(StringBuilder builder) { - super.toString(builder); - builder.append(',').append("ssid=").append(ssid); - } - - @Override - public boolean equals(Object o) { - return super.equals(o) - && Objects.equals(((WifiState) o).ssid, ssid); - } - } - } - - // TODO: Move to its own file. - public static class MobileSignalController extends SignalController< - MobileSignalController.MobileState, MobileSignalController.MobileIconGroup> { - private final TelephonyManager mPhone; - private final String mNetworkNameDefault; - private final String mNetworkNameSeparator; - @VisibleForTesting - final PhoneStateListener mPhoneStateListener; - // Save entire info for logging, we only use the id. - private final SubscriptionInfo mSubscriptionInfo; - - // @VisibleForDemoMode - final SparseArray<MobileIconGroup> mNetworkToIconLookup; - - // Since some pieces of the phone state are interdependent we store it locally, - // this could potentially become part of MobileState for simplification/complication - // of code. - private IccCardConstants.State mSimState = IccCardConstants.State.READY; - private int mDataNetType = TelephonyManager.NETWORK_TYPE_UNKNOWN; - private int mDataState = TelephonyManager.DATA_DISCONNECTED; - private ServiceState mServiceState; - private SignalStrength mSignalStrength; - private MobileIconGroup mDefaultIcons; - private Config mConfig; - - // TODO: Reduce number of vars passed in, if we have the NetworkController, probably don't - // need listener lists anymore. - public MobileSignalController(Context context, Config config, boolean hasMobileData, - TelephonyManager phone, List<NetworkSignalChangedCallback> signalCallbacks, - List<SignalCluster> signalClusters, NetworkControllerImpl networkController, - SubscriptionInfo info) { - super("MobileSignalController(" + info.getSubscriptionId() + ")", context, - NetworkCapabilities.TRANSPORT_CELLULAR, signalCallbacks, signalClusters, - networkController); - mNetworkToIconLookup = new SparseArray<>(); - mConfig = config; - mPhone = phone; - mSubscriptionInfo = info; - mPhoneStateListener = new MobilePhoneStateListener(info.getSubscriptionId()); - mNetworkNameSeparator = getStringIfExists(R.string.status_bar_network_name_separator); - mNetworkNameDefault = getStringIfExists( - com.android.internal.R.string.lockscreen_carrier_default); - - mapIconSets(); - - mLastState.networkName = mCurrentState.networkName = mNetworkNameDefault; - mLastState.enabled = mCurrentState.enabled = hasMobileData; - mLastState.iconGroup = mCurrentState.iconGroup = mDefaultIcons; - // Get initial data sim state. - updateDataSim(); - } - - public void setConfiguration(Config config) { - mConfig = config; - mapIconSets(); - updateTelephony(); - } - - /** - * Get (the mobile parts of) the carrier string. - * - * @param currentLabel can be used for concatenation, currently just empty - * @param connected whether the device has connection to the internet at all - * @param isMobileLabel whether to always return the network or just when data is connected - */ - public String getLabel(String currentLabel, boolean connected, boolean isMobileLabel) { - if (!mCurrentState.enabled) { - return ""; - } else { - String mobileLabel = ""; - // We want to show the carrier name if in service and either: - // - We are connected to mobile data, or - // - We are not connected to mobile data, as long as the *reason* packets are not - // being routed over that link is that we have better connectivity via wifi. - // If data is disconnected for some other reason but wifi (or ethernet/bluetooth) - // is connected, we show nothing. - // Otherwise (nothing connected) we show "No internet connection". - if (mCurrentState.dataConnected) { - mobileLabel = mCurrentState.networkName; - } else if (connected || mCurrentState.isEmergency) { - if (mCurrentState.connected || mCurrentState.isEmergency) { - // The isEmergencyOnly test covers the case of a phone with no SIM - mobileLabel = mCurrentState.networkName; - } - } else { - mobileLabel = mContext.getString( - R.string.status_bar_settings_signal_meter_disconnected); - } - - if (currentLabel.length() != 0) { - currentLabel = currentLabel + mNetworkNameSeparator; - } - // Now for things that should only be shown when actually using mobile data. - if (isMobileLabel) { - return currentLabel + mobileLabel; - } else { - return currentLabel - + (mCurrentState.dataConnected ? mobileLabel : currentLabel); - } - } - } - - public int getDataContentDescription() { - return getIcons().mDataContentDescription; - } - - public void setAirplaneMode(boolean airplaneMode) { - mCurrentState.airplaneMode = airplaneMode; - notifyListenersIfNecessary(); - } - - public void setInetCondition(int inetCondition, int inetConditionForNetwork) { - // For mobile data, use general inet condition for phone signal indexing, - // and network specific for data indexing (I think this might be a bug, but - // keeping for now). - // TODO: Update with explanation of why. - mCurrentState.inetForNetwork = inetConditionForNetwork; - setInetCondition(inetCondition); - } - - /** - * Start listening for phone state changes. - */ - public void registerListener() { - mPhone.listen(mPhoneStateListener, - PhoneStateListener.LISTEN_SERVICE_STATE - | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS - | PhoneStateListener.LISTEN_CALL_STATE - | PhoneStateListener.LISTEN_DATA_CONNECTION_STATE - | PhoneStateListener.LISTEN_DATA_ACTIVITY); - } - - /** - * Stop listening for phone state changes. - */ - public void unregisterListener() { - mPhone.listen(mPhoneStateListener, 0); - } - - /** - * Produce a mapping of data network types to icon groups for simple and quick use in - * updateTelephony. - */ - private void mapIconSets() { - mNetworkToIconLookup.clear(); - - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_0, TelephonyIcons.THREE_G); - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_A, TelephonyIcons.THREE_G); - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_B, TelephonyIcons.THREE_G); - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EHRPD, TelephonyIcons.THREE_G); - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UMTS, TelephonyIcons.THREE_G); - - if (!mConfig.showAtLeast3G) { - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UNKNOWN, - TelephonyIcons.UNKNOWN); - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EDGE, TelephonyIcons.E); - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_CDMA, TelephonyIcons.ONE_X); - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_1xRTT, TelephonyIcons.ONE_X); - - mDefaultIcons = TelephonyIcons.G; - } else { - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UNKNOWN, - TelephonyIcons.THREE_G); - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EDGE, - TelephonyIcons.THREE_G); - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_CDMA, - TelephonyIcons.THREE_G); - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_1xRTT, - TelephonyIcons.THREE_G); - mDefaultIcons = TelephonyIcons.THREE_G; - } - - MobileIconGroup hGroup = TelephonyIcons.THREE_G; - if (mConfig.hspaDataDistinguishable) { - hGroup = TelephonyIcons.H; - } - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSDPA, hGroup); - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSUPA, hGroup); - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSPA, hGroup); - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSPAP, hGroup); - - if (mConfig.show4gForLte) { - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE, TelephonyIcons.FOUR_G); - } else { - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE, TelephonyIcons.LTE); - } - } - - @Override - public void notifyListeners() { - MobileIconGroup icons = getIcons(); - - String contentDescription = getStringIfExists(getContentDescription()); - String dataContentDescription = getStringIfExists(icons.mDataContentDescription); - - boolean showDataIcon = mCurrentState.dataConnected && mCurrentState.inetForNetwork != 0 - || mCurrentState.iconGroup == TelephonyIcons.ROAMING; - - // Only send data sim callbacks to QS. - if (mCurrentState.dataSim) { - int qsTypeIcon = showDataIcon ? icons.mQsDataType[mCurrentState.inetForNetwork] : 0; - int length = mSignalsChangedCallbacks.size(); - for (int i = 0; i < length; i++) { - mSignalsChangedCallbacks.get(i).onMobileDataSignalChanged(mCurrentState.enabled - && !mCurrentState.isEmergency, - getQsCurrentIconId(), contentDescription, - qsTypeIcon, - mCurrentState.dataConnected && mCurrentState.activityIn, - mCurrentState.dataConnected && mCurrentState.activityOut, - dataContentDescription, - mCurrentState.isEmergency ? null : mCurrentState.networkName, - // Only wide if actually showing something. - icons.mIsWide && qsTypeIcon != 0); - } - } - int typeIcon = showDataIcon ? icons.mDataType : 0; - int signalClustersLength = mSignalClusters.size(); - for (int i = 0; i < signalClustersLength; i++) { - mSignalClusters.get(i).setMobileDataIndicators( - mCurrentState.enabled && !mCurrentState.airplaneMode, - getCurrentIconId(), - typeIcon, - contentDescription, - dataContentDescription, - // Only wide if actually showing something. - icons.mIsWide && typeIcon != 0, - mSubscriptionInfo.getSubscriptionId()); - } - } - - @Override - protected MobileState cleanState() { - return new MobileState(); - } - - private boolean hasService() { - if (mServiceState != null) { - // Consider the device to be in service if either voice or data - // service is available. Some SIM cards are marketed as data-only - // and do not support voice service, and on these SIM cards, we - // want to show signal bars for data service as well as the "no - // service" or "emergency calls only" text that indicates that voice - // is not available. - switch (mServiceState.getVoiceRegState()) { - case ServiceState.STATE_POWER_OFF: - return false; - case ServiceState.STATE_OUT_OF_SERVICE: - case ServiceState.STATE_EMERGENCY_ONLY: - return mServiceState.getDataRegState() == ServiceState.STATE_IN_SERVICE; - default: - return true; - } - } else { - return false; - } - } - - private boolean isCdma() { - return (mSignalStrength != null) && !mSignalStrength.isGsm(); - } - - public boolean isEmergencyOnly() { - return (mServiceState != null && mServiceState.isEmergencyOnly()); - } - - private boolean isRoaming() { - if (isCdma()) { - final int iconMode = mServiceState.getCdmaEriIconMode(); - return mServiceState.getCdmaEriIconIndex() != EriInfo.ROAMING_INDICATOR_OFF - && (iconMode == EriInfo.ROAMING_ICON_MODE_NORMAL - || iconMode == EriInfo.ROAMING_ICON_MODE_FLASH); - } else { - return mServiceState != null && mServiceState.getRoaming(); - } - } - - public void handleBroadcast(Intent intent) { - String action = intent.getAction(); - if (action.equals(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION)) { - updateNetworkName(intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false), - intent.getStringExtra(TelephonyIntents.EXTRA_SPN), - intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false), - intent.getStringExtra(TelephonyIntents.EXTRA_PLMN)); - notifyListenersIfNecessary(); - } else if (action.equals(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)) { - updateDataSim(); - } - } - - private void updateDataSim() { - int defaultDataSub = SubscriptionManager.getDefaultDataSubId(); - if (SubscriptionManager.isValidSubscriptionId(defaultDataSub)) { - mCurrentState.dataSim = defaultDataSub == mSubscriptionInfo.getSubscriptionId(); - } else { - // There doesn't seem to be a data sim selected, however if - // there isn't a MobileSignalController with dataSim set, then - // QS won't get any callbacks and will be blank. Instead - // lets just assume we are the data sim (which will basically - // show one at random) in QS until one is selected. The user - // should pick one soon after, so we shouldn't be in this state - // for long. - mCurrentState.dataSim = true; - } - notifyListenersIfNecessary(); - } - - /** - * Updates the network's name based on incoming spn and plmn. - */ - void updateNetworkName(boolean showSpn, String spn, boolean showPlmn, String plmn) { - if (CHATTY) { - Log.d("CarrierLabel", "updateNetworkName showSpn=" + showSpn + " spn=" + spn - + " showPlmn=" + showPlmn + " plmn=" + plmn); - } - StringBuilder str = new StringBuilder(); - if (showPlmn && plmn != null) { - str.append(plmn); - } - if (showSpn && spn != null) { - if (str.length() != 0) { - str.append(mNetworkNameSeparator); - } - str.append(spn); - } - if (str.length() != 0) { - mCurrentState.networkName = str.toString(); - } else { - mCurrentState.networkName = mNetworkNameDefault; - } - } - - /** - * Updates the current state based on mServiceState, mSignalStrength, mDataNetType, - * mDataState, and mSimState. It should be called any time one of these is updated. - * This will call listeners if necessary. - */ - private final void updateTelephony() { - if (DEBUG) { - Log.d(TAG, "updateTelephonySignalStrength: hasService=" + hasService() - + " ss=" + mSignalStrength); - } - mCurrentState.connected = hasService() && mSignalStrength != null; - if (mCurrentState.connected) { - if (!mSignalStrength.isGsm() && mConfig.alwaysShowCdmaRssi) { - mCurrentState.level = mSignalStrength.getCdmaLevel(); - } else { - mCurrentState.level = mSignalStrength.getLevel(); - } - } - if (mNetworkToIconLookup.indexOfKey(mDataNetType) >= 0) { - mCurrentState.iconGroup = mNetworkToIconLookup.get(mDataNetType); - } else { - mCurrentState.iconGroup = mDefaultIcons; - } - mCurrentState.dataConnected = mCurrentState.connected - && mDataState == TelephonyManager.DATA_CONNECTED; - - if (isRoaming()) { - mCurrentState.iconGroup = TelephonyIcons.ROAMING; - } - if (isEmergencyOnly() != mCurrentState.isEmergency) { - mCurrentState.isEmergency = isEmergencyOnly(); - mNetworkController.recalculateEmergency(); - } - // Fill in the network name if we think we have it. - if (mCurrentState.networkName == mNetworkNameDefault && mServiceState != null - && mServiceState.getOperatorAlphaShort() != null) { - mCurrentState.networkName = mServiceState.getOperatorAlphaShort(); - } - notifyListenersIfNecessary(); - } - - @VisibleForTesting - void setActivity(int activity) { - mCurrentState.activityIn = activity == TelephonyManager.DATA_ACTIVITY_INOUT - || activity == TelephonyManager.DATA_ACTIVITY_IN; - mCurrentState.activityOut = activity == TelephonyManager.DATA_ACTIVITY_INOUT - || activity == TelephonyManager.DATA_ACTIVITY_OUT; - notifyListenersIfNecessary(); - } - - @Override - public void dump(PrintWriter pw) { - super.dump(pw); - pw.println(" mSubscription=" + mSubscriptionInfo + ","); - pw.println(" mServiceState=" + mServiceState + ","); - pw.println(" mSignalStrength=" + mSignalStrength + ","); - pw.println(" mDataState=" + mDataState + ","); - pw.println(" mDataNetType=" + mDataNetType + ","); - } - - class MobilePhoneStateListener extends PhoneStateListener { - public MobilePhoneStateListener(int subId) { - super(subId); - } - - @Override - public void onSignalStrengthsChanged(SignalStrength signalStrength) { - if (DEBUG) { - Log.d(mTag, "onSignalStrengthsChanged signalStrength=" + signalStrength + - ((signalStrength == null) ? "" : (" level=" + signalStrength.getLevel()))); - } - mSignalStrength = signalStrength; - updateTelephony(); - } - - @Override - public void onServiceStateChanged(ServiceState state) { - if (DEBUG) { - Log.d(mTag, "onServiceStateChanged voiceState=" + state.getVoiceRegState() - + " dataState=" + state.getDataRegState()); - } - mServiceState = state; - updateTelephony(); - } - - @Override - public void onDataConnectionStateChanged(int state, int networkType) { - if (DEBUG) { - Log.d(mTag, "onDataConnectionStateChanged: state=" + state - + " type=" + networkType); - } - mDataState = state; - mDataNetType = networkType; - updateTelephony(); - } - - @Override - public void onDataActivity(int direction) { - if (DEBUG) { - Log.d(mTag, "onDataActivity: direction=" + direction); - } - setActivity(direction); - } - }; - - static class MobileIconGroup extends SignalController.IconGroup { - final int mDataContentDescription; // mContentDescriptionDataType - final int mDataType; - final boolean mIsWide; - final int[] mQsDataType; - - public MobileIconGroup(String name, int[][] sbIcons, int[][] qsIcons, int[] contentDesc, - int sbNullState, int qsNullState, int sbDiscState, int qsDiscState, - int discContentDesc, int dataContentDesc, int dataType, boolean isWide, - int[] qsDataType) { - super(name, sbIcons, qsIcons, contentDesc, sbNullState, qsNullState, sbDiscState, - qsDiscState, discContentDesc); - mDataContentDescription = dataContentDesc; - mDataType = dataType; - mIsWide = isWide; - mQsDataType = qsDataType; - } - } - - static class MobileState extends SignalController.State { - String networkName; - boolean dataSim; - boolean dataConnected; - boolean isEmergency; - boolean airplaneMode; - int inetForNetwork; - - @Override - public void copyFrom(State s) { - super.copyFrom(s); - MobileState state = (MobileState) s; - dataSim = state.dataSim; - networkName = state.networkName; - dataConnected = state.dataConnected; - inetForNetwork = state.inetForNetwork; - isEmergency = state.isEmergency; - airplaneMode = state.airplaneMode; - } - - @Override - protected void toString(StringBuilder builder) { - super.toString(builder); - builder.append(','); - builder.append("dataSim=").append(dataSim).append(','); - builder.append("networkName=").append(networkName).append(','); - builder.append("dataConnected=").append(dataConnected).append(','); - builder.append("inetForNetwork=").append(inetForNetwork).append(','); - builder.append("isEmergency=").append(isEmergency).append(','); - builder.append("airplaneMode=").append(airplaneMode); - } - - @Override - public boolean equals(Object o) { - return super.equals(o) - && Objects.equals(((MobileState) o).networkName, networkName) - && ((MobileState) o).dataSim == dataSim - && ((MobileState) o).dataConnected == dataConnected - && ((MobileState) o).isEmergency == isEmergency - && ((MobileState) o).airplaneMode == airplaneMode - && ((MobileState) o).inetForNetwork == inetForNetwork; - } - } - } - - /** - * Common base class for handling signal for both wifi and mobile data. - */ - static abstract class SignalController<T extends SignalController.State, - I extends SignalController.IconGroup> { - protected final String mTag; - protected final T mCurrentState; - protected final T mLastState; - protected final int mTransportType; - protected final Context mContext; - // The owner of the SignalController (i.e. NetworkController will maintain the following - // lists and call notifyListeners whenever the list has changed to ensure everyone - // is aware of current state. - protected final List<NetworkSignalChangedCallback> mSignalsChangedCallbacks; - protected final List<SignalCluster> mSignalClusters; - protected final NetworkControllerImpl mNetworkController; - - // Save the previous HISTORY_SIZE states for logging. - private final State[] mHistory; - // Where to copy the next state into. - private int mHistoryIndex; - - public SignalController(String tag, Context context, int type, - List<NetworkSignalChangedCallback> signalCallbacks, - List<SignalCluster> signalClusters, NetworkControllerImpl networkController) { - mTag = TAG + "." + tag; - mNetworkController = networkController; - mTransportType = type; - mContext = context; - mSignalsChangedCallbacks = signalCallbacks; - mSignalClusters = signalClusters; - mCurrentState = cleanState(); - mLastState = cleanState(); - if (RECORD_HISTORY) { - mHistory = new State[HISTORY_SIZE]; - for (int i = 0; i < HISTORY_SIZE; i++) { - mHistory[i] = cleanState(); - } - } - } - - public T getState() { - return mCurrentState; - } - - public int getTransportType() { - return mTransportType; - } - - public void setInetCondition(int inetCondition) { - mCurrentState.inetCondition = inetCondition; - notifyListenersIfNecessary(); - } - - /** - * Used at the end of demo mode to clear out any ugly state that it has created. - * Since we haven't had any callbacks, then isDirty will not have been triggered, - * so we can just take the last good state directly from there. - * - * Used for demo mode. - */ - void resetLastState() { - mCurrentState.copyFrom(mLastState); - } - - /** - * Determines if the state of this signal controller has changed and - * needs to trigger callbacks related to it. - */ - public boolean isDirty() { - if (!mLastState.equals(mCurrentState)) { - if (DEBUG) { - Log.d(mTag, "Change in state from: " + mLastState + "\n" - + "\tto: " + mCurrentState); - } - return true; - } - return false; - } - - public void saveLastState() { - if (RECORD_HISTORY) { - recordLastState(); - } - // Updates the current time. - mCurrentState.time = System.currentTimeMillis(); - mLastState.copyFrom(mCurrentState); - } - - /** - * Gets the signal icon for QS based on current state of connected, enabled, and level. - */ - public int getQsCurrentIconId() { - if (mCurrentState.connected) { - return getIcons().mQsIcons[mCurrentState.inetCondition][mCurrentState.level]; - } else if (mCurrentState.enabled) { - return getIcons().mQsDiscState; - } else { - return getIcons().mQsNullState; - } - } - - /** - * Gets the signal icon for SB based on current state of connected, enabled, and level. - */ - public int getCurrentIconId() { - if (mCurrentState.connected) { - return getIcons().mSbIcons[mCurrentState.inetCondition][mCurrentState.level]; - } else if (mCurrentState.enabled) { - return getIcons().mSbDiscState; - } else { - return getIcons().mSbNullState; - } - } - - /** - * Gets the content description id for the signal based on current state of connected and - * level. - */ - public int getContentDescription() { - if (mCurrentState.connected) { - return getIcons().mContentDesc[mCurrentState.level]; - } else { - return getIcons().mDiscContentDesc; - } - } - - public void notifyListenersIfNecessary() { - if (isDirty()) { - saveLastState(); - notifyListeners(); - mNetworkController.refreshCarrierLabel(); - } - } - - /** - * Returns the resource if resId is not 0, and an empty string otherwise. - */ - protected String getStringIfExists(int resId) { - return resId != 0 ? mContext.getString(resId) : ""; - } - - protected I getIcons() { - return (I) mCurrentState.iconGroup; - } - - /** - * Saves the last state of any changes, so we can log the current - * and last value of any state data. - */ - protected void recordLastState() { - mHistory[mHistoryIndex++ & (HISTORY_SIZE - 1)].copyFrom(mLastState); - } - - public void dump(PrintWriter pw) { - pw.println(" - " + mTag + " -----"); - pw.println(" Current State: " + mCurrentState); - if (RECORD_HISTORY) { - // Count up the states that actually contain time stamps, and only display those. - int size = 0; - for (int i = 0; i < HISTORY_SIZE; i++) { - if (mHistory[i].time != 0) size++; - } - // Print out the previous states in ordered number. - for (int i = mHistoryIndex + HISTORY_SIZE - 1; - i >= mHistoryIndex + HISTORY_SIZE - size; i--) { - pw.println(" Previous State(" + (mHistoryIndex + HISTORY_SIZE - i) + ": " - + mHistory[i & (HISTORY_SIZE - 1)]); - } - } - } - - /** - * Trigger callbacks based on current state. The callbacks should be completely - * based on current state, and only need to be called in the scenario where - * mCurrentState != mLastState. - */ - public abstract void notifyListeners(); - - /** - * Generate a blank T. - */ - protected abstract T cleanState(); - - /* - * Holds icons for a given state. Arrays are generally indexed as inet - * state (full connectivity or not) first, and second dimension as - * signal strength. - */ - static class IconGroup { - final int[][] mSbIcons; - final int[][] mQsIcons; - final int[] mContentDesc; - final int mSbNullState; - final int mQsNullState; - final int mSbDiscState; - final int mQsDiscState; - final int mDiscContentDesc; - // For logging. - final String mName; - - public IconGroup(String name, int[][] sbIcons, int[][] qsIcons, int[] contentDesc, - int sbNullState, int qsNullState, int sbDiscState, int qsDiscState, - int discContentDesc) { - mName = name; - mSbIcons = sbIcons; - mQsIcons = qsIcons; - mContentDesc = contentDesc; - mSbNullState = sbNullState; - mQsNullState = qsNullState; - mSbDiscState = sbDiscState; - mQsDiscState = qsDiscState; - mDiscContentDesc = discContentDesc; - } - - @Override - public String toString() { - return "IconGroup(" + mName + ")"; - } - } - - static class State { - boolean connected; - boolean enabled; - boolean activityIn; - boolean activityOut; - int level; - IconGroup iconGroup; - int inetCondition; - int rssi; // Only for logging. - - // Not used for comparison, just used for logging. - long time; - - public void copyFrom(State state) { - connected = state.connected; - enabled = state.enabled; - level = state.level; - iconGroup = state.iconGroup; - inetCondition = state.inetCondition; - activityIn = state.activityIn; - activityOut = state.activityOut; - rssi = state.rssi; - time = state.time; - } - - @Override - public String toString() { - if (time != 0) { - StringBuilder builder = new StringBuilder(); - toString(builder); - return builder.toString(); - } else { - return "Empty " + getClass().getSimpleName(); - } - } - - protected void toString(StringBuilder builder) { - builder.append("connected=").append(connected).append(',') - .append("enabled=").append(enabled).append(',') - .append("level=").append(level).append(',') - .append("inetCondition=").append(inetCondition).append(',') - .append("iconGroup=").append(iconGroup).append(',') - .append("activityIn=").append(activityIn).append(',') - .append("activityOut=").append(activityOut).append(',') - .append("rssi=").append(rssi).append(',') - .append("lastModified=").append(DateFormat.format("MM-dd hh:mm:ss", time)); - } - - @Override - public boolean equals(Object o) { - if (!o.getClass().equals(getClass())) { - return false; - } - State other = (State) o; - return other.connected == connected - && other.enabled == enabled - && other.level == level - && other.inetCondition == inetCondition - && other.iconGroup == iconGroup - && other.activityIn == activityIn - && other.activityOut == activityOut - && other.rssi == rssi; - } - } - } - public interface SignalCluster { void setWifiIndicators(boolean visible, int strengthIcon, String contentDescription); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PreviewInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PreviewInflater.java index 030cd6d..34068fd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PreviewInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PreviewInflater.java @@ -20,7 +20,6 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; -import android.os.UserHandle; import android.util.Log; import android.view.LayoutInflater; import android.view.View; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java new file mode 100644 index 0000000..7d721c2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.policy; + +import com.android.systemui.R; + +import android.annotation.NonNull; +import android.app.Notification; +import android.app.PendingIntent; +import android.app.RemoteInput; +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.util.AttributeSet; +import android.util.Log; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.ProgressBar; +import android.widget.TextView; + +/** + * Host for the remote input. + */ +public class RemoteInputView extends FrameLayout implements View.OnClickListener { + + private static final String TAG = "RemoteInput"; + + private RemoteEditText mEditText; + private ProgressBar mProgressBar; + private PendingIntent mPendingIntent; + private RemoteInput mRemoteInput; + private Notification.Action mAction; + + public RemoteInputView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + mProgressBar = (ProgressBar) findViewById(R.id.remote_input_progress); + + mEditText = (RemoteEditText) getChildAt(0); + mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + + // Check if this was the result of hitting the enter key + final boolean isSoftImeEvent = event == null + && (actionId == EditorInfo.IME_ACTION_DONE + || actionId == EditorInfo.IME_ACTION_NEXT + || actionId == EditorInfo.IME_ACTION_SEND); + final boolean isKeyboardEnterKey = event != null + && KeyEvent.isConfirmKey(event.getKeyCode()) + && event.getAction() == KeyEvent.ACTION_DOWN; + + if (isSoftImeEvent || isKeyboardEnterKey) { + sendRemoteInput(); + return true; + } + return false; + } + }); + mEditText.setOnClickListener(this); + mEditText.setInnerFocusable(false); + } + + private void sendRemoteInput() { + Bundle results = new Bundle(); + results.putString(mRemoteInput.getResultKey(), mEditText.getText().toString()); + Intent fillInIntent = new Intent(); + RemoteInput.addResultsToIntent(mAction.getRemoteInputs(), fillInIntent, + results); + + mEditText.setEnabled(false); + mProgressBar.setVisibility(VISIBLE); + + try { + mPendingIntent.send(mContext, 0, fillInIntent); + } catch (PendingIntent.CanceledException e) { + Log.i(TAG, "Unable to send remote input result", e); + } + } + + public static RemoteInputView inflate(Context context, ViewGroup root, + Notification.Action action, RemoteInput remoteInput) { + RemoteInputView v = (RemoteInputView) + LayoutInflater.from(context).inflate(R.layout.remote_input, root, false); + + v.mEditText.setHint(action.title); + v.mPendingIntent = action.actionIntent; + v.mRemoteInput = remoteInput; + v.mAction = action; + + return v; + } + + @Override + public void onClick(View v) { + if (v == mEditText) { + if (!mEditText.isFocusable()) { + mEditText.setInnerFocusable(true); + InputMethodManager imm = InputMethodManager.getInstance(); + if (imm != null) { + imm.viewClicked(mEditText); + imm.showSoftInput(mEditText, 0); + } + } + } + } + + /** + * An EditText that changes appearance based on whether it's focusable and becomes + * un-focusable whenever the user navigates away from it or it becomes invisible. + */ + public static class RemoteEditText extends EditText { + + private final Drawable mBackground; + + public RemoteEditText(Context context, AttributeSet attrs) { + super(context, attrs); + mBackground = getBackground(); + } + + private void defocusIfNeeded() { + if (isFocusable() && isEnabled()) { + setInnerFocusable(false); + } + } + + @Override + protected void onVisibilityChanged(View changedView, int visibility) { + super.onVisibilityChanged(changedView, visibility); + + if (!isShown()) { + defocusIfNeeded(); + } + } + + @Override + protected void onFocusLost() { + super.onFocusLost(); + defocusIfNeeded(); + } + + @Override + public boolean onKeyPreIme(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + defocusIfNeeded(); + } + return super.onKeyPreIme(keyCode, event); + } + + + void setInnerFocusable(boolean focusable) { + setFocusableInTouchMode(focusable); + setFocusable(focusable); + setCursorVisible(focusable); + + if (focusable) { + requestFocus(); + setBackground(mBackground); + } else { + setBackground(null); + } + + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java new file mode 100644 index 0000000..1d96c6b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java @@ -0,0 +1,323 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.policy; + +import static com.android.systemui.statusbar.policy.NetworkControllerImpl.TAG; + +import android.content.Context; +import android.text.format.DateFormat; +import android.util.Log; + +import com.android.systemui.statusbar.policy.NetworkController.NetworkSignalChangedCallback; +import com.android.systemui.statusbar.policy.NetworkControllerImpl.SignalCluster; + +import java.io.PrintWriter; +import java.util.List; + + +/** + * Common base class for handling signal for both wifi and mobile data. + */ +public abstract class SignalController<T extends SignalController.State, + I extends SignalController.IconGroup> { + // Save the previous SignalController.States of all SignalControllers for dumps. + static final boolean RECORD_HISTORY = true; + // If RECORD_HISTORY how many to save, must be a power of 2. + static final int HISTORY_SIZE = 64; + + protected static final boolean DEBUG = NetworkControllerImpl.DEBUG; + protected static final boolean CHATTY = NetworkControllerImpl.CHATTY; + + protected final String mTag; + protected final T mCurrentState; + protected final T mLastState; + protected final int mTransportType; + protected final Context mContext; + // The owner of the SignalController (i.e. NetworkController will maintain the following + // lists and call notifyListeners whenever the list has changed to ensure everyone + // is aware of current state. + protected final List<NetworkSignalChangedCallback> mSignalsChangedCallbacks; + protected final List<SignalCluster> mSignalClusters; + protected final NetworkControllerImpl mNetworkController; + + // Save the previous HISTORY_SIZE states for logging. + private final State[] mHistory; + // Where to copy the next state into. + private int mHistoryIndex; + + public SignalController(String tag, Context context, int type, + List<NetworkSignalChangedCallback> signalCallbacks, + List<SignalCluster> signalClusters, NetworkControllerImpl networkController) { + mTag = TAG + "." + tag; + mNetworkController = networkController; + mTransportType = type; + mContext = context; + mSignalsChangedCallbacks = signalCallbacks; + mSignalClusters = signalClusters; + mCurrentState = cleanState(); + mLastState = cleanState(); + if (RECORD_HISTORY) { + mHistory = new State[HISTORY_SIZE]; + for (int i = 0; i < HISTORY_SIZE; i++) { + mHistory[i] = cleanState(); + } + } + } + + public T getState() { + return mCurrentState; + } + + public int getTransportType() { + return mTransportType; + } + + public void setInetCondition(int inetCondition) { + mCurrentState.inetCondition = inetCondition; + notifyListenersIfNecessary(); + } + + /** + * Used at the end of demo mode to clear out any ugly state that it has created. + * Since we haven't had any callbacks, then isDirty will not have been triggered, + * so we can just take the last good state directly from there. + * + * Used for demo mode. + */ + public void resetLastState() { + mCurrentState.copyFrom(mLastState); + } + + /** + * Determines if the state of this signal controller has changed and + * needs to trigger callbacks related to it. + */ + public boolean isDirty() { + if (!mLastState.equals(mCurrentState)) { + if (DEBUG) { + Log.d(mTag, "Change in state from: " + mLastState + "\n" + + "\tto: " + mCurrentState); + } + return true; + } + return false; + } + + public void saveLastState() { + if (RECORD_HISTORY) { + recordLastState(); + } + // Updates the current time. + mCurrentState.time = System.currentTimeMillis(); + mLastState.copyFrom(mCurrentState); + } + + /** + * Gets the signal icon for QS based on current state of connected, enabled, and level. + */ + public int getQsCurrentIconId() { + if (mCurrentState.connected) { + return getIcons().mQsIcons[mCurrentState.inetCondition][mCurrentState.level]; + } else if (mCurrentState.enabled) { + return getIcons().mQsDiscState; + } else { + return getIcons().mQsNullState; + } + } + + /** + * Gets the signal icon for SB based on current state of connected, enabled, and level. + */ + public int getCurrentIconId() { + if (mCurrentState.connected) { + return getIcons().mSbIcons[mCurrentState.inetCondition][mCurrentState.level]; + } else if (mCurrentState.enabled) { + return getIcons().mSbDiscState; + } else { + return getIcons().mSbNullState; + } + } + + /** + * Gets the content description id for the signal based on current state of connected and + * level. + */ + public int getContentDescription() { + if (mCurrentState.connected) { + return getIcons().mContentDesc[mCurrentState.level]; + } else { + return getIcons().mDiscContentDesc; + } + } + + public void notifyListenersIfNecessary() { + if (isDirty()) { + saveLastState(); + notifyListeners(); + } + } + + /** + * Returns the resource if resId is not 0, and an empty string otherwise. + */ + protected String getStringIfExists(int resId) { + return resId != 0 ? mContext.getString(resId) : ""; + } + + protected I getIcons() { + return (I) mCurrentState.iconGroup; + } + + /** + * Saves the last state of any changes, so we can log the current + * and last value of any state data. + */ + protected void recordLastState() { + mHistory[mHistoryIndex++ & (HISTORY_SIZE - 1)].copyFrom(mLastState); + } + + public void dump(PrintWriter pw) { + pw.println(" - " + mTag + " -----"); + pw.println(" Current State: " + mCurrentState); + if (RECORD_HISTORY) { + // Count up the states that actually contain time stamps, and only display those. + int size = 0; + for (int i = 0; i < HISTORY_SIZE; i++) { + if (mHistory[i].time != 0) size++; + } + // Print out the previous states in ordered number. + for (int i = mHistoryIndex + HISTORY_SIZE - 1; + i >= mHistoryIndex + HISTORY_SIZE - size; i--) { + pw.println(" Previous State(" + (mHistoryIndex + HISTORY_SIZE - i) + "): " + + mHistory[i & (HISTORY_SIZE - 1)]); + } + } + } + + /** + * Trigger callbacks based on current state. The callbacks should be completely + * based on current state, and only need to be called in the scenario where + * mCurrentState != mLastState. + */ + public abstract void notifyListeners(); + + /** + * Generate a blank T. + */ + protected abstract T cleanState(); + + /* + * Holds icons for a given state. Arrays are generally indexed as inet + * state (full connectivity or not) first, and second dimension as + * signal strength. + */ + static class IconGroup { + final int[][] mSbIcons; + final int[][] mQsIcons; + final int[] mContentDesc; + final int mSbNullState; + final int mQsNullState; + final int mSbDiscState; + final int mQsDiscState; + final int mDiscContentDesc; + // For logging. + final String mName; + + public IconGroup(String name, int[][] sbIcons, int[][] qsIcons, int[] contentDesc, + int sbNullState, int qsNullState, int sbDiscState, int qsDiscState, + int discContentDesc) { + mName = name; + mSbIcons = sbIcons; + mQsIcons = qsIcons; + mContentDesc = contentDesc; + mSbNullState = sbNullState; + mQsNullState = qsNullState; + mSbDiscState = sbDiscState; + mQsDiscState = qsDiscState; + mDiscContentDesc = discContentDesc; + } + + @Override + public String toString() { + return "IconGroup(" + mName + ")"; + } + } + + static class State { + boolean connected; + boolean enabled; + boolean activityIn; + boolean activityOut; + int level; + IconGroup iconGroup; + int inetCondition; + int rssi; // Only for logging. + + // Not used for comparison, just used for logging. + long time; + + public void copyFrom(State state) { + connected = state.connected; + enabled = state.enabled; + level = state.level; + iconGroup = state.iconGroup; + inetCondition = state.inetCondition; + activityIn = state.activityIn; + activityOut = state.activityOut; + rssi = state.rssi; + time = state.time; + } + + @Override + public String toString() { + if (time != 0) { + StringBuilder builder = new StringBuilder(); + toString(builder); + return builder.toString(); + } else { + return "Empty " + getClass().getSimpleName(); + } + } + + protected void toString(StringBuilder builder) { + builder.append("connected=").append(connected).append(',') + .append("enabled=").append(enabled).append(',') + .append("level=").append(level).append(',') + .append("inetCondition=").append(inetCondition).append(',') + .append("iconGroup=").append(iconGroup).append(',') + .append("activityIn=").append(activityIn).append(',') + .append("activityOut=").append(activityOut).append(',') + .append("rssi=").append(rssi).append(',') + .append("lastModified=").append(DateFormat.format("MM-dd hh:mm:ss", time)); + } + + @Override + public boolean equals(Object o) { + if (!o.getClass().equals(getClass())) { + return false; + } + State other = (State) o; + return other.connected == connected + && other.enabled == enabled + && other.level == level + && other.inetCondition == inetCondition + && other.iconGroup == iconGroup + && other.activityIn == activityIn + && other.activityOut == activityOut + && other.rssi == rssi; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java index 4091619..d266ed8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.policy; import com.android.systemui.R; -import com.android.systemui.statusbar.policy.NetworkControllerImpl.MobileSignalController.MobileIconGroup; +import com.android.systemui.statusbar.policy.MobileSignalController.MobileIconGroup; class TelephonyIcons { //***** Signal strength icons diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java new file mode 100644 index 0000000..a97ca50 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.policy; + +import android.content.Context; +import android.content.Intent; +import android.net.NetworkCapabilities; +import android.net.NetworkInfo; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.os.Handler; +import android.os.Message; +import android.os.Messenger; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.AsyncChannel; +import com.android.systemui.statusbar.policy.NetworkController.NetworkSignalChangedCallback; +import com.android.systemui.statusbar.policy.NetworkControllerImpl.SignalCluster; + +import java.util.List; +import java.util.Objects; + + +public class WifiSignalController extends + SignalController<WifiSignalController.WifiState, SignalController.IconGroup> { + private final WifiManager mWifiManager; + private final AsyncChannel mWifiChannel; + private final boolean mHasMobileData; + + public WifiSignalController(Context context, boolean hasMobileData, + List<NetworkSignalChangedCallback> signalCallbacks, + List<SignalCluster> signalClusters, NetworkControllerImpl networkController) { + super("WifiSignalController", context, NetworkCapabilities.TRANSPORT_WIFI, + signalCallbacks, signalClusters, networkController); + mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + mHasMobileData = hasMobileData; + Handler handler = new WifiHandler(); + mWifiChannel = new AsyncChannel(); + Messenger wifiMessenger = mWifiManager.getWifiServiceMessenger(); + if (wifiMessenger != null) { + mWifiChannel.connect(context, handler, wifiMessenger); + } + // WiFi only has one state. + mCurrentState.iconGroup = mLastState.iconGroup = new IconGroup( + "Wi-Fi Icons", + WifiIcons.WIFI_SIGNAL_STRENGTH, + WifiIcons.QS_WIFI_SIGNAL_STRENGTH, + AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH, + WifiIcons.WIFI_NO_NETWORK, + WifiIcons.QS_WIFI_NO_NETWORK, + WifiIcons.WIFI_NO_NETWORK, + WifiIcons.QS_WIFI_NO_NETWORK, + AccessibilityContentDescriptions.WIFI_NO_CONNECTION + ); + } + + @Override + protected WifiState cleanState() { + return new WifiState(); + } + + @Override + public void notifyListeners() { + // only show wifi in the cluster if connected or if wifi-only + boolean wifiVisible = mCurrentState.enabled + && (mCurrentState.connected || !mHasMobileData); + String wifiDesc = wifiVisible ? mCurrentState.ssid : null; + boolean ssidPresent = wifiVisible && mCurrentState.ssid != null; + String contentDescription = getStringIfExists(getContentDescription()); + int length = mSignalsChangedCallbacks.size(); + for (int i = 0; i < length; i++) { + mSignalsChangedCallbacks.get(i).onWifiSignalChanged(mCurrentState.enabled, + mCurrentState.connected, getQsCurrentIconId(), + ssidPresent && mCurrentState.activityIn, + ssidPresent && mCurrentState.activityOut, contentDescription, wifiDesc); + } + + int signalClustersLength = mSignalClusters.size(); + for (int i = 0; i < signalClustersLength; i++) { + mSignalClusters.get(i).setWifiIndicators(wifiVisible, getCurrentIconId(), + contentDescription); + } + } + + /** + * Extract wifi state directly from broadcasts about changes in wifi state. + */ + public void handleBroadcast(Intent intent) { + String action = intent.getAction(); + if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { + mCurrentState.enabled = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, + WifiManager.WIFI_STATE_UNKNOWN) == WifiManager.WIFI_STATE_ENABLED; + } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { + final NetworkInfo networkInfo = (NetworkInfo) + intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); + mCurrentState.connected = networkInfo != null && networkInfo.isConnected(); + // If Connected grab the signal strength and ssid. + if (mCurrentState.connected) { + // try getting it out of the intent first + WifiInfo info = intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO) != null + ? (WifiInfo) intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO) + : mWifiManager.getConnectionInfo(); + if (info != null) { + mCurrentState.ssid = getSsid(info); + } else { + mCurrentState.ssid = null; + } + } else if (!mCurrentState.connected) { + mCurrentState.ssid = null; + } + } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) { + // Default to -200 as its below WifiManager.MIN_RSSI. + mCurrentState.rssi = intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200); + mCurrentState.level = WifiManager.calculateSignalLevel( + mCurrentState.rssi, WifiIcons.WIFI_LEVEL_COUNT); + } + + notifyListenersIfNecessary(); + } + + private String getSsid(WifiInfo info) { + String ssid = info.getSSID(); + if (ssid != null) { + return ssid; + } + // OK, it's not in the connectionInfo; we have to go hunting for it + List<WifiConfiguration> networks = mWifiManager.getConfiguredNetworks(); + int length = networks.size(); + for (int i = 0; i < length; i++) { + if (networks.get(i).networkId == info.getNetworkId()) { + return networks.get(i).SSID; + } + } + return null; + } + + @VisibleForTesting + void setActivity(int wifiActivity) { + mCurrentState.activityIn = wifiActivity == WifiManager.DATA_ACTIVITY_INOUT + || wifiActivity == WifiManager.DATA_ACTIVITY_IN; + mCurrentState.activityOut = wifiActivity == WifiManager.DATA_ACTIVITY_INOUT + || wifiActivity == WifiManager.DATA_ACTIVITY_OUT; + notifyListenersIfNecessary(); + } + + /** + * Handler to receive the data activity on wifi. + */ + private class WifiHandler extends Handler { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: + if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { + mWifiChannel.sendMessage(Message.obtain(this, + AsyncChannel.CMD_CHANNEL_FULL_CONNECTION)); + } else { + Log.e(mTag, "Failed to connect to wifi"); + } + break; + case WifiManager.DATA_ACTIVITY_NOTIFICATION: + setActivity(msg.arg1); + break; + default: + // Ignore + break; + } + } + } + + static class WifiState extends SignalController.State { + String ssid; + + @Override + public void copyFrom(State s) { + super.copyFrom(s); + WifiState state = (WifiState) s; + ssid = state.ssid; + } + + @Override + protected void toString(StringBuilder builder) { + super.toString(builder); + builder.append(',').append("ssid=").append(ssid); + } + + @Override + public boolean equals(Object o) { + return super.equals(o) + && Objects.equals(((WifiState) o).ssid, ssid); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java index 600b750..0e21457 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java @@ -31,6 +31,7 @@ public interface ZenModeController { void setUserId(int userId); boolean isZenAvailable(); ComponentName getEffectsSuppressor(); + boolean isCountdownConditionSupported(); public static class Callback { public void onZenChanged(int zen) {} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java index 37ed7d8..bea0c86 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java @@ -17,7 +17,6 @@ package com.android.systemui.statusbar.policy; import android.app.AlarmManager; -import android.app.INotificationManager; import android.app.NotificationManager; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -28,8 +27,6 @@ import android.content.IntentFilter; import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; -import android.os.RemoteException; -import android.os.ServiceManager; import android.os.UserHandle; import android.provider.Settings.Global; import android.provider.Settings.Secure; @@ -53,7 +50,7 @@ public class ZenModeControllerImpl implements ZenModeController { private final Context mContext; private final GlobalSetting mModeSetting; private final GlobalSetting mConfigSetting; - private final INotificationManager mNoMan; + private final NotificationManager mNoMan; private final LinkedHashMap<Uri, Condition> mConditions = new LinkedHashMap<Uri, Condition>(); private final AlarmManager mAlarmManager; private final SetupObserver mSetupObserver; @@ -78,8 +75,7 @@ public class ZenModeControllerImpl implements ZenModeController { }; mModeSetting.setListening(true); mConfigSetting.setListening(true); - mNoMan = INotificationManager.Stub.asInterface( - ServiceManager.getService(Context.NOTIFICATION_SERVICE)); + mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); mSetupObserver = new SetupObserver(handler); mSetupObserver.register(); @@ -113,11 +109,7 @@ public class ZenModeControllerImpl implements ZenModeController { @Override public void requestConditions(boolean request) { mRequesting = request; - try { - mNoMan.requestZenModeConditions(mListener, request ? Condition.FLAG_RELEVANT_NOW : 0); - } catch (RemoteException e) { - // noop - } + mNoMan.requestZenModeConditions(mListener, request ? Condition.FLAG_RELEVANT_NOW : 0); if (!mRequesting) { mConditions.clear(); } @@ -125,24 +117,12 @@ public class ZenModeControllerImpl implements ZenModeController { @Override public void setExitCondition(Condition exitCondition) { - try { - mNoMan.setZenModeCondition(exitCondition); - } catch (RemoteException e) { - // noop - } + mNoMan.setZenModeCondition(exitCondition); } @Override public Condition getExitCondition() { - try { - final ZenModeConfig config = mNoMan.getZenModeConfig(); - if (config != null) { - return config.exitCondition; - } - } catch (RemoteException e) { - // noop - } - return null; + return mNoMan.getZenModeCondition(); } @Override @@ -169,6 +149,12 @@ public class ZenModeControllerImpl implements ZenModeController { return NotificationManager.from(mContext).getEffectsSuppressor(); } + @Override + public boolean isCountdownConditionSupported() { + return NotificationManager.from(mContext) + .isSystemConditionProviderEnabled(ZenModeConfig.COUNTDOWN_PATH); + } + private void fireNextAlarmChanged() { for (Callback cb : mCallbacks) { cb.onNextAlarmChanged(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java new file mode 100644 index 0000000..3c9e8cf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java @@ -0,0 +1,406 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.stack; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; + +import com.android.systemui.R; +import com.android.systemui.statusbar.ExpandableNotificationRow; +import com.android.systemui.statusbar.ExpandableView; + +import java.util.ArrayList; +import java.util.List; + +/** + * A container containing child notifications + */ +public class NotificationChildrenContainer extends ViewGroup { + + private final int mChildPadding; + private final int mDividerHeight; + private final int mMaxNotificationHeight; + private final List<View> mDividers = new ArrayList<>(); + private final List<ExpandableNotificationRow> mChildren = new ArrayList<>(); + private final View mCollapseButton; + private final View mCollapseDivider; + private final int mCollapseButtonHeight; + private final int mNotificationAppearDistance; + + public NotificationChildrenContainer(Context context) { + this(context, null); + } + + public NotificationChildrenContainer(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + mChildPadding = getResources().getDimensionPixelSize( + R.dimen.notification_children_padding); + mDividerHeight = getResources().getDimensionPixelSize( + R.dimen.notification_children_divider_height); + mMaxNotificationHeight = getResources().getDimensionPixelSize( + R.dimen.notification_max_height); + mNotificationAppearDistance = getResources().getDimensionPixelSize( + R.dimen.notification_appear_distance); + LayoutInflater inflater = mContext.getSystemService(LayoutInflater.class); + mCollapseButton = inflater.inflate(R.layout.notification_collapse_button, this, + false); + mCollapseButtonHeight = getResources().getDimensionPixelSize( + R.dimen.notification_bottom_decor_height); + addView(mCollapseButton); + mCollapseDivider = inflateDivider(); + addView(mCollapseDivider); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + int childCount = mChildren.size(); + boolean firstChild = true; + for (int i = 0; i < childCount; i++) { + View child = mChildren.get(i); + boolean viewGone = child.getVisibility() == View.GONE; + if (i != 0) { + View divider = mDividers.get(i - 1); + int dividerVisibility = divider.getVisibility(); + int newVisibility = viewGone ? INVISIBLE : VISIBLE; + if (dividerVisibility != newVisibility) { + divider.setVisibility(newVisibility); + } + } + if (viewGone) { + continue; + } + child.layout(0, 0, getWidth(), child.getMeasuredHeight()); + if (!firstChild) { + mDividers.get(i - 1).layout(0, 0, getWidth(), mDividerHeight); + } else { + firstChild = false; + } + } + mCollapseButton.layout(0, 0, getWidth(), mCollapseButtonHeight); + mCollapseDivider.layout(0, mCollapseButtonHeight - mDividerHeight, getWidth(), + mCollapseButtonHeight); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int ownMaxHeight = mMaxNotificationHeight; + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY; + boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST; + if (hasFixedHeight || isHeightLimited) { + int size = MeasureSpec.getSize(heightMeasureSpec); + ownMaxHeight = Math.min(ownMaxHeight, size); + } + int newHeightSpec = MeasureSpec.makeMeasureSpec(ownMaxHeight, MeasureSpec.AT_MOST); + int dividerHeightSpec = MeasureSpec.makeMeasureSpec(mDividerHeight, MeasureSpec.EXACTLY); + int collapseButtonHeightSpec = MeasureSpec.makeMeasureSpec(mCollapseButtonHeight, + MeasureSpec.EXACTLY); + mCollapseButton.measure(widthMeasureSpec, collapseButtonHeightSpec); + mCollapseDivider.measure(widthMeasureSpec, dividerHeightSpec); + int height = mCollapseButtonHeight; + int childCount = mChildren.size(); + boolean firstChild = true; + for (int i = 0; i < childCount; i++) { + View child = mChildren.get(i); + if (child.getVisibility() == View.GONE) { + continue; + } + child.measure(widthMeasureSpec, newHeightSpec); + height += child.getMeasuredHeight(); + if (!firstChild) { + // layout the divider + View divider = mDividers.get(i - 1); + divider.measure(widthMeasureSpec, dividerHeightSpec); + height += mChildPadding; + } else { + firstChild = false; + } + } + int width = MeasureSpec.getSize(widthMeasureSpec); + height = hasFixedHeight ? ownMaxHeight + : isHeightLimited ? Math.min(ownMaxHeight, height) + : height; + setMeasuredDimension(width, height); + } + + /** + * Add a child notification to this view. + * + * @param row the row to add + * @param childIndex the index to add it at, if -1 it will be added at the end + */ + public void addNotification(ExpandableNotificationRow row, int childIndex) { + int newIndex = childIndex < 0 ? mChildren.size() : childIndex; + mChildren.add(newIndex, row); + addView(row); + if (mChildren.size() != 1) { + View divider = inflateDivider(); + addView(divider); + mDividers.add(Math.max(newIndex - 1, 0), divider); + } + // TODO: adapt background corners + // TODO: fix overdraw + } + + public void removeNotification(ExpandableNotificationRow row) { + int childIndex = mChildren.indexOf(row); + mChildren.remove(row); + removeView(row); + if (!mDividers.isEmpty()) { + View divider = mDividers.remove(Math.max(childIndex - 1, 0)); + removeView(divider); + } + row.setSystemChildExpanded(false); + // TODO: adapt background corners + } + + private View inflateDivider() { + return LayoutInflater.from(mContext).inflate( + R.layout.notification_children_divider, this, false); + } + + public List<ExpandableNotificationRow> getNotificationChildren() { + return mChildren; + } + + /** + * Apply the order given in the list to the children. + * + * @param childOrder the new list order + * @return whether the list order has changed + */ + public boolean applyChildOrder(List<ExpandableNotificationRow> childOrder) { + if (childOrder == null) { + return false; + } + boolean result = false; + for (int i = 0; i < mChildren.size() && i < childOrder.size(); i++) { + ExpandableNotificationRow child = mChildren.get(i); + ExpandableNotificationRow desiredChild = childOrder.get(i); + if (child != desiredChild) { + mChildren.remove(desiredChild); + mChildren.add(i, desiredChild); + result = true; + } + } + + // Let's make the first child expanded! + boolean first = true; + for (int i = 0; i < childOrder.size(); i++) { + ExpandableNotificationRow child = childOrder.get(i); + child.setSystemChildExpanded(first); + first = false; + } + return result; + } + + public int getIntrinsicHeight() { + int childCount = mChildren.size(); + int intrinsicHeight = 0; + int visibleChildren = 0; + for (int i = 0; i < childCount; i++) { + ExpandableNotificationRow child = mChildren.get(i); + if (child.getVisibility() == View.GONE) { + continue; + } + intrinsicHeight += child.getIntrinsicHeight(); + visibleChildren++; + } + if (visibleChildren > 0) { + intrinsicHeight += (visibleChildren - 1) * mDividerHeight; + } + return intrinsicHeight; + } + + /** + * Update the state of all its children based on a linear layout algorithm. + * + * @param resultState the state to update + * @param parentState the state of the parent + */ + public void getState(StackScrollState resultState, StackViewState parentState) { + int childCount = mChildren.size(); + int yPosition = mCollapseButtonHeight; + boolean firstChild = true; + for (int i = 0; i < childCount; i++) { + ExpandableNotificationRow child = mChildren.get(i); + if (child.getVisibility() == View.GONE) { + continue; + } + if (!firstChild) { + // There's a divider + yPosition += mChildPadding; + } else { + firstChild = false; + } + StackViewState childState = resultState.getViewStateForView(child); + int intrinsicHeight = child.getIntrinsicHeight(); + childState.yTranslation = yPosition; + childState.zTranslation = 0; + childState.height = intrinsicHeight; + childState.dimmed = parentState.dimmed; + childState.dark = parentState.dark; + childState.hideSensitive = parentState.hideSensitive; + childState.belowSpeedBump = parentState.belowSpeedBump; + childState.scale = parentState.scale; + childState.clipTopAmount = 0; + childState.topOverLap = 0; + childState.location = parentState.location; + yPosition += intrinsicHeight; + } + } + + public void applyState(StackScrollState state) { + int childCount = mChildren.size(); + boolean firstChild = true; + ViewState dividerState = new ViewState(); + for (int i = 0; i < childCount; i++) { + ExpandableNotificationRow child = mChildren.get(i); + StackViewState viewState = state.getViewStateForView(child); + if (child.getVisibility() == View.GONE) { + continue; + } + if (!firstChild) { + // layout the divider + View divider = mDividers.get(i - 1); + dividerState.initFrom(divider); + dividerState.yTranslation = (int) (viewState.yTranslation + - (mChildPadding + mDividerHeight) / 2.0f); + dividerState.alpha = 1; + state.applyViewState(divider, dividerState); + } else { + firstChild = false; + } + state.applyState(child, viewState); + } + } + + public void setCollapseClickListener(OnClickListener collapseClickListener) { + mCollapseButton.setOnClickListener(collapseClickListener); + } + + /** + * This is called when the children expansion has changed and positions the children properly + * for an appear animation. + * + * @param state the new state we animate to + */ + public void prepareExpansionChanged(StackScrollState state) { + int childCount = mChildren.size(); + boolean firstChild = true; + StackViewState sourceState = new StackViewState(); + ViewState dividerState = new ViewState(); + for (int i = 0; i < childCount; i++) { + ExpandableNotificationRow child = mChildren.get(i); + StackViewState viewState = state.getViewStateForView(child); + if (child.getVisibility() == View.GONE) { + continue; + } + if (!firstChild) { + // layout the divider + View divider = mDividers.get(i - 1); + dividerState.initFrom(divider); + dividerState.yTranslation = viewState.yTranslation + - (mChildPadding + mDividerHeight) / 2.0f + mNotificationAppearDistance; + dividerState.alpha = 0; + state.applyViewState(divider, dividerState); + } else { + firstChild = false; + } + sourceState.copyFrom(viewState); + sourceState.alpha = 0; + sourceState.yTranslation += mNotificationAppearDistance; + state.applyState(child, sourceState); + } + mCollapseButton.setAlpha(0); + mCollapseDivider.setAlpha(0); + mCollapseDivider.setTranslationY(mNotificationAppearDistance / 4); + } + + public void startAnimationToState(StackScrollState state, StackStateAnimator stateAnimator, + boolean withDelays, long baseDelay, long duration) { + int childCount = mChildren.size(); + boolean firstChild = true; + ViewState dividerState = new ViewState(); + int notGoneIndex = 0; + for (int i = 0; i < childCount; i++) { + ExpandableNotificationRow child = mChildren.get(i); + StackViewState viewState = state.getViewStateForView(child); + if (child.getVisibility() == View.GONE) { + continue; + } + int difference = Math.min(StackStateAnimator.DELAY_EFFECT_MAX_INDEX_DIFFERENCE_CHILDREN, + notGoneIndex + 1); + long delay = withDelays + ? difference * StackStateAnimator.ANIMATION_DELAY_PER_ELEMENT_EXPAND_CHILDREN + : 0; + delay += baseDelay; + if (!firstChild) { + // layout the divider + View divider = mDividers.get(i - 1); + dividerState.initFrom(divider); + dividerState.yTranslation = viewState.yTranslation + - (mChildPadding + mDividerHeight) / 2.0f; + dividerState.alpha = 1; + stateAnimator.startViewAnimations(divider, dividerState, delay, duration); + } else { + firstChild = false; + } + stateAnimator.startStackAnimations(child, viewState, state, -1, delay); + notGoneIndex++; + } + dividerState.initFrom(mCollapseButton); + dividerState.alpha = 1.0f; + stateAnimator.startViewAnimations(mCollapseButton, dividerState, baseDelay, duration); + dividerState.initFrom(mCollapseDivider); + dividerState.alpha = 1.0f; + dividerState.yTranslation = 0.0f; + stateAnimator.startViewAnimations(mCollapseDivider, dividerState, baseDelay, duration); + } + + public ExpandableNotificationRow getViewAtPosition(float y) { + // find the view under the pointer, accounting for GONE views + final int count = mChildren.size(); + for (int childIdx = 0; childIdx < count; childIdx++) { + ExpandableNotificationRow slidingChild = mChildren.get(childIdx); + float childTop = slidingChild.getTranslationY(); + float top = childTop + slidingChild.getClipTopAmount(); + float bottom = childTop + slidingChild.getActualHeight(); + if (y >= top && y <= bottom) { + return slidingChild; + } + } + return null; + } + + public void setTintColor(int color) { + ExpandableNotificationRow.applyTint(mCollapseDivider, color); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index 6dcbed6..2eafd57 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -41,12 +41,13 @@ import com.android.systemui.statusbar.DismissView; import com.android.systemui.statusbar.EmptyShadeView; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.ExpandableView; +import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.SpeedBumpView; import com.android.systemui.statusbar.StackScrollerDecorView; import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.PhoneStatusBar; import com.android.systemui.statusbar.policy.ScrollAdapter; -import com.android.systemui.statusbar.stack.StackScrollState.ViewState; import java.util.ArrayList; import java.util.HashSet; @@ -56,7 +57,7 @@ import java.util.HashSet; */ public class NotificationStackScrollLayout extends ViewGroup implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter, - ExpandableView.OnHeightChangedListener { + ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener { private static final String TAG = "NotificationStackScrollLayout"; private static final boolean DEBUG = false; @@ -119,6 +120,7 @@ public class NotificationStackScrollLayout extends ViewGroup */ private StackScrollState mCurrentStackScrollState = new StackScrollState(this); private AmbientState mAmbientState = new AmbientState(); + private NotificationGroupManager mGroupManager; private ArrayList<View> mChildrenToAddAnimated = new ArrayList<View>(); private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<View>(); private ArrayList<View> mSnappedBackChildren = new ArrayList<View>(); @@ -181,6 +183,7 @@ public class NotificationStackScrollLayout extends ViewGroup private boolean mDontReportNextOverScroll; private boolean mRequestViewResizeAnimationOnLayout; private boolean mNeedViewResizeAnimation; + private View mExpandedGroupView; private boolean mEverythingNeedsAnimation; /** @@ -214,6 +217,8 @@ public class NotificationStackScrollLayout extends ViewGroup }; private PhoneStatusBar mPhoneStatusBar; private int[] mTempInt2 = new int[2]; + private boolean mGenerateChildOrderChangedEvent; + private boolean mRemoveAnimationEnabled; public NotificationStackScrollLayout(Context context) { this(context, null); @@ -309,7 +314,7 @@ public class NotificationStackScrollLayout extends ViewGroup private void notifyHeightChangeListener(ExpandableView view) { if (mOnHeightChangedListener != null) { - mOnHeightChangedListener.onHeightChanged(view); + mOnHeightChangedListener.onHeightChanged(view, false /* needsAnimation */); } } @@ -329,6 +334,9 @@ public class NotificationStackScrollLayout extends ViewGroup float centerX = getWidth() / 2.0f; for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); + if (child.getVisibility() == GONE) { + continue; + } float width = child.getMeasuredWidth(); float height = child.getMeasuredHeight(); child.layout((int) (centerX - width / 2.0f), @@ -339,16 +347,18 @@ public class NotificationStackScrollLayout extends ViewGroup setMaxLayoutHeight(getHeight()); updateContentHeight(); clampScrollPosition(); - requestAnimationOnViewResize(); + if (mRequestViewResizeAnimationOnLayout) { + requestAnimationOnViewResize(); + mRequestViewResizeAnimationOnLayout = false; + } requestChildrenUpdate(); } private void requestAnimationOnViewResize() { - if (mRequestViewResizeAnimationOnLayout && mIsExpanded && mAnimationsEnabled) { + if (mIsExpanded && mAnimationsEnabled) { mNeedViewResizeAnimation = true; mNeedsAnimation = true; } - mRequestViewResizeAnimationOnLayout = false; } public void updateSpeedBumpIndex(int newIndex) { @@ -375,15 +385,15 @@ public class NotificationStackScrollLayout extends ViewGroup * Returns the location the given child is currently rendered at. * * @param child the child to get the location for - * @return one of {@link ViewState}'s <code>LOCATION_*</code> constants + * @return one of {@link StackViewState}'s <code>LOCATION_*</code> constants */ public int getChildLocation(View child) { - ViewState childViewState = mCurrentStackScrollState.getViewStateForView(child); + StackViewState childViewState = mCurrentStackScrollState.getViewStateForView(child); if (childViewState == null) { - return ViewState.LOCATION_UNKNOWN; + return StackViewState.LOCATION_UNKNOWN; } if (childViewState.gone) { - return ViewState.LOCATION_GONE; + return StackViewState.LOCATION_GONE; } return childViewState.location; } @@ -645,6 +655,10 @@ public class NotificationStackScrollLayout extends ViewGroup int right = getWidth(); if (touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) { + if (slidingChild instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild; + return row.getViewAtPosition(touchY - childTop); + } return slidingChild; } } @@ -723,7 +737,6 @@ public class NotificationStackScrollLayout extends ViewGroup } public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) { - child.setClipBounds(null); mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration); } @@ -1544,6 +1557,14 @@ public class NotificationStackScrollLayout extends ViewGroup @Override protected void onViewRemoved(View child) { super.onViewRemoved(child); + // we only call our internal methods if this is actually a removal and not just a + // notification which becomes a child notification + if (!isChildInGroup(child)) { + onViewRemovedInternal(child); + } + } + + private void onViewRemovedInternal(View child) { mStackScrollAlgorithm.notifyChildrenChanged(this); if (mChangePositionInProgress) { // This is only a position change, don't do anything special @@ -1552,16 +1573,27 @@ public class NotificationStackScrollLayout extends ViewGroup ((ExpandableView) child).setOnHeightChangedListener(null); mCurrentStackScrollState.removeViewStateForView(child); updateScrollStateForRemovedChild(child); - boolean animationGenerated = generateRemoveAnimation(child); - if (animationGenerated && !mSwipedOutViews.contains(child)) { - // Add this view to an overlay in order to ensure that it will still be temporary - // drawn when removed - getOverlay().add(child); + if (mRemoveAnimationEnabled) { + boolean animationGenerated = generateRemoveAnimation(child); + if (animationGenerated && !mSwipedOutViews.contains(child)) { + // Add this view to an overlay in order to ensure that it will still be temporary + // drawn when removed + getOverlay().add(child); + } + } else { + // TODO: handle this more cleanly when HEADS-up and the shade are merged + requestAnimateEverything(); } updateAnimationState(false, child); // Make sure the clipRect we might have set is removed - child.setClipBounds(null); + ((ExpandableView) child).setClipTopOptimization(0); + } + + private boolean isChildInGroup(View child) { + return child instanceof ExpandableNotificationRow + && mGroupManager.isChildInGroupWithSummary( + ((ExpandableNotificationRow) child).getStatusBarNotification()); } /** @@ -1571,7 +1603,7 @@ public class NotificationStackScrollLayout extends ViewGroup * @return Whether an animation was generated. */ private boolean generateRemoveAnimation(View child) { - if (mIsExpanded && mAnimationsEnabled) { + if (mIsExpanded && mAnimationsEnabled && !isChildInInvisibleGroup(child)) { if (!mChildrenToAddAnimated.contains(child)) { // Generate Animations mChildrenToRemoveAnimated.add(child); @@ -1587,6 +1619,23 @@ public class NotificationStackScrollLayout extends ViewGroup } /** + * @param child the child to query + * @return whether a view is not a top level child but a child notification and that group is + * not expanded + */ + private boolean isChildInInvisibleGroup(View child) { + if (child instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) child; + ExpandableNotificationRow groupSummary = + mGroupManager.getGroupSummary(row.getStatusBarNotification()); + if (groupSummary != null && groupSummary != row) { + return !groupSummary.areChildrenExpanded(); + } + } + return false; + } + + /** * Updates the scroll position when a child was removed * * @param removedChild the removed child @@ -1634,6 +1683,10 @@ public class NotificationStackScrollLayout extends ViewGroup @Override protected void onViewAdded(View child) { super.onViewAdded(child); + onViewAddedInternal(child); + } + + private void onViewAddedInternal(View child) { mStackScrollAlgorithm.notifyChildrenChanged(this); ((ExpandableView) child).setOnHeightChangedListener(this); generateAddAnimation(child, false /* fromMoreCard */); @@ -1646,6 +1699,14 @@ public class NotificationStackScrollLayout extends ViewGroup } } + public void notifyGroupChildRemoved(View row) { + onViewRemovedInternal(row); + } + + public void notifyGroupChildAdded(View row) { + onViewAddedInternal(row); + } + public void setAnimationsEnabled(boolean animationsEnabled) { mAnimationsEnabled = animationsEnabled; updateNotificationAnimationStates(); @@ -1741,10 +1802,20 @@ public class NotificationStackScrollLayout extends ViewGroup generateDarkEvent(); generateGoToFullShadeEvent(); generateViewResizeEvent(); + generateGroupExpansionEvent(); generateAnimateEverythingEvent(); mNeedsAnimation = false; } + private void generateGroupExpansionEvent() { + // Generate a group expansion/collapsing event if there is such a group at all + if (mExpandedGroupView != null) { + mAnimationEvents.add(new AnimationEvent(mExpandedGroupView, + AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED)); + mExpandedGroupView = null; + } + } + private void generateViewResizeEvent() { if (mNeedViewResizeAnimation) { mAnimationEvents.add( @@ -1791,6 +1862,11 @@ public class NotificationStackScrollLayout extends ViewGroup AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION)); } mChildrenChangingPositions.clear(); + if (mGenerateChildOrderChangedEvent) { + mAnimationEvents.add(new AnimationEvent(null, + AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION)); + mGenerateChildOrderChangedEvent = false; + } } private void generateChildAdditionEvents() { @@ -2059,11 +2135,14 @@ public class NotificationStackScrollLayout extends ViewGroup } @Override - public void onHeightChanged(ExpandableView view) { + public void onHeightChanged(ExpandableView view, boolean needsAnimation) { updateContentHeight(); updateScrollPositionOnExpandInBottom(view); clampScrollPosition(); notifyHeightChangeListener(view); + if (needsAnimation) { + requestAnimationOnViewResize(); + } requestChildrenUpdate(); } @@ -2338,6 +2417,20 @@ public class NotificationStackScrollLayout extends ViewGroup public void setDismissAllInProgress(boolean dismissAllInProgress) { mDismissAllInProgress = dismissAllInProgress; mDismissView.setDismissAllInProgress(dismissAllInProgress); + if (dismissAllInProgress) { + disableClipOptimization(); + } + } + + private void disableClipOptimization() { + final int count = getChildCount(); + for (int i = 0; i < count; i++) { + ExpandableView child = (ExpandableView) getChildAt(i); + if (child.getVisibility() == GONE) { + continue; + } + child.setClipTopOptimization(0); + } } public boolean isDismissViewNotGone() { @@ -2392,28 +2485,97 @@ public class NotificationStackScrollLayout extends ViewGroup this.mPhoneStatusBar = phoneStatusBar; } + public void setGroupManager(NotificationGroupManager groupManager) { + this.mGroupManager = groupManager; + } + public void onGoToKeyguard() { + requestAnimateEverything(); + } + + private void requestAnimateEverything() { if (mIsExpanded && mAnimationsEnabled) { mEverythingNeedsAnimation = true; + mNeedsAnimation = true; requestChildrenUpdate(); } } private boolean isBelowLastNotification(float touchX, float touchY) { - ExpandableView lastChildNotGone = (ExpandableView) getLastChildNotGone(); - if (lastChildNotGone == null) { - return touchY > mIntrinsicPadding; - } - if (lastChildNotGone != mDismissView && lastChildNotGone != mEmptyShadeView) { - return touchY > lastChildNotGone.getY() + lastChildNotGone.getActualHeight(); - } else if (lastChildNotGone == mEmptyShadeView) { - return touchY > mEmptyShadeView.getY(); - } else { - float dismissY = mDismissView.getY(); - boolean belowDismissView = touchY > dismissY + mDismissView.getActualHeight(); - return belowDismissView || (touchY > dismissY - && mDismissView.isOnEmptySpace(touchX - mDismissView.getX(), - touchY - dismissY)); + int childCount = getChildCount(); + for (int i = childCount - 1; i >= 0; i--) { + ExpandableView child = (ExpandableView) getChildAt(i); + if (child.getVisibility() != View.GONE) { + float childTop = child.getY(); + if (childTop > touchY) { + // we are above a notification entirely let's abort + return false; + } + boolean belowChild = touchY > childTop + child.getActualHeight(); + if (child == mDismissView) { + if(!belowChild && !mDismissView.isOnEmptySpace(touchX - mDismissView.getX(), + touchY - childTop)) { + // We clicked on the dismiss button + return false; + } + } else if (child == mEmptyShadeView) { + // We arrived at the empty shade view, for which we accept all clicks + return true; + } else if (!belowChild){ + // We are on a child + return false; + } + } + } + return touchY > mIntrinsicPadding; + } + + public void setRemoveAnimationEnabled(boolean enabled) { + mRemoveAnimationEnabled = enabled; + } + + private void updateExpandButtons() { + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + if (child instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) child; + row.updateExpandButton(); + } + } + } + + @Override + public void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded) { + boolean animated = mAnimationsEnabled && mIsExpanded; + if (animated) { + mExpandedGroupView = changedRow; + mNeedsAnimation = true; + } + changedRow.setChildrenExpanded(expanded, animated); + onHeightChanged(changedRow, false /* needsAnimation */); + } + + @Override + public void onGroupsProhibitedChanged() { + updateExpandButtons(); + } + + @Override + public void onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group) { + for (NotificationData.Entry entry : group.children) { + ExpandableNotificationRow row = entry.row; + if (indexOfChild(row) != -1) { + removeView(row); + group.summary.row.addChildNotification(row); + } + } + } + + public void generateChildOrderChangedEvent() { + if (mIsExpanded && mAnimationsEnabled) { + mGenerateChildOrderChangedEvent = true; + mNeedsAnimation = true; + requestChildrenUpdate(); } } @@ -2553,6 +2715,14 @@ public class NotificationStackScrollLayout extends ViewGroup .animateY() .animateZ(), + // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED + new AnimationFilter() + .animateAlpha() + .animateHeight() + .animateTopInset() + .animateY() + .animateZ(), + // ANIMATION_TYPE_EVERYTHING new AnimationFilter() .animateAlpha() @@ -2607,6 +2777,9 @@ public class NotificationStackScrollLayout extends ViewGroup // ANIMATION_TYPE_VIEW_RESIZE StackStateAnimator.ANIMATION_DURATION_STANDARD, + // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED + StackStateAnimator.ANIMATION_DURATION_EXPAND_CLICKED, + // ANIMATION_TYPE_EVERYTHING StackStateAnimator.ANIMATION_DURATION_STANDARD, }; @@ -2624,7 +2797,8 @@ public class NotificationStackScrollLayout extends ViewGroup static final int ANIMATION_TYPE_GO_TO_FULL_SHADE = 10; static final int ANIMATION_TYPE_HIDE_SENSITIVE = 11; static final int ANIMATION_TYPE_VIEW_RESIZE = 12; - static final int ANIMATION_TYPE_EVERYTHING = 13; + static final int ANIMATION_TYPE_GROUP_EXPANSION_CHANGED = 13; + static final int ANIMATION_TYPE_EVERYTHING = 14; static final int DARK_ANIMATION_ORIGIN_INDEX_ABOVE = -1; static final int DARK_ANIMATION_ORIGIN_INDEX_BELOW = -2; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java index ddc4251..e7bf47b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java @@ -27,6 +27,7 @@ import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.ExpandableView; import java.util.ArrayList; +import java.util.List; /** * The Algorithm of the {@link com.android.systemui.statusbar.stack @@ -171,6 +172,19 @@ public class StackScrollAlgorithm { updateDimmedActivatedHideSensitive(ambientState, resultState, algorithmState); updateClipping(resultState, algorithmState); updateSpeedBumpState(resultState, algorithmState, ambientState.getSpeedBumpIndex()); + getNotificationChildrenStates(resultState, algorithmState); + } + + private void getNotificationChildrenStates(StackScrollState resultState, + StackScrollAlgorithmState algorithmState) { + int childCount = algorithmState.visibleChildren.size(); + for (int i = 0; i < childCount; i++) { + ExpandableView v = algorithmState.visibleChildren.get(i); + if (v instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) v; + row.getChildrenStates(resultState); + } + } } private void updateSpeedBumpState(StackScrollState resultState, @@ -178,7 +192,7 @@ public class StackScrollAlgorithm { int childCount = algorithmState.visibleChildren.size(); for (int i = 0; i < childCount; i++) { View child = algorithmState.visibleChildren.get(i); - StackScrollState.ViewState childViewState = resultState.getViewStateForView(child); + StackViewState childViewState = resultState.getViewStateForView(child); // The speed bump can also be gone, so equality needs to be taken when comparing // indices. @@ -194,7 +208,7 @@ public class StackScrollAlgorithm { int childCount = algorithmState.visibleChildren.size(); for (int i = 0; i < childCount; i++) { ExpandableView child = algorithmState.visibleChildren.get(i); - StackScrollState.ViewState state = resultState.getViewStateForView(child); + StackViewState state = resultState.getViewStateForView(child); float newYTranslation = state.yTranslation + state.height * (1f - state.scale) / 2f; float newHeight = state.height * state.scale; // apply clipping and shadow @@ -242,8 +256,8 @@ public class StackScrollAlgorithm { * @param backgroundHeight the desired background height. The shadows of the view will be * based on this height and the content will be clipped from the top */ - private void updateChildClippingAndBackground(StackScrollState.ViewState state, - float realHeight, float clipHeight, float backgroundHeight) { + private void updateChildClippingAndBackground(StackViewState state, float realHeight, + float clipHeight, float backgroundHeight) { if (realHeight > clipHeight) { // Rather overlap than create a hole. state.topOverLap = (int) Math.floor((realHeight - clipHeight) / state.scale); @@ -270,7 +284,7 @@ public class StackScrollAlgorithm { int childCount = algorithmState.visibleChildren.size(); for (int i = 0; i < childCount; i++) { View child = algorithmState.visibleChildren.get(i); - StackScrollState.ViewState childViewState = resultState.getViewStateForView(child); + StackViewState childViewState = resultState.getViewStateForView(child); childViewState.dimmed = dimmed; childViewState.dark = dark; childViewState.hideSensitive = hideSensitive; @@ -297,14 +311,14 @@ public class StackScrollAlgorithm { if (!draggedViews.contains(nextChild)) { // only if the view is not dragged itself we modify its state to be fully // visible - StackScrollState.ViewState viewState = resultState.getViewStateForView( + StackViewState viewState = resultState.getViewStateForView( nextChild); // The child below the dragged one must be fully visible viewState.alpha = 1; } // Lets set the alpha to the one it currently has, as its currently being dragged - StackScrollState.ViewState viewState = resultState.getViewStateForView(draggedView); + StackViewState viewState = resultState.getViewStateForView(draggedView); // The dragged child should keep the set alpha viewState.alpha = draggedView.getAlpha(); } @@ -320,12 +334,31 @@ public class StackScrollAlgorithm { int childCount = hostView.getChildCount(); state.visibleChildren.clear(); state.visibleChildren.ensureCapacity(childCount); + int notGoneIndex = 0; for (int i = 0; i < childCount; i++) { ExpandableView v = (ExpandableView) hostView.getChildAt(i); if (v.getVisibility() != View.GONE) { - StackScrollState.ViewState viewState = resultState.getViewStateForView(v); - viewState.notGoneIndex = state.visibleChildren.size(); + StackViewState viewState = resultState.getViewStateForView(v); + viewState.notGoneIndex = notGoneIndex; state.visibleChildren.add(v); + notGoneIndex++; + + // handle the notgoneIndex for the children as well + if (v instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) v; + List<ExpandableNotificationRow> children = + row.getNotificationChildren(); + if (row.areChildrenExpanded() && children != null) { + for (ExpandableNotificationRow childRow : children) { + if (childRow.getVisibility() != View.GONE) { + StackViewState childState + = resultState.getViewStateForView(childRow); + childState.notGoneIndex = notGoneIndex; + notGoneIndex++; + } + } + } + } } } } @@ -355,8 +388,8 @@ public class StackScrollAlgorithm { int numberOfElementsCompletelyIn = (int) algorithmState.itemsInTopStack; for (int i = 0; i < childCount; i++) { ExpandableView child = algorithmState.visibleChildren.get(i); - StackScrollState.ViewState childViewState = resultState.getViewStateForView(child); - childViewState.location = StackScrollState.ViewState.LOCATION_UNKNOWN; + StackViewState childViewState = resultState.getViewStateForView(child); + childViewState.location = StackViewState.LOCATION_UNKNOWN; int childHeight = getMaxAllowedChildHeight(child); float yPositionInScrollViewAfterElement = yPositionInScrollView + childHeight @@ -413,7 +446,7 @@ public class StackScrollAlgorithm { } else { // Case 3: // We are in the regular scroll area. - childViewState.location = StackScrollState.ViewState.LOCATION_MAIN_AREA; + childViewState.location = StackViewState.LOCATION_MAIN_AREA; clampYTranslation(childViewState, childHeight); } @@ -427,9 +460,9 @@ public class StackScrollAlgorithm { bottomPeekStart - mCollapseSecondCardPadding - childViewState.yTranslation, mCollapsedSize); } - childViewState.location = StackScrollState.ViewState.LOCATION_FIRST_CARD; + childViewState.location = StackViewState.LOCATION_FIRST_CARD; } - if (childViewState.location == StackScrollState.ViewState.LOCATION_UNKNOWN) { + if (childViewState.location == StackViewState.LOCATION_UNKNOWN) { Log.wtf(LOG_TAG, "Failed to assign location for child " + i); } currentYPosition = childViewState.yTranslation + childHeight + mPaddingBetweenElements; @@ -445,7 +478,7 @@ public class StackScrollAlgorithm { * @param childViewState the view state of the child * @param childHeight the height of this child */ - private void clampYTranslation(StackScrollState.ViewState childViewState, int childHeight) { + private void clampYTranslation(StackViewState childViewState, int childHeight) { clampPositionToBottomStackStart(childViewState, childHeight); clampPositionToTopStackEnd(childViewState, childHeight); } @@ -457,7 +490,7 @@ public class StackScrollAlgorithm { * @param childViewState the view state of the child * @param childHeight the height of this child */ - private void clampPositionToBottomStackStart(StackScrollState.ViewState childViewState, + private void clampPositionToBottomStackStart(StackViewState childViewState, int childHeight) { childViewState.yTranslation = Math.min(childViewState.yTranslation, mInnerHeight - mBottomStackPeekSize - mCollapseSecondCardPadding - childHeight); @@ -470,7 +503,7 @@ public class StackScrollAlgorithm { * @param childViewState the view state of the child * @param childHeight the height of this child */ - private void clampPositionToTopStackEnd(StackScrollState.ViewState childViewState, + private void clampPositionToTopStackEnd(StackViewState childViewState, int childHeight) { childViewState.yTranslation = Math.max(childViewState.yTranslation, mCollapsedSize - childHeight); @@ -489,7 +522,7 @@ public class StackScrollAlgorithm { private void updateStateForChildTransitioningInBottom(StackScrollAlgorithmState algorithmState, float transitioningPositionStart, float bottomPeakStart, float currentYPosition, - StackScrollState.ViewState childViewState, int childHeight) { + StackViewState childViewState, int childHeight) { // This is the transitioning element on top of bottom stack, calculate how far we are in. algorithmState.partialInBottom = 1.0f - ( @@ -510,11 +543,11 @@ public class StackScrollAlgorithm { // We want at least to be at the end of the top stack when collapsing clampPositionToTopStackEnd(childViewState, newHeight); - childViewState.location = StackScrollState.ViewState.LOCATION_MAIN_AREA; + childViewState.location = StackViewState.LOCATION_MAIN_AREA; } private void updateStateForChildFullyInBottomStack(StackScrollAlgorithmState algorithmState, - float transitioningPositionStart, StackScrollState.ViewState childViewState, + float transitioningPositionStart, StackViewState childViewState, int childHeight) { float currentYPosition; @@ -524,7 +557,7 @@ public class StackScrollAlgorithm { currentYPosition = transitioningPositionStart + mBottomStackIndentationFunctor.getValue(algorithmState.itemsInBottomStack) - mPaddingBetweenElements; - childViewState.location = StackScrollState.ViewState.LOCATION_BOTTOM_STACK_PEEKING; + childViewState.location = StackViewState.LOCATION_BOTTOM_STACK_PEEKING; } else { // we are fully inside the stack if (algorithmState.itemsInBottomStack > MAX_ITEMS_IN_BOTTOM_STACK + 2) { @@ -533,7 +566,7 @@ public class StackScrollAlgorithm { > MAX_ITEMS_IN_BOTTOM_STACK + 1) { childViewState.alpha = 1.0f - algorithmState.partialInBottom; } - childViewState.location = StackScrollState.ViewState.LOCATION_BOTTOM_STACK_HIDDEN; + childViewState.location = StackViewState.LOCATION_BOTTOM_STACK_HIDDEN; currentYPosition = mInnerHeight; } childViewState.yTranslation = currentYPosition - childHeight; @@ -542,7 +575,7 @@ public class StackScrollAlgorithm { private void updateStateForTopStackChild(StackScrollAlgorithmState algorithmState, int numberOfElementsCompletelyIn, int i, int childHeight, - StackScrollState.ViewState childViewState, float scrollOffset) { + StackViewState childViewState, float scrollOffset) { // First we calculate the index relative to the current stack window of size at most @@ -574,7 +607,7 @@ public class StackScrollAlgorithm { - mTopStackIndentationFunctor.getValue(numItemsBefore); childViewState.yTranslation = currentChildEndY - childHeight; } - childViewState.location = StackScrollState.ViewState.LOCATION_TOP_STACK_PEEKING; + childViewState.location = StackViewState.LOCATION_TOP_STACK_PEEKING; } else { if (paddedIndex == -1) { childViewState.alpha = 1.0f - algorithmState.partialInTop; @@ -583,7 +616,7 @@ public class StackScrollAlgorithm { childViewState.alpha = 0.0f; } childViewState.yTranslation = mCollapsedSize - childHeight; - childViewState.location = StackScrollState.ViewState.LOCATION_TOP_STACK_HIDDEN; + childViewState.location = StackViewState.LOCATION_TOP_STACK_HIDDEN; } @@ -605,7 +638,7 @@ public class StackScrollAlgorithm { // find the number of elements in the top stack. for (int i = 0; i < childCount; i++) { ExpandableView child = algorithmState.visibleChildren.get(i); - StackScrollState.ViewState childViewState = resultState.getViewStateForView(child); + StackViewState childViewState = resultState.getViewStateForView(child); int childHeight = getMaxAllowedChildHeight(child); float yPositionInScrollViewAfterElement = yPositionInScrollView + childHeight @@ -676,7 +709,7 @@ public class StackScrollAlgorithm { int childCount = algorithmState.visibleChildren.size(); for (int i = 0; i < childCount; i++) { View child = algorithmState.visibleChildren.get(i); - StackScrollState.ViewState childViewState = resultState.getViewStateForView(child); + StackViewState childViewState = resultState.getViewStateForView(child); if (i < algorithmState.itemsInTopStack) { float stackIndex = algorithmState.itemsInTopStack - i; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java index 0b1ce8f..feae590 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.stack; -import android.graphics.Rect; import android.util.Log; import android.view.View; import android.view.ViewGroup; @@ -24,10 +23,12 @@ import android.view.ViewGroup; import com.android.systemui.R; import com.android.systemui.statusbar.DismissView; import com.android.systemui.statusbar.EmptyShadeView; +import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.ExpandableView; import com.android.systemui.statusbar.SpeedBumpView; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -39,13 +40,12 @@ public class StackScrollState { private static final String CHILD_NOT_FOUND_TAG = "StackScrollStateNoSuchChild"; private final ViewGroup mHostView; - private Map<ExpandableView, ViewState> mStateMap; - private final Rect mClipRect = new Rect(); + private Map<ExpandableView, StackViewState> mStateMap; private final int mClearAllTopPadding; public StackScrollState(ViewGroup hostView) { mHostView = hostView; - mStateMap = new HashMap<ExpandableView, ViewState>(); + mStateMap = new HashMap<ExpandableView, StackViewState>(); mClearAllTopPadding = hostView.getContext().getResources().getDimensionPixelSize( R.dimen.clear_all_padding_top); } @@ -58,20 +58,36 @@ public class StackScrollState { int numChildren = mHostView.getChildCount(); for (int i = 0; i < numChildren; i++) { ExpandableView child = (ExpandableView) mHostView.getChildAt(i); - ViewState viewState = mStateMap.get(child); - if (viewState == null) { - viewState = new ViewState(); - mStateMap.put(child, viewState); + resetViewState(child); + + // handling reset for child notifications + if (child instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) child; + List<ExpandableNotificationRow> children = + row.getNotificationChildren(); + if (row.areChildrenExpanded() && children != null) { + for (ExpandableNotificationRow childRow : children) { + resetViewState(childRow); + } + } } - // initialize with the default values of the view - viewState.height = child.getIntrinsicHeight(); - viewState.gone = child.getVisibility() == View.GONE; - viewState.alpha = 1; - viewState.notGoneIndex = -1; } } - public ViewState getViewStateForView(View requestedView) { + private void resetViewState(ExpandableView view) { + StackViewState viewState = mStateMap.get(view); + if (viewState == null) { + viewState = new StackViewState(); + mStateMap.put(view, viewState); + } + // initialize with the default values of the view + viewState.height = view.getIntrinsicHeight(); + viewState.gone = view.getVisibility() == View.GONE; + viewState.alpha = 1; + viewState.notGoneIndex = -1; + } + + public StackViewState getViewStateForView(View requestedView) { return mStateMap.get(requestedView); } @@ -87,126 +103,139 @@ public class StackScrollState { int numChildren = mHostView.getChildCount(); for (int i = 0; i < numChildren; i++) { ExpandableView child = (ExpandableView) mHostView.getChildAt(i); - ViewState state = mStateMap.get(child); - if (state == null) { - Log.wtf(CHILD_NOT_FOUND_TAG, "No child state was found when applying this state " + - "to the hostView"); + StackViewState state = mStateMap.get(child); + if (!applyState(child, state)) { continue; } - if (!state.gone) { - float alpha = child.getAlpha(); - float yTranslation = child.getTranslationY(); - float xTranslation = child.getTranslationX(); - float zTranslation = child.getTranslationZ(); - float scale = child.getScaleX(); - int height = child.getActualHeight(); - float newAlpha = state.alpha; - float newYTranslation = state.yTranslation; - float newZTranslation = state.zTranslation; - float newScale = state.scale; - int newHeight = state.height; - boolean becomesInvisible = newAlpha == 0.0f; - if (alpha != newAlpha && xTranslation == 0) { - // apply layer type - boolean becomesFullyVisible = newAlpha == 1.0f; - boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible; - int layerType = child.getLayerType(); - int newLayerType = newLayerTypeIsHardware - ? View.LAYER_TYPE_HARDWARE - : View.LAYER_TYPE_NONE; - if (layerType != newLayerType) { - child.setLayerType(newLayerType, null); - } - - // apply alpha - child.setAlpha(newAlpha); - } - - // apply visibility - int oldVisibility = child.getVisibility(); - int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE; - if (newVisibility != oldVisibility) { - child.setVisibility(newVisibility); - } - - // apply yTranslation - if (yTranslation != newYTranslation) { - child.setTranslationY(newYTranslation); - } + if(child instanceof SpeedBumpView) { + performSpeedBumpAnimation(i, (SpeedBumpView) child, state, 0); + } else if (child instanceof DismissView) { + DismissView dismissView = (DismissView) child; + boolean visible = state.topOverLap < mClearAllTopPadding; + dismissView.performVisibilityAnimation(visible && !dismissView.willBeGone()); + } else if (child instanceof EmptyShadeView) { + EmptyShadeView emptyShadeView = (EmptyShadeView) child; + boolean visible = state.topOverLap <= 0; + emptyShadeView.performVisibilityAnimation( + visible && !emptyShadeView.willBeGone()); + } + } + } - // apply zTranslation - if (zTranslation != newZTranslation) { - child.setTranslationZ(newZTranslation); - } + /** + * Applies a {@link StackViewState} to an {@link ExpandableView}. + * + * @return whether the state was applied correctly + */ + public boolean applyState(ExpandableView view, StackViewState state) { + if (state == null) { + Log.wtf(CHILD_NOT_FOUND_TAG, "No child state was found when applying this state " + + "to the hostView"); + return false; + } + if (state.gone) { + return false; + } + applyViewState(view, state); - // apply scale - if (scale != newScale) { - child.setScaleX(newScale); - child.setScaleY(newScale); - } + int height = view.getActualHeight(); + int newHeight = state.height; - // apply height - if (height != newHeight) { - child.setActualHeight(newHeight, false /* notifyListeners */); - } + // apply height + if (height != newHeight) { + view.setActualHeight(newHeight, false /* notifyListeners */); + } - // apply dimming - child.setDimmed(state.dimmed, false /* animate */); + // apply dimming + view.setDimmed(state.dimmed, false /* animate */); - // apply dark - child.setDark(state.dark, false /* animate */, 0 /* delay */); + // apply dark + view.setDark(state.dark, false /* animate */, 0 /* delay */); - // apply hiding sensitive - child.setHideSensitive( - state.hideSensitive, false /* animated */, 0 /* delay */, 0 /* duration */); + // apply hiding sensitive + view.setHideSensitive( + state.hideSensitive, false /* animated */, 0 /* delay */, 0 /* duration */); - // apply speed bump state - child.setBelowSpeedBump(state.belowSpeedBump); + // apply speed bump state + view.setBelowSpeedBump(state.belowSpeedBump); - // apply clipping - float oldClipTopAmount = child.getClipTopAmount(); - if (oldClipTopAmount != state.clipTopAmount) { - child.setClipTopAmount(state.clipTopAmount); - } - updateChildClip(child, newHeight, state.topOverLap); - - if(child instanceof SpeedBumpView) { - performSpeedBumpAnimation(i, (SpeedBumpView) child, state, 0); - } else if (child instanceof DismissView) { - DismissView dismissView = (DismissView) child; - boolean visible = state.topOverLap < mClearAllTopPadding; - dismissView.performVisibilityAnimation(visible && !dismissView.willBeGone()); - } else if (child instanceof EmptyShadeView) { - EmptyShadeView emptyShadeView = (EmptyShadeView) child; - boolean visible = state.topOverLap <= 0; - emptyShadeView.performVisibilityAnimation( - visible && !emptyShadeView.willBeGone()); - } - } + // apply clipping + float oldClipTopAmount = view.getClipTopAmount(); + if (oldClipTopAmount != state.clipTopAmount) { + view.setClipTopAmount(state.clipTopAmount); } + float oldClipTopOptimization = view.getClipTopOptimization(); + if (oldClipTopOptimization != state.topOverLap) { + view.setClipTopOptimization(state.topOverLap); + } + if (view instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) view; + row.applyChildrenState(this); + } + return true; } /** - * Updates the clipping of a view - * - * @param child the view to update - * @param height the currently applied height of the view - * @param clipInset how much should this view be clipped from the top + * Applies a {@link ViewState} to a normal view. */ - private void updateChildClip(View child, int height, int clipInset) { - mClipRect.set(0, - clipInset, - child.getWidth(), - height); - child.setClipBounds(mClipRect); + public void applyViewState(View view, ViewState state) { + float alpha = view.getAlpha(); + float yTranslation = view.getTranslationY(); + float xTranslation = view.getTranslationX(); + float zTranslation = view.getTranslationZ(); + float scale = view.getScaleX(); + float newAlpha = state.alpha; + float newYTranslation = state.yTranslation; + float newZTranslation = state.zTranslation; + float newScale = state.scale; + boolean becomesInvisible = newAlpha == 0.0f; + if (alpha != newAlpha && xTranslation == 0) { + // apply layer type + boolean becomesFullyVisible = newAlpha == 1.0f; + boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible + && view.hasOverlappingRendering(); + int layerType = view.getLayerType(); + int newLayerType = newLayerTypeIsHardware + ? View.LAYER_TYPE_HARDWARE + : View.LAYER_TYPE_NONE; + if (layerType != newLayerType) { + view.setLayerType(newLayerType, null); + } + + // apply alpha + view.setAlpha(newAlpha); + } + + // apply visibility + int oldVisibility = view.getVisibility(); + int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE; + if (newVisibility != oldVisibility) { + view.setVisibility(newVisibility); + } + + // apply yTranslation + if (yTranslation != newYTranslation) { + view.setTranslationY(newYTranslation); + } + + // apply zTranslation + if (zTranslation != newZTranslation) { + view.setTranslationZ(newZTranslation); + } + + // apply scale + if (scale != newScale) { + view.setScaleX(newScale); + view.setScaleY(newScale); + } } - public void performSpeedBumpAnimation(int i, SpeedBumpView speedBump, ViewState state, + public void performSpeedBumpAnimation(int i, SpeedBumpView speedBump, StackViewState state, long delay) { View nextChild = getNextChildNotGone(i); if (nextChild != null) { float lineEnd = state.yTranslation + state.height / 2; - ViewState nextState = getViewStateForView(nextChild); + StackViewState nextState = getViewStateForView(nextChild); boolean startIsAboveNext = nextState.yTranslation > lineEnd; speedBump.animateDivider(startIsAboveNext, delay, null /* onFinishedRunnable */); } @@ -223,53 +252,4 @@ public class StackScrollState { return null; } - public static class ViewState { - - // These are flags such that we can create masks for filtering. - - public static final int LOCATION_UNKNOWN = 0x00; - public static final int LOCATION_FIRST_CARD = 0x01; - public static final int LOCATION_TOP_STACK_HIDDEN = 0x02; - public static final int LOCATION_TOP_STACK_PEEKING = 0x04; - public static final int LOCATION_MAIN_AREA = 0x08; - public static final int LOCATION_BOTTOM_STACK_PEEKING = 0x10; - public static final int LOCATION_BOTTOM_STACK_HIDDEN = 0x20; - /** The view isn't layouted at all. */ - public static final int LOCATION_GONE = 0x40; - - float alpha; - float yTranslation; - float zTranslation; - int height; - boolean gone; - float scale; - boolean dimmed; - boolean dark; - boolean hideSensitive; - boolean belowSpeedBump; - - /** - * The amount which the view should be clipped from the top. This is calculated to - * perceive consistent shadows. - */ - int clipTopAmount; - - /** - * How much does the child overlap with the previous view on the top? Can be used for - * a clipping optimization - */ - int topOverLap; - - /** - * The index of the view, only accounting for views not equal to GONE - */ - int notGoneIndex; - - /** - * The location this view is currently rendered at. - * - * <p>See <code>LOCATION_</code> flags.</p> - */ - int location; - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java index b027787..b249fbf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java @@ -26,6 +26,7 @@ import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import com.android.systemui.R; +import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.ExpandableView; import com.android.systemui.statusbar.SpeedBumpView; @@ -42,12 +43,15 @@ public class StackStateAnimator { public static final int ANIMATION_DURATION_STANDARD = 360; public static final int ANIMATION_DURATION_GO_TO_FULL_SHADE = 448; public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464; + public static final int ANIMATION_DURATION_EXPAND_CLICKED = 360; public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220; public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80; + public static final int ANIMATION_DELAY_PER_ELEMENT_EXPAND_CHILDREN = 54; public static final int ANIMATION_DELAY_PER_ELEMENT_MANUAL = 32; public static final int ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE = 48; public static final int ANIMATION_DELAY_PER_ELEMENT_DARK = 24; - private static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE = 2; + public static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE = 2; + public static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE_CHILDREN = 3; private static final int TAG_ANIMATOR_TRANSLATION_Y = R.id.translation_y_animator_tag; private static final int TAG_ANIMATOR_TRANSLATION_Z = R.id.translation_z_animator_tag; @@ -85,6 +89,7 @@ public class StackStateAnimator { private ValueAnimator mTopOverScrollAnimator; private ValueAnimator mBottomOverScrollAnimator; + private ExpandableNotificationRow mChildExpandingView; public StackStateAnimator(NotificationStackScrollLayout hostLayout) { mHostLayout = hostLayout; @@ -113,13 +118,13 @@ public class StackStateAnimator { for (int i = 0; i < childCount; i++) { final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i); - StackScrollState.ViewState viewState = finalState.getViewStateForView(child); + StackViewState viewState = finalState.getViewStateForView(child); if (viewState == null || child.getVisibility() == View.GONE) { continue; } - child.setClipBounds(null); - startAnimations(child, viewState, finalState, i); + child.setClipTopOptimization(0); + startStackAnimations(child, viewState, finalState, i, -1 /* fixedDelay */); } if (!isRunning()) { // no child has preformed any animation, lets finish @@ -127,6 +132,7 @@ public class StackStateAnimator { } mNewEvents.clear(); mNewAddChildren.clear(); + mChildExpandingView = null; } private int findLastNotAddedIndex(StackScrollState finalState) { @@ -134,7 +140,7 @@ public class StackStateAnimator { for (int i = childCount - 1; i >= 0; i--) { final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i); - StackScrollState.ViewState viewState = finalState.getViewStateForView(child); + StackViewState viewState = finalState.getViewStateForView(child); if (viewState == null || child.getVisibility() == View.GONE) { continue; } @@ -145,18 +151,29 @@ public class StackStateAnimator { return -1; } + /** - * Start an animation to the given viewState + * Start an animation to the given {@link StackViewState}. + * + * @param child the child to start the animation on + * @param viewState the {@link StackViewState} of the view to animate to + * @param finalState the final state after the animation + * @param i the index of the view; only relevant if the view is the speed bump and is + * ignored otherwise + * @param fixedDelay a fixed delay if desired or -1 if the delay should be calculated */ - private void startAnimations(final ExpandableView child, StackScrollState.ViewState viewState, - StackScrollState finalState, int i) { - int childVisibility = child.getVisibility(); - boolean wasVisible = childVisibility == View.VISIBLE; + public void startStackAnimations(final ExpandableView child, StackViewState viewState, + StackScrollState finalState, int i, long fixedDelay) { final float alpha = viewState.alpha; - if (!wasVisible && alpha != 0 && !viewState.gone) { - child.setVisibility(View.VISIBLE); + boolean wasAdded = mNewAddChildren.contains(child); + long duration = mCurrentLength; + if (wasAdded && mAnimationFilter.hasGoToFullShadeEvent) { + child.setTranslationY(child.getTranslationY() + mGoToFullShadeAppearingTranslation); + float longerDurationFactor = viewState.notGoneIndex - mCurrentLastNotAddedIndex; + longerDurationFactor = (float) Math.pow(longerDurationFactor, 0.7f); + duration = ANIMATION_DURATION_APPEAR_DISAPPEAR + 50 + + (long) (100 * longerDurationFactor); } - boolean yTranslationChanging = child.getTranslationY() != viewState.yTranslation; boolean zTranslationChanging = child.getTranslationZ() != viewState.zTranslation; boolean scaleChanging = child.getScaleX() != viewState.scale; @@ -164,94 +181,40 @@ public class StackStateAnimator { boolean heightChanging = viewState.height != child.getActualHeight(); boolean darkChanging = viewState.dark != child.isDark(); boolean topInsetChanging = viewState.clipTopAmount != child.getClipTopAmount(); - boolean wasAdded = mNewAddChildren.contains(child); boolean hasDelays = mAnimationFilter.hasDelays; boolean isDelayRelevant = yTranslationChanging || zTranslationChanging || scaleChanging || alphaChanging || heightChanging || topInsetChanging || darkChanging; - boolean noAnimation = wasAdded; long delay = 0; - long duration = mCurrentLength; - if (hasDelays && isDelayRelevant || wasAdded) { + if (fixedDelay != -1) { + delay = fixedDelay; + } else if (hasDelays && isDelayRelevant || wasAdded) { delay = mCurrentAdditionalDelay + calculateChildAnimationDelay(viewState, finalState); } - if (wasAdded && mAnimationFilter.hasGoToFullShadeEvent) { - child.setTranslationY(child.getTranslationY() + mGoToFullShadeAppearingTranslation); - yTranslationChanging = true; - float longerDurationFactor = viewState.notGoneIndex - mCurrentLastNotAddedIndex; - longerDurationFactor = (float) Math.pow(longerDurationFactor, 0.7f); - duration = ANIMATION_DURATION_APPEAR_DISAPPEAR + 50 + - (long) (100 * longerDurationFactor); - } - - // start translationY animation - if (yTranslationChanging) { - if (noAnimation && !mAnimationFilter.hasGoToFullShadeEvent) { - child.setTranslationY(viewState.yTranslation); - } else { - startYTranslationAnimation(child, viewState, duration, delay); - } - } - - // start translationZ animation - if (zTranslationChanging) { - if (noAnimation) { - child.setTranslationZ(viewState.zTranslation); - } else { - startZTranslationAnimation(child, viewState, duration, delay); - } - } - - // start scale animation - if (scaleChanging) { - if (noAnimation) { - child.setScaleX(viewState.scale); - child.setScaleY(viewState.scale); - } else { - startScaleAnimation(child, viewState, duration); - } - } - - // start alpha animation - if (alphaChanging && child.getTranslationX() == 0) { - if (noAnimation) { - child.setAlpha(viewState.alpha); - } else { - startAlphaAnimation(child, viewState, duration, delay); - } - } + startViewAnimations(child, viewState, delay, duration); // start height animation if (heightChanging && child.getActualHeight() != 0) { - if (noAnimation) { - child.setActualHeight(viewState.height, false); - } else { - startHeightAnimation(child, viewState, duration, delay); - } + startHeightAnimation(child, viewState, duration, delay); } // start top inset animation if (topInsetChanging) { - if (noAnimation) { - child.setClipTopAmount(viewState.clipTopAmount); - } else { - startInsetAnimation(child, viewState, duration, delay); - } + startInsetAnimation(child, viewState, duration, delay); } // start dimmed animation - child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed && !wasAdded - && !noAnimation); + child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed); // start dark animation - child.setDark(viewState.dark, mAnimationFilter.animateDark && !noAnimation, delay); + child.setDark(viewState.dark, mAnimationFilter.animateDark, delay); // apply speed bump state child.setBelowSpeedBump(viewState.belowSpeedBump); // start hiding sensitive animation - child.setHideSensitive(viewState.hideSensitive, mAnimationFilter.animateHideSensitive && - !wasAdded && !noAnimation, delay, duration); + child.setHideSensitive(viewState.hideSensitive, mAnimationFilter.animateHideSensitive, + delay, duration); if (wasAdded) { child.performAddAnimation(delay, mCurrentLength); @@ -259,10 +222,55 @@ public class StackStateAnimator { if (child instanceof SpeedBumpView) { finalState.performSpeedBumpAnimation(i, (SpeedBumpView) child, viewState, delay + duration); + } else if (child instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) child; + row.startChildAnimation(finalState, this, child == mChildExpandingView, delay, + duration); } } - private long calculateChildAnimationDelay(StackScrollState.ViewState viewState, + /** + * Start an animation to a new {@link ViewState}. + * + * @param child the child to start the animation on + * @param viewState the {@link StackViewState} of the view to animate to + * @param delay a fixed delay + * @param duration the duration of the animation + */ + public void startViewAnimations(View child, ViewState viewState, long delay, long duration) { + boolean wasVisible = child.getVisibility() == View.VISIBLE; + final float alpha = viewState.alpha; + if (!wasVisible && alpha != 0 && !viewState.gone) { + child.setVisibility(View.VISIBLE); + } + boolean yTranslationChanging = child.getTranslationY() != viewState.yTranslation; + boolean zTranslationChanging = child.getTranslationZ() != viewState.zTranslation; + boolean scaleChanging = child.getScaleX() != viewState.scale; + float childAlpha = child.getVisibility() == View.INVISIBLE ? 0.0f : child.getAlpha(); + boolean alphaChanging = viewState.alpha != childAlpha; + + // start translationY animation + if (yTranslationChanging) { + startYTranslationAnimation(child, viewState, duration, delay); + } + + // start translationZ animation + if (zTranslationChanging) { + startZTranslationAnimation(child, viewState, duration, delay); + } + + // start scale animation + if (scaleChanging) { + startScaleAnimation(child, viewState, duration); + } + + // start alpha animation + if (alphaChanging && child.getTranslationX() == 0) { + startAlphaAnimation(child, viewState, duration, delay); + } + } + + private long calculateChildAnimationDelay(StackViewState viewState, StackScrollState finalState) { if (mAnimationFilter.hasDarkEvent) { return calculateDelayDark(viewState); @@ -314,7 +322,7 @@ public class StackStateAnimator { return minDelay; } - private long calculateDelayDark(StackScrollState.ViewState viewState) { + private long calculateDelayDark(StackViewState viewState) { int referenceIndex; if (mAnimationFilter.darkAnimationOriginIndex == NotificationStackScrollLayout.AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE) { @@ -328,14 +336,14 @@ public class StackStateAnimator { return Math.abs(referenceIndex - viewState.notGoneIndex) * ANIMATION_DELAY_PER_ELEMENT_DARK; } - private long calculateDelayGoToFullShade(StackScrollState.ViewState viewState) { + private long calculateDelayGoToFullShade(StackViewState viewState) { float index = viewState.notGoneIndex; index = (float) Math.pow(index, 0.7f); return (long) (index * ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE); } private void startHeightAnimation(final ExpandableView child, - StackScrollState.ViewState viewState, long duration, long delay) { + StackViewState viewState, long duration, long delay) { Integer previousStartValue = getChildTag(child, TAG_START_HEIGHT); Integer previousEndValue = getChildTag(child, TAG_END_HEIGHT); int newEndValue = viewState.height; @@ -394,7 +402,7 @@ public class StackStateAnimator { } private void startInsetAnimation(final ExpandableView child, - StackScrollState.ViewState viewState, long duration, long delay) { + StackViewState viewState, long duration, long delay) { Integer previousStartValue = getChildTag(child, TAG_START_TOP_INSET); Integer previousEndValue = getChildTag(child, TAG_END_TOP_INSET); int newEndValue = viewState.clipTopAmount; @@ -451,8 +459,8 @@ public class StackStateAnimator { child.setTag(TAG_END_TOP_INSET, newEndValue); } - private void startAlphaAnimation(final ExpandableView child, - final StackScrollState.ViewState viewState, long duration, long delay) { + private void startAlphaAnimation(final View child, + final ViewState viewState, long duration, long delay) { Float previousStartValue = getChildTag(child,TAG_START_ALPHA); Float previousEndValue = getChildTag(child,TAG_END_ALPHA); final float newEndValue = viewState.alpha; @@ -525,8 +533,8 @@ public class StackStateAnimator { child.setTag(TAG_END_ALPHA, newEndValue); } - private void startZTranslationAnimation(final ExpandableView child, - final StackScrollState.ViewState viewState, long duration, long delay) { + private void startZTranslationAnimation(final View child, + final ViewState viewState, long duration, long delay) { Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Z); Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Z); float newEndValue = viewState.zTranslation; @@ -577,8 +585,8 @@ public class StackStateAnimator { child.setTag(TAG_END_TRANSLATION_Z, newEndValue); } - private void startYTranslationAnimation(final ExpandableView child, - StackScrollState.ViewState viewState, long duration, long delay) { + private void startYTranslationAnimation(final View child, + ViewState viewState, long duration, long delay) { Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Y); Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Y); float newEndValue = viewState.yTranslation; @@ -630,8 +638,8 @@ public class StackStateAnimator { child.setTag(TAG_END_TRANSLATION_Y, newEndValue); } - private void startScaleAnimation(final ExpandableView child, - StackScrollState.ViewState viewState, long duration) { + private void startScaleAnimation(final View child, + ViewState viewState, long duration) { Float previousStartValue = getChildTag(child, TAG_START_SCALE); Float previousEndValue = getChildTag(child, TAG_END_SCALE); float newEndValue = viewState.scale; @@ -765,7 +773,7 @@ public class StackStateAnimator { NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD) { // This item is added, initialize it's properties. - StackScrollState.ViewState viewState = finalState + StackViewState viewState = finalState .getViewStateForView(changingView); if (viewState == null) { // The position for this child was never generated, let's continue. @@ -776,10 +784,7 @@ public class StackStateAnimator { finalState.removeViewStateForView(changingView); continue; } - changingView.setAlpha(viewState.alpha); - changingView.setTranslationY(viewState.yTranslation); - changingView.setTranslationZ(viewState.zTranslation); - changingView.setActualHeight(viewState.height, false); + finalState.applyState(changingView, viewState); mNewAddChildren.add(changingView); } else if (event.animationType == @@ -791,7 +796,7 @@ public class StackStateAnimator { // Find the amount to translate up. This is needed in order to understand the // direction of the remove animation (either downwards or upwards) - StackScrollState.ViewState viewState = finalState + StackViewState viewState = finalState .getViewStateForView(event.viewAfterChangingView); int actualHeight = changingView.getActualHeight(); // upwards by default @@ -813,11 +818,16 @@ public class StackStateAnimator { mHostLayout.getOverlay().remove(changingView); } }); - } else if (event.animationType == + } else if (event.animationType == NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) { // A race condition can trigger the view to be added to the overlay even though // it is swiped out. So let's remove it mHostLayout.getOverlay().remove(changingView); + } else if (event.animationType == NotificationStackScrollLayout + .AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED) { + ExpandableNotificationRow row = (ExpandableNotificationRow) event.changingView; + row.prepareExpansionChanged(finalState); + mChildExpandingView = row; } mNewEvents.add(event); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackViewState.java new file mode 100644 index 0000000..55ef440 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackViewState.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.stack; + +import android.view.View; + +import com.android.systemui.statusbar.ExpandableView; + +/** +* A state of an expandable view +*/ +public class StackViewState extends ViewState { + + // These are flags such that we can create masks for filtering. + + public static final int LOCATION_UNKNOWN = 0x00; + public static final int LOCATION_FIRST_CARD = 0x01; + public static final int LOCATION_TOP_STACK_HIDDEN = 0x02; + public static final int LOCATION_TOP_STACK_PEEKING = 0x04; + public static final int LOCATION_MAIN_AREA = 0x08; + public static final int LOCATION_BOTTOM_STACK_PEEKING = 0x10; + public static final int LOCATION_BOTTOM_STACK_HIDDEN = 0x20; + /** The view isn't layouted at all. */ + public static final int LOCATION_GONE = 0x40; + + public int height; + public boolean dimmed; + public boolean dark; + public boolean hideSensitive; + public boolean belowSpeedBump; + + /** + * The amount which the view should be clipped from the top. This is calculated to + * perceive consistent shadows. + */ + public int clipTopAmount; + + /** + * How much does the child overlap with the previous view on the top? Can be used for + * a clipping optimization + */ + public int topOverLap; + + /** + * The index of the view, only accounting for views not equal to GONE + */ + public int notGoneIndex; + + /** + * The location this view is currently rendered at. + * + * <p>See <code>LOCATION_</code> flags.</p> + */ + public int location; + + @Override + public void copyFrom(ViewState viewState) { + super.copyFrom(viewState); + if (viewState instanceof StackViewState) { + StackViewState svs = (StackViewState) viewState; + height = svs.height; + dimmed = svs.dimmed; + dark = svs.dark; + hideSensitive = svs.hideSensitive; + belowSpeedBump = svs.belowSpeedBump; + clipTopAmount = svs.clipTopAmount; + topOverLap = svs.topOverLap; + notGoneIndex = svs.notGoneIndex; + location = svs.location; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java new file mode 100644 index 0000000..3e538df --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.stack; + +import android.view.View; + +/** + * A state of a view. This can be used to apply a set of view properties to a view with + * {@link com.android.systemui.statusbar.stack.StackScrollState} or start animations with + * {@link com.android.systemui.statusbar.stack.StackStateAnimator}. +*/ +public class ViewState { + + public float alpha; + public float yTranslation; + public float zTranslation; + public boolean gone; + public float scale; + + public void copyFrom(ViewState viewState) { + alpha = viewState.alpha; + yTranslation = viewState.yTranslation; + zTranslation = viewState.zTranslation; + gone = viewState.gone; + scale = viewState.scale; + } + + public void initFrom(View view) { + alpha = view.getVisibility() == View.INVISIBLE ? 0.0f : view.getAlpha(); + yTranslation = view.getTranslationY(); + zTranslation = view.getTranslationZ(); + gone = view.getVisibility() == View.GONE; + scale = view.getScaleX(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java index 08732e5..c272e48 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java @@ -26,6 +26,7 @@ import android.view.WindowManager; import com.android.internal.statusbar.StatusBarIcon; import com.android.systemui.statusbar.ActivatableNotificationView; import com.android.systemui.statusbar.BaseStatusBar; +import com.android.systemui.statusbar.NotificationData; /* * Status bar implementation for "large screen" products that mostly present no on-screen nav @@ -47,7 +48,8 @@ public class TvStatusBar extends BaseStatusBar { } @Override - public void addNotification(StatusBarNotification notification, RankingMap ranking) { + public void addNotification(StatusBarNotification notification, RankingMap ranking, + NotificationData.Entry entry) { } @Override @@ -104,16 +106,6 @@ public class TvStatusBar extends BaseStatusBar { } @Override - protected WindowManager.LayoutParams getSearchLayoutParams( - LayoutParams layoutParams) { - return null; - } - - @Override - protected void haltTicker() { - } - - @Override protected void setAreThereNotifications() { } @@ -122,15 +114,7 @@ public class TvStatusBar extends BaseStatusBar { } @Override - protected void tick(StatusBarNotification n, boolean firstTime) { - } - - @Override - protected void updateExpandedViewPos(int expandedPosition) { - } - - @Override - protected boolean shouldDisableNavbarGestures() { + public boolean shouldDisableNavbarGestures() { return true; } @@ -139,7 +123,7 @@ public class TvStatusBar extends BaseStatusBar { } @Override - public void resetHeadsUpDecayTimer() { + public void scheduleHeadsUpDecay(long delay) { } @Override @@ -182,4 +166,16 @@ public class TvStatusBar extends BaseStatusBar { @Override public void showScreenPinningRequest() { } + + @Override + public void appTransitionPending() { + } + + @Override + public void appTransitionCancelled() { + } + + @Override + public void appTransitionStarting(long startTime, long duration) { + } } diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java index f804736..5771d22 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java +++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java @@ -327,7 +327,7 @@ public class StorageNotification extends SystemUI { pi = PendingIntent.getBroadcastAsUser(mContext, 0, intent, 0, UserHandle.CURRENT); } - mUsbStorageNotification.color = mContext.getResources().getColor( + mUsbStorageNotification.color = mContext.getColor( com.android.internal.R.color.system_notification_accent_color); mUsbStorageNotification.setLatestEventInfo(mContext, title, message, pi); mUsbStorageNotification.visibility = Notification.VISIBILITY_PUBLIC; @@ -422,7 +422,7 @@ public class StorageNotification extends SystemUI { } mMediaStorageNotification.icon = icon; - mMediaStorageNotification.color = mContext.getResources().getColor( + mMediaStorageNotification.color = mContext.getColor( com.android.internal.R.color.system_notification_accent_color); mMediaStorageNotification.setLatestEventInfo(mContext, title, message, pi); mMediaStorageNotification.visibility = Notification.VISIBILITY_PUBLIC; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPanel.java b/packages/SystemUI/src/com/android/systemui/volume/D.java index 272c321..db7c853 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPanel.java +++ b/packages/SystemUI/src/com/android/systemui/volume/D.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 The Android Open Source Project + * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,8 +14,10 @@ * limitations under the License. */ -package com.android.systemui.statusbar; +package com.android.systemui.volume; -public interface StatusBarPanel { - public boolean isInContentArea(int x, int y); +import android.util.Log; + +class D { + public static boolean BUG = Log.isLoggable("volume", Log.DEBUG); } diff --git a/packages/SystemUI/src/com/android/systemui/volume/Events.java b/packages/SystemUI/src/com/android/systemui/volume/Events.java new file mode 100644 index 0000000..12dca94 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/Events.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume; + +import android.media.AudioManager; +import android.media.AudioSystem; +import android.provider.Settings.Global; +import android.util.Log; + +import com.android.systemui.volume.VolumeDialogController.State; + +import java.util.Arrays; + +/** + * Interesting events related to the volume. + */ +public class Events { + private static final String TAG = Util.logTag(Events.class); + + public static final int EVENT_SHOW_DIALOG = 0; // (reason|int) (keyguard|bool) + public static final int EVENT_DISMISS_DIALOG = 1; // (reason|int) + public static final int EVENT_ACTIVE_STREAM_CHANGED = 2; // (stream|int) + public static final int EVENT_EXPAND = 3; // (expand|bool) + public static final int EVENT_KEY = 4; + public static final int EVENT_COLLECTION_STARTED = 5; + public static final int EVENT_COLLECTION_STOPPED = 6; + public static final int EVENT_ICON_CLICK = 7; // (stream|int) (icon_state|int) + public static final int EVENT_SETTINGS_CLICK = 8; + public static final int EVENT_TOUCH_LEVEL_CHANGED = 9; // (stream|int) (level|int) + public static final int EVENT_LEVEL_CHANGED = 10; // (stream|int) (level|int) + public static final int EVENT_INTERNAL_RINGER_MODE_CHANGED = 11; // (mode|int) + public static final int EVENT_EXTERNAL_RINGER_MODE_CHANGED = 12; // (mode|int) + public static final int EVENT_ZEN_MODE_CHANGED = 13; // (mode|int) + public static final int EVENT_SUPPRESSOR_CHANGED = 14; // (component|string) (name|string) + public static final int EVENT_MUTE_CHANGED = 15; // (stream|int) (muted|bool) + + private static final String[] EVENT_TAGS = { + "show_dialog", + "dismiss_dialog", + "active_stream_changed", + "expand", + "key", + "collection_started", + "collection_stopped", + "icon_click", + "settings_click", + "touch_level_changed", + "level_changed", + "internal_ringer_mode_changed", + "external_ringer_mode_changed", + "zen_mode_changed", + "suppressor_changed", + "mute_changed", + }; + + public static final int DISMISS_REASON_UNKNOWN = 0; + public static final int DISMISS_REASON_TOUCH_OUTSIDE = 1; + public static final int DISMISS_REASON_VOLUME_CONTROLLER = 2; + public static final int DISMISS_REASON_TIMEOUT = 3; + public static final int DISMISS_REASON_SCREEN_OFF = 4; + public static final int DISMISS_REASON_SETTINGS_CLICKED = 5; + public static final int DISMISS_REASON_DONE_CLICKED = 6; + public static final String[] DISMISS_REASONS = { + "unknown", + "touch_outside", + "volume_controller", + "timeout", + "screen_off", + "settings_clicked", + "done_clicked", + }; + + public static final int SHOW_REASON_UNKNOWN = 0; + public static final int SHOW_REASON_VOLUME_CHANGED = 1; + public static final int SHOW_REASON_REMOTE_VOLUME_CHANGED = 2; + public static final String[] SHOW_REASONS = { + "unknown", + "volume_changed", + "remote_volume_changed" + }; + + public static final int ICON_STATE_UNKNOWN = 0; + public static final int ICON_STATE_UNMUTE = 1; + public static final int ICON_STATE_MUTE = 2; + public static final int ICON_STATE_VIBRATE = 3; + + public static Callback sCallback; + + public static void writeEvent(int tag, Object... list) { + final long time = System.currentTimeMillis(); + final StringBuilder sb = new StringBuilder("writeEvent ").append(EVENT_TAGS[tag]); + if (list != null && list.length > 0) { + sb.append(" "); + switch (tag) { + case EVENT_SHOW_DIALOG: + sb.append(SHOW_REASONS[(Integer) list[0]]).append(" keyguard=").append(list[1]); + break; + case EVENT_EXPAND: + sb.append(list[0]); + break; + case EVENT_DISMISS_DIALOG: + sb.append(DISMISS_REASONS[(Integer) list[0]]); + break; + case EVENT_ACTIVE_STREAM_CHANGED: + sb.append(AudioSystem.streamToString((Integer) list[0])); + break; + case EVENT_ICON_CLICK: + sb.append(AudioSystem.streamToString((Integer) list[0])).append(' ') + .append(iconStateToString((Integer) list[1])); + break; + case EVENT_TOUCH_LEVEL_CHANGED: + case EVENT_LEVEL_CHANGED: + case EVENT_MUTE_CHANGED: + sb.append(AudioSystem.streamToString((Integer) list[0])).append(' ') + .append(list[1]); + break; + case EVENT_INTERNAL_RINGER_MODE_CHANGED: + case EVENT_EXTERNAL_RINGER_MODE_CHANGED: + sb.append(ringerModeToString((Integer) list[0])); + break; + case EVENT_ZEN_MODE_CHANGED: + sb.append(zenModeToString((Integer) list[0])); + break; + case EVENT_SUPPRESSOR_CHANGED: + sb.append(list[0]).append(' ').append(list[1]); + break; + default: + sb.append(Arrays.asList(list)); + break; + } + } + Log.i(TAG, sb.toString()); + if (sCallback != null) { + sCallback.writeEvent(time, tag, list); + } + } + + public static void writeState(long time, State state) { + if (sCallback != null) { + sCallback.writeState(time, state); + } + } + + private static String iconStateToString(int iconState) { + switch (iconState) { + case ICON_STATE_UNMUTE: return "unmute"; + case ICON_STATE_MUTE: return "mute"; + case ICON_STATE_VIBRATE: return "vibrate"; + default: return "unknown_state_" + iconState; + } + } + + private static String ringerModeToString(int ringerMode) { + switch (ringerMode) { + case AudioManager.RINGER_MODE_SILENT: return "silent"; + case AudioManager.RINGER_MODE_VIBRATE: return "vibrate"; + case AudioManager.RINGER_MODE_NORMAL: return "normal"; + default: return "unknown"; + } + } + + private static String zenModeToString(int zenMode) { + switch (zenMode) { + case Global.ZEN_MODE_OFF: return "off"; + case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: return "important_interruptions"; + case Global.ZEN_MODE_ALARMS: return "alarms"; + case Global.ZEN_MODE_NO_INTERRUPTIONS: return "no_interruptions"; + default: return "unknown"; + } + } + + public interface Callback { + void writeEvent(long time, int tag, Object[] list); + void writeState(long time, State state); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java b/packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java new file mode 100644 index 0000000..712ea27 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java @@ -0,0 +1,378 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume; + +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; +import android.media.IRemoteVolumeController; +import android.media.MediaMetadata; +import android.media.session.ISessionController; +import android.media.session.MediaController; +import android.media.session.MediaController.PlaybackInfo; +import android.media.session.MediaSession.QueueItem; +import android.media.session.MediaSession.Token; +import android.media.session.MediaSessionManager; +import android.media.session.MediaSessionManager.OnActiveSessionsChangedListener; +import android.media.session.PlaybackState; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.util.Log; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * Convenience client for all media session updates. Provides a callback interface for events + * related to remote media sessions. + */ +public class MediaSessions { + private static final String TAG = Util.logTag(MediaSessions.class); + + private static final boolean USE_SERVICE_LABEL = false; + + private final Context mContext; + private final H mHandler; + private final MediaSessionManager mMgr; + private final Map<Token, MediaControllerRecord> mRecords = new HashMap<>(); + private final Callbacks mCallbacks; + + private boolean mInit; + + public MediaSessions(Context context, Looper looper, Callbacks callbacks) { + mContext = context; + mHandler = new H(looper); + mMgr = (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE); + mCallbacks = callbacks; + } + + public void dump(PrintWriter writer) { + writer.println(getClass().getSimpleName() + " state:"); + writer.print(" mInit: "); writer.println(mInit); + writer.print(" mRecords.size: "); writer.println(mRecords.size()); + int i = 0; + for (MediaControllerRecord r : mRecords.values()) { + dump(++i, writer, r.controller); + } + } + + public void init() { + if (D.BUG) Log.d(TAG, "init"); + // will throw if no permission + mMgr.addOnActiveSessionsChangedListener(mSessionsListener, null, mHandler); + mInit = true; + postUpdateSessions(); + mMgr.setRemoteVolumeController(mRvc); + } + + protected void postUpdateSessions() { + if (!mInit) return; + mHandler.sendEmptyMessage(H.UPDATE_SESSIONS); + } + + public void destroy() { + if (D.BUG) Log.d(TAG, "destroy"); + mInit = false; + mMgr.removeOnActiveSessionsChangedListener(mSessionsListener); + } + + public void setVolume(Token token, int level) { + final MediaControllerRecord r = mRecords.get(token); + if (r == null) { + Log.w(TAG, "setVolume: No record found for token " + token); + return; + } + if (D.BUG) Log.d(TAG, "Setting level to " + level); + r.controller.setVolumeTo(level, 0); + } + + private void onRemoteVolumeChangedH(ISessionController session, int flags) { + final MediaController controller = new MediaController(mContext, session); + if (D.BUG) Log.d(TAG, "remoteVolumeChangedH " + controller.getPackageName() + " " + + Util.audioManagerFlagsToString(flags)); + final Token token = controller.getSessionToken(); + mCallbacks.onRemoteVolumeChanged(token, flags); + } + + private void onUpdateRemoteControllerH(ISessionController session) { + final MediaController controller = session != null ? new MediaController(mContext, session) + : null; + final String pkg = controller != null ? controller.getPackageName() : null; + if (D.BUG) Log.d(TAG, "updateRemoteControllerH " + pkg); + // this may be our only indication that a remote session is changed, refresh + postUpdateSessions(); + } + + protected void onActiveSessionsUpdatedH(List<MediaController> controllers) { + if (D.BUG) Log.d(TAG, "onActiveSessionsUpdatedH n=" + controllers.size()); + final Set<Token> toRemove = new HashSet<Token>(mRecords.keySet()); + for (MediaController controller : controllers) { + final Token token = controller.getSessionToken(); + final PlaybackInfo pi = controller.getPlaybackInfo(); + toRemove.remove(token); + if (!mRecords.containsKey(token)) { + final MediaControllerRecord r = new MediaControllerRecord(controller); + r.name = getControllerName(controller); + mRecords.put(token, r); + controller.registerCallback(r, mHandler); + } + final MediaControllerRecord r = mRecords.get(token); + final boolean remote = isRemote(pi); + if (remote) { + updateRemoteH(token, r.name, pi); + r.sentRemote = true; + } + } + for (Token t : toRemove) { + final MediaControllerRecord r = mRecords.get(t); + r.controller.unregisterCallback(r); + mRecords.remove(t); + if (D.BUG) Log.d(TAG, "Removing " + r.name + " sentRemote=" + r.sentRemote); + if (r.sentRemote) { + mCallbacks.onRemoteRemoved(t); + r.sentRemote = false; + } + } + } + + private static boolean isRemote(PlaybackInfo pi) { + return pi != null && pi.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE; + } + + protected String getControllerName(MediaController controller) { + final PackageManager pm = mContext.getPackageManager(); + final String pkg = controller.getPackageName(); + try { + if (USE_SERVICE_LABEL) { + final List<ResolveInfo> ris = pm.queryIntentServices( + new Intent("android.media.MediaRouteProviderService").setPackage(pkg), 0); + if (ris != null) { + for (ResolveInfo ri : ris) { + if (ri.serviceInfo == null) continue; + if (pkg.equals(ri.serviceInfo.packageName)) { + final String serviceLabel = + Objects.toString(ri.serviceInfo.loadLabel(pm), "").trim(); + if (serviceLabel.length() > 0) { + return serviceLabel; + } + } + } + } + } + final ApplicationInfo ai = pm.getApplicationInfo(pkg, 0); + final String appLabel = Objects.toString(ai.loadLabel(pm), "").trim(); + if (appLabel.length() > 0) { + return appLabel; + } + } catch (NameNotFoundException e) { } + return pkg; + } + + private void updateRemoteH(Token token, String name, PlaybackInfo pi) { + if (mCallbacks != null) { + mCallbacks.onRemoteUpdate(token, name, pi); + } + } + + private static void dump(int n, PrintWriter writer, MediaController c) { + writer.println(" Controller " + n + ": " + c.getPackageName()); + final Bundle extras = c.getExtras(); + final long flags = c.getFlags(); + final MediaMetadata mm = c.getMetadata(); + final PlaybackInfo pi = c.getPlaybackInfo(); + final PlaybackState playbackState = c.getPlaybackState(); + final List<QueueItem> queue = c.getQueue(); + final CharSequence queueTitle = c.getQueueTitle(); + final int ratingType = c.getRatingType(); + final PendingIntent sessionActivity = c.getSessionActivity(); + + writer.println(" PlaybackState: " + Util.playbackStateToString(playbackState)); + writer.println(" PlaybackInfo: " + Util.playbackInfoToString(pi)); + if (mm != null) { + writer.println(" MediaMetadata.desc=" + mm.getDescription()); + } + writer.println(" RatingType: " + ratingType); + writer.println(" Flags: " + flags); + if (extras != null) { + writer.println(" Extras:"); + for (String key : extras.keySet()) { + writer.println(" " + key + "=" + extras.get(key)); + } + } + if (queueTitle != null) { + writer.println(" QueueTitle: " + queueTitle); + } + if (queue != null && !queue.isEmpty()) { + writer.println(" Queue:"); + for (QueueItem qi : queue) { + writer.println(" " + qi); + } + } + if (pi != null) { + writer.println(" sessionActivity: " + sessionActivity); + } + } + + public static void dumpMediaSessions(Context context) { + final MediaSessionManager mgr = (MediaSessionManager) context + .getSystemService(Context.MEDIA_SESSION_SERVICE); + try { + final List<MediaController> controllers = mgr.getActiveSessions(null); + final int N = controllers.size(); + if (D.BUG) Log.d(TAG, N + " controllers"); + for (int i = 0; i < N; i++) { + final StringWriter sw = new StringWriter(); + final PrintWriter pw = new PrintWriter(sw, true); + dump(i + 1, pw, controllers.get(i)); + if (D.BUG) Log.d(TAG, sw.toString()); + } + } catch (SecurityException e) { + Log.w(TAG, "Not allowed to get sessions", e); + } + } + + private final class MediaControllerRecord extends MediaController.Callback { + private final MediaController controller; + + private boolean sentRemote; + private String name; + + private MediaControllerRecord(MediaController controller) { + this.controller = controller; + } + + private String cb(String method) { + return method + " " + controller.getPackageName() + " "; + } + + @Override + public void onAudioInfoChanged(PlaybackInfo info) { + if (D.BUG) Log.d(TAG, cb("onAudioInfoChanged") + Util.playbackInfoToString(info) + + " sentRemote=" + sentRemote); + final boolean remote = isRemote(info); + if (!remote && sentRemote) { + mCallbacks.onRemoteRemoved(controller.getSessionToken()); + sentRemote = false; + } else if (remote) { + updateRemoteH(controller.getSessionToken(), name, info); + sentRemote = true; + } + } + + @Override + public void onExtrasChanged(Bundle extras) { + if (D.BUG) Log.d(TAG, cb("onExtrasChanged") + extras); + } + + @Override + public void onMetadataChanged(MediaMetadata metadata) { + if (D.BUG) Log.d(TAG, cb("onMetadataChanged") + Util.mediaMetadataToString(metadata)); + } + + @Override + public void onPlaybackStateChanged(PlaybackState state) { + if (D.BUG) Log.d(TAG, cb("onPlaybackStateChanged") + Util.playbackStateToString(state)); + } + + @Override + public void onQueueChanged(List<QueueItem> queue) { + if (D.BUG) Log.d(TAG, cb("onQueueChanged") + queue); + } + + @Override + public void onQueueTitleChanged(CharSequence title) { + if (D.BUG) Log.d(TAG, cb("onQueueTitleChanged") + title); + } + + @Override + public void onSessionDestroyed() { + if (D.BUG) Log.d(TAG, cb("onSessionDestroyed")); + } + + @Override + public void onSessionEvent(String event, Bundle extras) { + if (D.BUG) Log.d(TAG, cb("onSessionEvent") + "event=" + event + " extras=" + extras); + } + } + + private final OnActiveSessionsChangedListener mSessionsListener = + new OnActiveSessionsChangedListener() { + @Override + public void onActiveSessionsChanged(List<MediaController> controllers) { + onActiveSessionsUpdatedH(controllers); + } + }; + + private final IRemoteVolumeController mRvc = new IRemoteVolumeController.Stub() { + @Override + public void remoteVolumeChanged(ISessionController session, int flags) + throws RemoteException { + mHandler.obtainMessage(H.REMOTE_VOLUME_CHANGED, flags, 0, session).sendToTarget(); + } + + @Override + public void updateRemoteController(final ISessionController session) + throws RemoteException { + mHandler.obtainMessage(H.UPDATE_REMOTE_CONTROLLER, session).sendToTarget(); + } + }; + + private final class H extends Handler { + private static final int UPDATE_SESSIONS = 1; + private static final int REMOTE_VOLUME_CHANGED = 2; + private static final int UPDATE_REMOTE_CONTROLLER = 3; + + private H(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case UPDATE_SESSIONS: + onActiveSessionsUpdatedH(mMgr.getActiveSessions(null)); + break; + case REMOTE_VOLUME_CHANGED: + onRemoteVolumeChangedH((ISessionController) msg.obj, msg.arg1); + break; + case UPDATE_REMOTE_CONTROLLER: + onUpdateRemoteControllerH((ISessionController) msg.obj); + break; + } + } + } + + public interface Callbacks { + void onRemoteUpdate(Token token, String name, PlaybackInfo pi); + void onRemoteRemoved(Token t); + void onRemoteVolumeChanged(Token token, int flags); + } + +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/Prefs.java b/packages/SystemUI/src/com/android/systemui/volume/Prefs.java new file mode 100644 index 0000000..58bc9f4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/Prefs.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; +import android.preference.PreferenceManager; + +/** + * Configuration for the volume dialog + related policy. + */ +public class Prefs { + + public static final String PREF_ENABLE_PROTOTYPE = "pref_enable_prototype"; // not persistent + public static final String PREF_SHOW_ALARMS = "pref_show_alarms"; + public static final String PREF_SHOW_SYSTEM = "pref_show_system"; + public static final String PREF_SHOW_HEADERS = "pref_show_headers"; + public static final String PREF_SHOW_FAKE_REMOTE_1 = "pref_show_fake_remote_1"; + public static final String PREF_SHOW_FAKE_REMOTE_2 = "pref_show_fake_remote_2"; + public static final String PREF_SHOW_FOOTER = "pref_show_footer"; + public static final String PREF_ZEN_FOOTER = "pref_zen_footer"; + public static final String PREF_ENABLE_AUTOMUTE = "pref_enable_automute"; + public static final String PREF_ENABLE_SILENT_MODE = "pref_enable_silent_mode"; + public static final String PREF_DEBUG_LOGGING = "pref_debug_logging"; + public static final String PREF_SEND_LOGS = "pref_send_logs"; + public static final String PREF_ADJUST_SYSTEM = "pref_adjust_system"; + public static final String PREF_ADJUST_VOICE_CALLS = "pref_adjust_voice_calls"; + public static final String PREF_ADJUST_BLUETOOTH_SCO = "pref_adjust_bluetooth_sco"; + public static final String PREF_ADJUST_MEDIA = "pref_adjust_media"; + public static final String PREF_ADJUST_ALARMS = "pref_adjust_alarms"; + public static final String PREF_ADJUST_NOTIFICATION = "pref_adjust_notification"; + + public static final boolean DEFAULT_SHOW_HEADERS = true; + public static final boolean DEFAULT_SHOW_FOOTER = true; + public static final boolean DEFAULT_ENABLE_AUTOMUTE = true; + public static final boolean DEFAULT_ENABLE_SILENT_MODE = true; + public static final boolean DEFAULT_ZEN_FOOTER = true; + + public static void unregisterCallbacks(Context c, OnSharedPreferenceChangeListener listener) { + prefs(c).unregisterOnSharedPreferenceChangeListener(listener); + } + + public static void registerCallbacks(Context c, OnSharedPreferenceChangeListener listener) { + prefs(c).registerOnSharedPreferenceChangeListener(listener); + } + + private static SharedPreferences prefs(Context context) { + return PreferenceManager.getDefaultSharedPreferences(context); + } + + public static boolean get(Context context, String key, boolean def) { + return prefs(context).getBoolean(key, def); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/SegmentedButtons.java b/packages/SystemUI/src/com/android/systemui/volume/SegmentedButtons.java index 2f02f7c..4f20ac7 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/SegmentedButtons.java +++ b/packages/SystemUI/src/com/android/systemui/volume/SegmentedButtons.java @@ -60,17 +60,14 @@ public class SegmentedButtons extends LinearLayout { final Object tag = c.getTag(); final boolean selected = Objects.equals(mSelectedValue, tag); c.setSelected(selected); - c.getCompoundDrawables()[1].setTint(mContext.getResources().getColor(selected - ? R.color.segmented_button_selected : R.color.segmented_button_unselected)); } fireOnSelected(); } - public void addButton(int labelResId, int iconResId, Object value) { + public void addButton(int labelResId, Object value) { final Button b = (Button) mInflater.inflate(R.layout.segmented_button, this, false); b.setTag(LABEL_RES_KEY, labelResId); b.setText(labelResId); - b.setCompoundDrawablesWithIntrinsicBounds(0, iconResId, 0, 0); final LayoutParams lp = (LayoutParams) b.getLayoutParams(); if (getChildCount() == 0) { lp.leftMargin = lp.rightMargin = 0; // first button has no margin diff --git a/packages/SystemUI/src/com/android/systemui/volume/SpTexts.java b/packages/SystemUI/src/com/android/systemui/volume/SpTexts.java new file mode 100644 index 0000000..d8e53db --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/SpTexts.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume; + +import android.content.Context; +import android.content.res.Resources; +import android.util.ArrayMap; +import android.util.TypedValue; +import android.view.View; +import android.view.View.OnAttachStateChangeListener; +import android.widget.TextView; + +/** + * Capture initial sp values for registered textviews, and update properly when configuration + * changes. + */ +public class SpTexts { + + private final Context mContext; + private final ArrayMap<TextView, Integer> mTexts = new ArrayMap<>(); + + public SpTexts(Context context) { + mContext = context; + } + + public int add(final TextView text) { + if (text == null) return 0; + final Resources res = mContext.getResources(); + final float fontScale = res.getConfiguration().fontScale; + final float density = res.getDisplayMetrics().density; + final float px = text.getTextSize(); + final int sp = (int)(px / fontScale / density); + mTexts.put(text, sp); + text.addOnAttachStateChangeListener(new OnAttachStateChangeListener() { + @Override + public void onViewDetachedFromWindow(View v) { + } + + @Override + public void onViewAttachedToWindow(View v) { + setTextSizeH(text, sp); + } + }); + return sp; + } + + public void update() { + if (mTexts.isEmpty()) return; + mTexts.keyAt(0).post(mUpdateAll); + } + + private void setTextSizeH(TextView text, int sp) { + text.setTextSize(TypedValue.COMPLEX_UNIT_SP, sp); + } + + private final Runnable mUpdateAll = new Runnable() { + @Override + public void run() { + for (int i = 0; i < mTexts.size(); i++) { + setTextSizeH(mTexts.keyAt(i), mTexts.valueAt(i)); + } + } + }; +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/Util.java b/packages/SystemUI/src/com/android/systemui/volume/Util.java new file mode 100644 index 0000000..fbdc1ca --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/Util.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume; + +import android.media.AudioManager; +import android.media.MediaMetadata; +import android.media.VolumeProvider; +import android.media.session.MediaController.PlaybackInfo; +import android.media.session.PlaybackState; +import android.service.notification.ZenModeConfig.DowntimeInfo; +import android.view.View; +import android.widget.TextView; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.Objects; + +/** + * Static helpers for the volume dialog. + */ +class Util { + + // Note: currently not shown (only used in the text footer) + private static final SimpleDateFormat HMMAA = new SimpleDateFormat("h:mm aa", Locale.US); + + private static int[] AUDIO_MANAGER_FLAGS = new int[] { + AudioManager.FLAG_SHOW_UI, + AudioManager.FLAG_VIBRATE, + AudioManager.FLAG_PLAY_SOUND, + AudioManager.FLAG_ALLOW_RINGER_MODES, + AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE, + AudioManager.FLAG_SHOW_VIBRATE_HINT, + AudioManager.FLAG_SHOW_SILENT_HINT, + AudioManager.FLAG_FROM_KEY, + }; + + 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", + }; + + public static String logTag(Class<?> c) { + final String tag = "vol." + c.getSimpleName(); + return tag.length() < 23 ? tag : tag.substring(0, 23); + } + + public static String ringerModeToString(int ringerMode) { + switch (ringerMode) { + case AudioManager.RINGER_MODE_SILENT: return "RINGER_MODE_SILENT"; + case AudioManager.RINGER_MODE_VIBRATE: return "RINGER_MODE_VIBRATE"; + case AudioManager.RINGER_MODE_NORMAL: return "RINGER_MODE_NORMAL"; + default: return "RINGER_MODE_UNKNOWN_" + ringerMode; + } + } + + public static String mediaMetadataToString(MediaMetadata metadata) { + return metadata.getDescription().toString(); + } + + public static String playbackInfoToString(PlaybackInfo info) { + if (info == null) return null; + final String type = playbackInfoTypeToString(info.getPlaybackType()); + final String vc = volumeProviderControlToString(info.getVolumeControl()); + return String.format("PlaybackInfo[vol=%s,max=%s,type=%s,vc=%s],atts=%s", + info.getCurrentVolume(), info.getMaxVolume(), type, vc, info.getAudioAttributes()); + } + + public static String playbackInfoTypeToString(int type) { + switch (type) { + case PlaybackInfo.PLAYBACK_TYPE_LOCAL: return "LOCAL"; + case PlaybackInfo.PLAYBACK_TYPE_REMOTE: return "REMOTE"; + default: return "UNKNOWN_" + type; + } + } + + public static String playbackStateStateToString(int state) { + switch (state) { + case PlaybackState.STATE_NONE: return "STATE_NONE"; + case PlaybackState.STATE_STOPPED: return "STATE_STOPPED"; + case PlaybackState.STATE_PAUSED: return "STATE_PAUSED"; + case PlaybackState.STATE_PLAYING: return "STATE_PLAYING"; + default: return "UNKNOWN_" + state; + } + } + + public static String volumeProviderControlToString(int control) { + switch (control) { + case VolumeProvider.VOLUME_CONTROL_ABSOLUTE: return "VOLUME_CONTROL_ABSOLUTE"; + case VolumeProvider.VOLUME_CONTROL_FIXED: return "VOLUME_CONTROL_FIXED"; + case VolumeProvider.VOLUME_CONTROL_RELATIVE: return "VOLUME_CONTROL_RELATIVE"; + default: return "VOLUME_CONTROL_UNKNOWN_" + control; + } + } + + public static String playbackStateToString(PlaybackState playbackState) { + if (playbackState == null) return null; + return playbackStateStateToString(playbackState.getState()) + " " + playbackState; + } + + public static String audioManagerFlagsToString(int value) { + return bitFieldToString(value, AUDIO_MANAGER_FLAGS, AUDIO_MANAGER_FLAG_NAMES); + } + + private static String bitFieldToString(int value, int[] values, String[] names) { + if (value == 0) return ""; + final StringBuilder sb = new StringBuilder(); + for (int i = 0; i < values.length; i++) { + if ((value & values[i]) != 0) { + if (sb.length() > 0) sb.append(','); + sb.append(names[i]); + } + value &= ~values[i]; + } + if (value != 0) { + if (sb.length() > 0) sb.append(','); + sb.append("UNKNOWN_").append(value); + } + return sb.toString(); + } + + public static String getShortTime(long millis) { + return HMMAA.format(new Date(millis)); + } + + public static String getShortTime(DowntimeInfo info) { + return ((info.endHour + 1) % 12) + ":" + (info.endMinute < 10 ? " " : "") + info.endMinute; + } + + public static void setText(TextView tv, CharSequence text) { + if (Objects.equals(tv.getText(), text)) return; + tv.setText(text); + } + + public static final void setVisOrGone(View v, boolean vis) { + if (v == null || (v.getVisibility() == View.VISIBLE) == vis) return; + v.setVisibility(vis ? View.VISIBLE : View.GONE); + } + + public static final void setVisOrInvis(View v, boolean vis) { + if (v == null || (v.getVisibility() == View.VISIBLE) == vis) return; + v.setVisibility(vis ? View.VISIBLE : View.INVISIBLE); + } + +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java index e3f8f3d..1f0ee57 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java @@ -16,10 +16,18 @@ package com.android.systemui.volume; +import android.content.res.Configuration; + import com.android.systemui.DemoMode; import com.android.systemui.statusbar.policy.ZenModeController; +import java.io.FileDescriptor; +import java.io.PrintWriter; + public interface VolumeComponent extends DemoMode { ZenModeController getZenController(); void dismissNow(); + void onConfigurationChanged(Configuration newConfig); + void dump(FileDescriptor fd, PrintWriter pw, String[] args); + void register(); } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java new file mode 100644 index 0000000..5fbb18d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java @@ -0,0 +1,1039 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume; + +import android.animation.LayoutTransition; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.annotation.SuppressLint; +import android.app.Dialog; +import android.app.KeyguardManager; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.AnimatedVectorDrawable; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.media.AudioManager; +import android.media.AudioSystem; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.SystemClock; +import android.provider.Settings.Global; +import android.service.notification.ZenModeConfig; +import android.service.notification.ZenModeConfig.DowntimeInfo; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.SparseBooleanArray; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnLayoutChangeListener; +import android.view.View.OnTouchListener; +import android.view.ViewGroup; +import android.view.ViewGroup.MarginLayoutParams; +import android.view.Window; +import android.view.WindowManager; +import android.view.animation.DecelerateInterpolator; +import android.widget.Button; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.SeekBar; +import android.widget.SeekBar.OnSeekBarChangeListener; +import android.widget.TextView; + +import com.android.systemui.R; +import com.android.systemui.statusbar.policy.ZenModeController; +import com.android.systemui.volume.VolumeDialogController.State; +import com.android.systemui.volume.VolumeDialogController.StreamState; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +/** + * Visual presentation of the volume dialog. + * + * A client of VolumeDialogController and its state model. + * + * Methods ending in "H" must be called on the (ui) handler. + */ +public class VolumeDialog { + private static final String TAG = Util.logTag(VolumeDialog.class); + + private static final long USER_ATTEMPT_GRACE_PERIOD = 1000; + private static final int WAIT_FOR_RIPPLE = 200; + private static final int UPDATE_ANIMATION_DURATION = 80; + + private final Context mContext; + private final H mHandler = new H(); + private final VolumeDialogController mController; + + private final CustomDialog mDialog; + private final ViewGroup mDialogView; + private final ViewGroup mDialogContentView; + private final ImageButton mExpandButton; + private final TextView mFootlineText; + private final Button mFootlineAction; + private final View mSettingsButton; + private final View mFooter; + private final List<VolumeRow> mRows = new ArrayList<VolumeRow>(); + private final SpTexts mSpTexts; + private final SparseBooleanArray mDynamic = new SparseBooleanArray(); + private final KeyguardManager mKeyguard; + private final int mExpandButtonAnimationDuration; + private final View mTextFooter; + private final ZenFooter mZenFooter; + private final LayoutTransition mLayoutTransition; + + private boolean mShowing; + private boolean mExpanded; + private int mActiveStream; + private boolean mShowHeaders = Prefs.DEFAULT_SHOW_HEADERS; + private boolean mShowFooter = Prefs.DEFAULT_SHOW_FOOTER; + private boolean mShowZenFooter = Prefs.DEFAULT_ZEN_FOOTER; + private boolean mAutomute = Prefs.DEFAULT_ENABLE_AUTOMUTE; + private boolean mSilentMode = Prefs.DEFAULT_ENABLE_SILENT_MODE; + private State mState; + private int mExpandAnimRes; + private boolean mExpanding; + + public VolumeDialog(Context context, VolumeDialogController controller, + ZenModeController zenModeController) { + mContext = context; + mController = controller; + mSpTexts = new SpTexts(mContext); + mKeyguard = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); + + mDialog = new CustomDialog(mContext); + + final Window window = mDialog.getWindow(); + window.requestFeature(Window.FEATURE_NO_TITLE); + window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); + window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED + | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH + | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); + mDialog.setCanceledOnTouchOutside(true); + final Resources res = mContext.getResources(); + final WindowManager.LayoutParams lp = window.getAttributes(); + lp.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR; + lp.format = PixelFormat.TRANSLUCENT; + lp.setTitle(VolumeDialog.class.getSimpleName()); + lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL; + lp.windowAnimations = R.style.VolumeDialogAnimations; + lp.y = res.getDimensionPixelSize(R.dimen.volume_offset_top); + lp.gravity = Gravity.TOP; + window.setAttributes(lp); + + mDialog.setContentView(R.layout.volume_dialog); + mDialogView = (ViewGroup) mDialog.findViewById(R.id.volume_dialog); + mDialogContentView = (ViewGroup) mDialog.findViewById(R.id.volume_dialog_content); + mExpandButton = (ImageButton) mDialogView.findViewById(R.id.volume_expand_button); + mExpandButton.setOnClickListener(mClickExpand); + updateWindowWidthH(); + updateExpandButtonH(); + mLayoutTransition = new LayoutTransition(); + mLayoutTransition.setDuration(new ValueAnimator().getDuration() / 2); + mLayoutTransition.disableTransitionType(LayoutTransition.DISAPPEARING); + mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING); + mDialogContentView.setLayoutTransition(mLayoutTransition); + + addRow(AudioManager.STREAM_RING, + R.drawable.ic_volume_ringer, R.drawable.ic_volume_ringer_mute, true); + addRow(AudioManager.STREAM_MUSIC, + R.drawable.ic_volume_media, R.drawable.ic_volume_media_mute, true); + addRow(AudioManager.STREAM_ALARM, + R.drawable.ic_volume_alarm, R.drawable.ic_volume_alarm_mute, false); + addRow(AudioManager.STREAM_VOICE_CALL, + R.drawable.ic_volume_voice, R.drawable.ic_volume_voice, false); + addRow(AudioManager.STREAM_BLUETOOTH_SCO, + R.drawable.ic_volume_bt_sco, R.drawable.ic_volume_bt_sco, false); + addRow(AudioManager.STREAM_SYSTEM, + R.drawable.ic_volume_system, R.drawable.ic_volume_system_mute, false); + + mTextFooter = mDialog.findViewById(R.id.volume_text_footer); + mFootlineText = (TextView) mDialog.findViewById(R.id.volume_footline_text); + mSpTexts.add(mFootlineText); + mFootlineAction = (Button) mDialog.findViewById(R.id.volume_footline_action_button); + mSpTexts.add(mFootlineAction); + mFooter = mDialog.findViewById(R.id.volume_footer); + mSettingsButton = mDialog.findViewById(R.id.volume_settings_button); + mSettingsButton.setOnClickListener(mClickSettings); + mExpandButtonAnimationDuration = res.getInteger(R.integer.volume_expand_animation_duration); + mZenFooter = (ZenFooter) mDialog.findViewById(R.id.volume_zen_footer); + mZenFooter.init(zenModeController, mZenFooterCallback); + + controller.addCallback(mControllerCallbackH, mHandler); + controller.getState(); + } + + private void updateWindowWidthH() { + final ViewGroup.LayoutParams lp = mDialogView.getLayoutParams(); + final DisplayMetrics dm = mContext.getResources().getDisplayMetrics(); + if (D.BUG) Log.d(TAG, "updateWindowWidth dm.w=" + dm.widthPixels); + int w = dm.widthPixels; + final int max = mContext.getResources() + .getDimensionPixelSize(R.dimen.standard_notification_panel_width); + if (w > max) { + w = max; + } + w -= mContext.getResources().getDimensionPixelSize(R.dimen.notification_side_padding) * 2; + lp.width = w; + mDialogView.setLayoutParams(lp); + } + + public void setStreamImportant(int stream, boolean important) { + mHandler.obtainMessage(H.SET_STREAM_IMPORTANT, stream, important ? 1 : 0).sendToTarget(); + } + + public void setShowHeaders(boolean showHeaders) { + if (showHeaders == mShowHeaders) return; + mShowHeaders = showHeaders; + mHandler.sendEmptyMessage(H.RECHECK_ALL); + } + + public void setShowFooter(boolean show) { + if (mShowFooter == show) return; + mShowFooter = show; + mHandler.sendEmptyMessage(H.RECHECK_ALL); + } + + public void setZenFooter(boolean zen) { + if (mShowZenFooter == zen) return; + mShowZenFooter = zen; + mHandler.sendEmptyMessage(H.RECHECK_ALL); + } + + public void setAutomute(boolean automute) { + if (mAutomute == automute) return; + mAutomute = automute; + mHandler.sendEmptyMessage(H.RECHECK_ALL); + } + + public void setSilentMode(boolean silentMode) { + if (mSilentMode == silentMode) return; + mSilentMode = silentMode; + mHandler.sendEmptyMessage(H.RECHECK_ALL); + } + + private void addRow(int stream, int iconRes, int iconMuteRes, boolean important) { + final VolumeRow row = initRow(stream, iconRes, iconMuteRes, important); + if (!mRows.isEmpty()) { + final View v = new View(mContext); + final int h = mContext.getResources() + .getDimensionPixelSize(R.dimen.volume_slider_interspacing); + final LinearLayout.LayoutParams lp = + new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, h); + mDialogContentView.addView(v, mDialogContentView.getChildCount() - 1, lp); + row.space = v; + } + row.settingsButton.addOnLayoutChangeListener(new OnLayoutChangeListener() { + @Override + public void onLayoutChange(View v, int left, int top, int right, int bottom, + int oldLeft, int oldTop, int oldRight, int oldBottom) { + if (D.BUG) Log.d(TAG, "onLayoutChange" + + " old=" + new Rect(oldLeft, oldTop, oldRight, oldBottom).toShortString() + + " new=" + new Rect(left,top,right,bottom).toShortString()); + if (oldLeft != left || oldTop != top) { + for (int i = 0; i < mDialogContentView.getChildCount(); i++) { + final View c = mDialogContentView.getChildAt(i); + if (!c.isShown()) continue; + if (c == row.view) { + repositionExpandAnim(row); + } + return; + } + } + } + }); + // add new row just before the footer + mDialogContentView.addView(row.view, mDialogContentView.getChildCount() - 1); + mRows.add(row); + } + + private boolean isAttached() { + return mDialogContentView != null && mDialogContentView.isAttachedToWindow(); + } + + private VolumeRow getActiveRow() { + for (VolumeRow row : mRows) { + if (row.stream == mActiveStream) { + return row; + } + } + return mRows.get(0); + } + + private VolumeRow findRow(int stream) { + for (VolumeRow row : mRows) { + if (row.stream == stream) return row; + } + return null; + } + + private void repositionExpandAnim(VolumeRow row) { + final int[] loc = new int[2]; + row.settingsButton.getLocationInWindow(loc); + final MarginLayoutParams mlp = (MarginLayoutParams) mDialogView.getLayoutParams(); + final int x = loc[0] - mlp.leftMargin; + final int y = loc[1] - mlp.topMargin; + if (D.BUG) Log.d(TAG, "repositionExpandAnim x=" + x + " y=" + y); + mExpandButton.setTranslationX(x); + mExpandButton.setTranslationY(y); + } + + public void dump(PrintWriter writer) { + writer.println(VolumeDialog.class.getSimpleName() + " state:"); + writer.print(" mShowing: "); writer.println(mShowing); + writer.print(" mExpanded: "); writer.println(mExpanded); + writer.print(" mExpanding: "); writer.println(mExpanding); + writer.print(" mActiveStream: "); writer.println(mActiveStream); + writer.print(" mDynamic: "); writer.println(mDynamic); + writer.print(" mShowHeaders: "); writer.println(mShowHeaders); + writer.print(" mShowFooter: "); writer.println(mShowFooter); + writer.print(" mAutomute: "); writer.println(mAutomute); + writer.print(" mSilentMode: "); writer.println(mSilentMode); + } + + private static int getImpliedLevel(SeekBar seekBar, int progress) { + final int m = seekBar.getMax(); + final int n = m / 100 - 1; + final int level = progress == 0 ? 0 + : progress == m ? (m / 100) : (1 + (int)((progress / (float) m) * n)); + return level; + } + + @SuppressLint("InflateParams") + private VolumeRow initRow(final int stream, int iconRes, int iconMuteRes, boolean important) { + final VolumeRow row = new VolumeRow(); + row.stream = stream; + row.iconRes = iconRes; + row.iconMuteRes = iconMuteRes; + row.important = important; + row.view = mDialog.getLayoutInflater().inflate(R.layout.volume_dialog_row, null); + row.view.setTag(row); + row.header = (TextView) row.view.findViewById(R.id.volume_row_header); + mSpTexts.add(row.header); + row.slider = (SeekBar) row.view.findViewById(R.id.volume_row_slider); + row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row)); + + // forward events above the slider into the slider + row.view.setOnTouchListener(new OnTouchListener() { + private final Rect mSliderHitRect = new Rect(); + private boolean mDragging; + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouch(View v, MotionEvent event) { + row.slider.getHitRect(mSliderHitRect); + if (!mDragging && event.getActionMasked() == MotionEvent.ACTION_DOWN + && event.getY() < mSliderHitRect.top) { + mDragging = true; + } + if (mDragging) { + event.offsetLocation(-mSliderHitRect.left, -mSliderHitRect.top); + row.slider.dispatchTouchEvent(event); + if (event.getActionMasked() == MotionEvent.ACTION_UP + || event.getActionMasked() == MotionEvent.ACTION_CANCEL) { + mDragging = false; + } + return true; + } + return false; + } + }); + row.icon = (ImageButton) row.view.findViewById(R.id.volume_row_icon); + row.icon.setImageResource(iconRes); + row.icon.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + Events.writeEvent(Events.EVENT_ICON_CLICK, row.stream, row.iconState); + mController.setActiveStream(row.stream); + if (row.stream == AudioManager.STREAM_RING) { + final boolean hasVibrator = mController.hasVibrator(); + if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) { + if (hasVibrator) { + mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, false); + } else { + final boolean wasZero = row.ss.level == 0; + mController.setStreamVolume(stream, wasZero ? row.lastAudibleLevel : 0); + } + } else { + mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false); + if (row.ss.level == 0) { + mController.setStreamVolume(stream, 1); + } + } + } else { + if (mAutomute && !row.ss.muteSupported) { + final boolean vmute = row.ss.level == 0; + mController.setStreamVolume(stream, vmute ? row.lastAudibleLevel : 0); + } else { + final boolean mute = !row.ss.muted; + mController.setStreamMute(stream, mute); + if (mAutomute) { + if (!mute && row.ss.level == 0) { + mController.setStreamVolume(stream, 1); + } + } + } + } + row.userAttempt = 0; // reset the grace period, slider should update immediately + } + }); + row.settingsButton = (ImageButton) row.view.findViewById(R.id.volume_settings_button); + row.settingsButton.setOnClickListener(mClickSettings); + return row; + } + + public void destroy() { + mController.removeCallback(mControllerCallbackH); + } + + public void show(int reason) { + mHandler.obtainMessage(H.SHOW, reason, 0).sendToTarget(); + } + + public void dismiss(int reason) { + mHandler.obtainMessage(H.DISMISS, reason, 0).sendToTarget(); + } + + protected void onSettingsClickedH() { + // hook for subclasses + } + + protected void onZenSettingsClickedH() { + // hook for subclasses + } + + private void showH(int reason) { + mHandler.removeMessages(H.SHOW); + mHandler.removeMessages(H.DISMISS); + rescheduleTimeoutH(); + if (mShowing) return; + mShowing = true; + mDialog.show(); + Events.writeEvent(Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked()); + mController.notifyVisible(true); + } + + protected void rescheduleTimeoutH() { + mHandler.removeMessages(H.DISMISS); + final int timeout = computeTimeoutH(); + if (D.BUG) Log.d(TAG, "rescheduleTimeout " + timeout); + mHandler.sendMessageDelayed(mHandler + .obtainMessage(H.DISMISS, Events.DISMISS_REASON_TIMEOUT, 0), timeout); + mController.userActivity(); + } + + private int computeTimeoutH() { + if (mZenFooter != null && mZenFooter.isFooterExpanded()) return 10000; + if (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); + } + + 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 == mExpandAnimRes) return; + mExpandAnimRes = res; + mExpandButton.setImageResource(res); + } + + private boolean isVisibleH(VolumeRow row, boolean isActive) { + return mExpanded && row.view.getVisibility() == View.VISIBLE + || (mExpanded && (row.important || isActive)) + || !mExpanded && isActive; + } + + private void updateRowsH() { + final VolumeRow activeRow = getActiveRow(); + updateFooterH(); + updateExpandButtonH(); + final boolean footerVisible = mFooter.getVisibility() == View.VISIBLE; + if (!mShowing) { + trimObsoleteH(); + } + // first, find the last visible row + VolumeRow lastVisible = null; + for (VolumeRow row : mRows) { + final boolean isActive = row == activeRow; + if (isVisibleH(row, isActive)) { + lastVisible = row; + } + } + // apply changes to all rows + for (VolumeRow row : mRows) { + final boolean isActive = row == activeRow; + final boolean visible = isVisibleH(row, isActive); + Util.setVisOrGone(row.view, visible); + Util.setVisOrGone(row.space, visible && mExpanded); + final int expandButtonRes = mExpanded ? R.drawable.ic_volume_settings : 0; + if (expandButtonRes != row.cachedExpandButtonRes) { + row.cachedExpandButtonRes = expandButtonRes; + if (expandButtonRes == 0) { + row.settingsButton.setImageDrawable(null); + } else { + row.settingsButton.setImageResource(expandButtonRes); + } + } + Util.setVisOrInvis(row.settingsButton, + mExpanded && (!footerVisible && row == lastVisible)); + row.header.setAlpha(mExpanded && isActive ? 1 : 0.5f); + } + } + + private void trimObsoleteH() { + for (int i = mRows.size() -1; i >= 0; i--) { + final VolumeRow row = mRows.get(i); + if (row.ss == null || !row.ss.dynamic) continue; + if (!mDynamic.get(row.stream)) { + mRows.remove(i); + mDialogContentView.removeView(row.view); + mDialogContentView.removeView(row.space); + } + } + } + + private void onStateChangedH(State state) { + mState = state; + mDynamic.clear(); + // add any new dynamic rows + for (int i = 0; i < state.states.size(); i++) { + final int stream = state.states.keyAt(i); + final StreamState ss = state.states.valueAt(i); + if (!ss.dynamic) continue; + mDynamic.put(stream, true); + if (findRow(stream) == null) { + addRow(stream, R.drawable.ic_volume_remote, R.drawable.ic_volume_remote_mute, true); + } + } + + if (mActiveStream != state.activeStream) { + mActiveStream = state.activeStream; + updateRowsH(); + rescheduleTimeoutH(); + } + for (VolumeRow row : mRows) { + updateVolumeRowH(row); + } + updateFooterH(); + } + + private void updateTextFooterH() { + final boolean zen = mState.zenMode != Global.ZEN_MODE_OFF; + final boolean wasVisible = mFooter.getVisibility() == View.VISIBLE; + Util.setVisOrGone(mTextFooter, mExpanded && mShowFooter && (zen || mShowing && wasVisible)); + if (mTextFooter.getVisibility() == View.VISIBLE) { + String text = null; + String action = null; + if (mState.exitCondition != null) { + final long countdown = ZenModeConfig.tryParseCountdownConditionId(mState + .exitCondition.id); + if (countdown != 0) { + text = mContext.getString(R.string.volume_dnd_ends_at, + Util.getShortTime(countdown)); + action = mContext.getString(R.string.volume_end_now); + } else { + final DowntimeInfo info = ZenModeConfig.tryParseDowntimeConditionId(mState. + exitCondition.id); + if (info != null) { + text = mContext.getString(R.string.volume_dnd_ends_at, + Util.getShortTime(info)); + action = mContext.getString(R.string.volume_end_now); + } + } + } + if (text == null) { + text = mContext.getString(R.string.volume_dnd_is_on); + } + if (action == null) { + action = mContext.getString(R.string.volume_turn_off); + } + Util.setText(mFootlineText, text); + Util.setText(mFootlineAction, action); + mFootlineAction.setOnClickListener(mTurnOffDnd); + } + Util.setVisOrGone(mFootlineText, zen); + Util.setVisOrGone(mFootlineAction, zen); + } + + private void updateFooterH() { + if (!mShowFooter) { + Util.setVisOrGone(mFooter, false); + return; + } + if (mShowZenFooter) { + Util.setVisOrGone(mTextFooter, false); + final boolean ringActive = mActiveStream == AudioManager.STREAM_RING; + Util.setVisOrGone(mZenFooter, mZenFooter.isZen() && ringActive + || mShowing && (mExpanded || mZenFooter.getVisibility() == View.VISIBLE)); + mZenFooter.update(); + } else { + Util.setVisOrGone(mZenFooter, false); + updateTextFooterH(); + } + } + + private void updateVolumeRowH(VolumeRow row) { + if (mState == null) return; + final StreamState ss = mState.states.get(row.stream); + if (ss == null) return; + row.ss = ss; + if (ss.level > 0) { + row.lastAudibleLevel = ss.level; + } + final boolean isRingStream = row.stream == AudioManager.STREAM_RING; + final boolean isSystemStream = row.stream == AudioManager.STREAM_SYSTEM; + final boolean isRingVibrate = isRingStream + && mState.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE; + final boolean isNoned = (isRingStream || isSystemStream) + && mState.zenMode == Global.ZEN_MODE_NO_INTERRUPTIONS; + final boolean isLimited = isRingStream + && mState.zenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + final boolean isRingAndSuppressed = isRingStream && mState.effectsSuppressor != null; + + // 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 (isRingAndSuppressed) { + text = mContext.getString(R.string.volume_stream_suppressed, ss.name, + mState.effectsSuppressorName); + } else if (isNoned) { + text = mContext.getString(R.string.volume_stream_muted_dnd, ss.name); + } else if (isRingVibrate && isLimited) { + text = mContext.getString(R.string.volume_stream_vibrate_dnd, ss.name); + } else if (isRingVibrate) { + text = mContext.getString(R.string.volume_stream_vibrate, ss.name); + } else if (ss.muted || mAutomute && ss.level == 0) { + text = mContext.getString(R.string.volume_stream_muted, ss.name); + } else if (isLimited) { + text = mContext.getString(R.string.volume_stream_limited_dnd, ss.name); + } else { + text = ss.name; + } + Util.setText(row.header, text); + + // update icon + final boolean iconEnabled = !isRingAndSuppressed && (mAutomute || ss.muteSupported); + row.icon.setEnabled(iconEnabled); + row.icon.setAlpha(iconEnabled ? 1 : 0.5f); + final int iconRes = + !isRingAndSuppressed && isRingVibrate ? R.drawable.ic_volume_ringer_vibrate + : ss.routedToBluetooth ? + (ss.muted ? R.drawable.ic_volume_bt_mute : R.drawable.ic_volume_bt) + : isRingAndSuppressed || (mAutomute && ss.level == 0) ? row.iconMuteRes + : (ss.muted ? row.iconMuteRes : row.iconRes); + if (iconRes != row.cachedIconRes) { + if (row.cachedIconRes != 0 && isRingVibrate) { + mController.vibrate(); + } + row.cachedIconRes = iconRes; + row.icon.setImageResource(iconRes); + } + row.iconState = + iconRes == R.drawable.ic_volume_ringer_vibrate ? Events.ICON_STATE_VIBRATE + : (iconRes == R.drawable.ic_volume_bt_mute || iconRes == row.iconMuteRes) + ? Events.ICON_STATE_MUTE + : (iconRes == R.drawable.ic_volume_bt || iconRes == row.iconRes) + ? Events.ICON_STATE_UNMUTE + : Events.ICON_STATE_UNKNOWN; + + // update slider + updateVolumeRowSliderH(row, isRingAndSuppressed); + } + + private void updateVolumeRowSliderH(VolumeRow row, boolean isRingAndSuppressed) { + row.slider.setEnabled(!isRingAndSuppressed); + if (row.tracking) { + return; // don't update if user is sliding + } + if (isRingAndSuppressed) { + row.slider.setProgress(0); + return; + } + 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 final VolumeDialogController.Callbacks mControllerCallbackH + = new VolumeDialogController.Callbacks() { + @Override + public void onShowRequested(int reason) { + showH(reason); + } + + @Override + public void onDismissRequested(int reason) { + dismissH(reason); + } + + 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); + } + } + + public void onShowSilentHint() { + if (mSilentMode) { + mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false); + } + } + }; + + private final OnClickListener mClickExpand = new OnClickListener() { + @Override + public void onClick(View v) { + if (mExpanding) return; + final boolean newExpand = !mExpanded; + Events.writeEvent(Events.EVENT_EXPAND, v); + setExpandedH(newExpand); + } + }; + + private final OnClickListener mClickSettings = new OnClickListener() { + @Override + public void onClick(View v) { + mSettingsButton.postDelayed(new Runnable() { + @Override + public void run() { + Events.writeEvent(Events.EVENT_SETTINGS_CLICK); + onSettingsClickedH(); + } + }, WAIT_FOR_RIPPLE); + } + }; + + private final View.OnClickListener mTurnOffDnd = new View.OnClickListener() { + @Override + public void onClick(View v) { + mSettingsButton.postDelayed(new Runnable() { + @Override + public void run() { + mController.setZenMode(Global.ZEN_MODE_OFF); + } + }, WAIT_FOR_RIPPLE); + } + }; + + private final ZenFooter.Callback mZenFooterCallback = new ZenFooter.Callback() { + @Override + public void onFooterExpanded() { + mHandler.sendEmptyMessage(H.RESCHEDULE_TIMEOUT); + } + + @Override + public void onSettingsClicked() { + dismiss(Events.DISMISS_REASON_SETTINGS_CLICKED); + onZenSettingsClickedH(); + } + + @Override + public void onDoneClicked() { + dismiss(Events.DISMISS_REASON_DONE_CLICKED); + } + }; + + private final class H extends Handler { + private static final int SHOW = 1; + private static final int DISMISS = 2; + private static final int RECHECK = 3; + private static final int RECHECK_ALL = 4; + private static final int SET_STREAM_IMPORTANT = 5; + private static final int RESCHEDULE_TIMEOUT = 6; + + public H() { + super(Looper.getMainLooper()); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case SHOW: showH(msg.arg1); break; + case DISMISS: dismissH(msg.arg1); break; + case RECHECK: recheckH((VolumeRow) msg.obj); break; + case RECHECK_ALL: recheckH(null); break; + case SET_STREAM_IMPORTANT: setStreamImportantH(msg.arg1, msg.arg2 != 0); break; + case RESCHEDULE_TIMEOUT: rescheduleTimeoutH(); break; + } + } + } + + private final class CustomDialog extends Dialog { + public CustomDialog(Context context) { + super(context); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + rescheduleTimeoutH(); + return super.dispatchTouchEvent(ev); + } + + @Override + protected void onStop() { + super.onStop(); + mHandler.sendEmptyMessage(H.RECHECK_ALL); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (isShowing()) { + if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { + dismissH(Events.DISMISS_REASON_TOUCH_OUTSIDE); + return true; + } + } + return false; + } + } + + private final class VolumeSeekBarChangeListener implements OnSeekBarChangeListener { + private final VolumeRow mRow; + + private VolumeSeekBarChangeListener(VolumeRow row) { + mRow = row; + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (mRow.ss == null) return; + if (D.BUG) Log.d(TAG, AudioSystem.streamToString(mRow.stream) + + " onProgressChanged " + progress + " fromUser=" + fromUser); + if (!fromUser) return; + if (mRow.ss.levelMin > 0) { + final int minProgress = mRow.ss.levelMin * 100; + if (progress < minProgress) { + seekBar.setProgress(minProgress); + } + } + final int userLevel = getImpliedLevel(seekBar, progress); + if (mRow.ss.level != userLevel || mRow.ss.muted && userLevel > 0) { + mRow.userAttempt = SystemClock.uptimeMillis(); + if (mAutomute) { + if (mRow.stream != AudioManager.STREAM_RING) { + if (userLevel > 0 && mRow.ss.muted) { + mController.setStreamMute(mRow.stream, false); + } + if (userLevel == 0 && mRow.ss.muteSupported && !mRow.ss.muted) { + mController.setStreamMute(mRow.stream, true); + } + } + } + if (mRow.requestedLevel != userLevel) { + mController.setStreamVolume(mRow.stream, userLevel); + mRow.requestedLevel = userLevel; + Events.writeEvent(Events.EVENT_TOUCH_LEVEL_CHANGED, mRow.stream, userLevel); + } + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + if (D.BUG) Log.d(TAG, "onStartTrackingTouch"+ " " + mRow.stream); + mController.setActiveStream(mRow.stream); + mRow.tracking = true; + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + if (D.BUG) Log.d(TAG, "onStopTrackingTouch"+ " " + mRow.stream); + mRow.tracking = false; + mRow.userAttempt = SystemClock.uptimeMillis(); + int userLevel = getImpliedLevel(seekBar, seekBar.getProgress()); + if (mRow.ss.level != userLevel) { + mHandler.sendMessageDelayed(mHandler.obtainMessage(H.RECHECK, mRow), + USER_ATTEMPT_GRACE_PERIOD); + } + } + } + + private static class VolumeRow { + private View view; + private View space; + private TextView header; + private ImageButton icon; + private SeekBar slider; + private ImageButton settingsButton; + private int stream; + private StreamState ss; + private long userAttempt; // last user-driven slider change + private boolean tracking; // tracking slider touch + private int requestedLevel; + private int iconRes; + private int iconMuteRes; + private boolean important; + private int cachedIconRes; + private int iconState; // from Events + private boolean cachedShowHeaders = Prefs.DEFAULT_SHOW_HEADERS; + private int cachedExpandButtonRes; + private ObjectAnimator anim; // slider progress animation for non-touch-related updates + private int animTargetProgress; + private int lastAudibleLevel = 1; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java new file mode 100644 index 0000000..741e498 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume; + +import android.content.Context; +import android.content.res.Configuration; +import android.media.AudioManager; +import android.media.VolumePolicy; +import android.os.Bundle; +import android.os.Handler; + +import com.android.systemui.SystemUI; +import com.android.systemui.keyguard.KeyguardViewMediator; +import com.android.systemui.qs.tiles.DndTile; +import com.android.systemui.statusbar.phone.PhoneStatusBar; +import com.android.systemui.statusbar.policy.ZenModeController; + +import java.io.FileDescriptor; +import java.io.PrintWriter; + +/** + * Implementation of VolumeComponent backed by the new volume dialog. + */ +public class VolumeDialogComponent implements VolumeComponent { + private final SystemUI mSysui; + private final Context mContext; + private final VolumeDialogController mController; + private final ZenModeController mZenModeController; + private final VolumeDialog mDialog; + + public VolumeDialogComponent(SystemUI sysui, Context context, Handler handler, + ZenModeController zen) { + mSysui = sysui; + mContext = context; + mController = new VolumeDialogController(context, null) { + @Override + protected void onUserActivityW() { + sendUserActivity(); + } + }; + mZenModeController = zen; + mDialog = new VolumeDialog(context, mController, zen) { + @Override + protected void onZenSettingsClickedH() { + startZenSettings(); + } + }; + applyConfiguration(); + } + + private void sendUserActivity() { + final KeyguardViewMediator kvm = mSysui.getComponent(KeyguardViewMediator.class); + if (kvm != null) { + kvm.userActivity(); + } + } + + private void applyConfiguration() { + mDialog.setStreamImportant(AudioManager.STREAM_ALARM, true); + mDialog.setStreamImportant(AudioManager.STREAM_SYSTEM, false); + mDialog.setShowHeaders(false); + mDialog.setShowFooter(true); + mDialog.setZenFooter(true); + mDialog.setAutomute(true); + mDialog.setSilentMode(false); + mController.setVolumePolicy(VolumePolicy.DEFAULT); + mController.showDndTile(false); + } + + @Override + public ZenModeController getZenController() { + return mZenModeController; + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + // noop + } + + @Override + public void dismissNow() { + mController.dismiss(); + } + + @Override + public void dispatchDemoCommand(String command, Bundle args) { + // noop + } + + @Override + public void register() { + mController.register(); + DndTile.setCombinedIcon(mContext, true); + } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + mController.dump(fd, pw, args); + mDialog.dump(pw); + } + + private void startZenSettings() { + mSysui.getComponent(PhoneStatusBar.class).startActivityDismissingKeyguard( + ZenModePanel.ZEN_SETTINGS, true /* onlyProvisioned */, true /* dismissShade */); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java new file mode 100644 index 0000000..ae5312e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java @@ -0,0 +1,962 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume; + +import android.app.NotificationManager; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.database.ContentObserver; +import android.media.AudioManager; +import android.media.AudioSystem; +import android.media.IVolumeController; +import android.media.VolumePolicy; +import android.media.session.MediaController.PlaybackInfo; +import android.media.session.MediaSession.Token; +import android.net.Uri; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.os.Vibrator; +import android.provider.Settings; +import android.service.notification.Condition; +import android.util.Log; +import android.util.SparseArray; + +import com.android.systemui.R; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * Source of truth for all state / events related to the volume dialog. No presentation. + * + * All work done on a dedicated background worker thread & associated worker. + * + * Methods ending in "W" must be called on the worker thread. + */ +public class VolumeDialogController { + private static final String TAG = Util.logTag(VolumeDialogController.class); + + private static final int DYNAMIC_STREAM_START_INDEX = 100; + private static final int VIBRATE_HINT_DURATION = 50; + + private static final int[] STREAMS = { + AudioSystem.STREAM_ALARM, + AudioSystem.STREAM_BLUETOOTH_SCO, + AudioSystem.STREAM_DTMF, + AudioSystem.STREAM_MUSIC, + AudioSystem.STREAM_NOTIFICATION, + AudioSystem.STREAM_RING, + AudioSystem.STREAM_SYSTEM, + AudioSystem.STREAM_SYSTEM_ENFORCED, + AudioSystem.STREAM_TTS, + AudioSystem.STREAM_VOICE_CALL, + }; + + private final HandlerThread mWorkerThread; + private final W mWorker; + private final Context mContext; + private final AudioManager mAudio; + private final NotificationManager mNoMan; + private final ComponentName mComponent; + private final SettingObserver mObserver; + private final Receiver mReceiver = new Receiver(); + private final MediaSessions mMediaSessions; + private final VC mVolumeController = new VC(); + private final C mCallbacks = new C(); + private final State mState = new State(); + private final String[] mStreamTitles; + private final MediaSessionsCallbacks mMediaSessionsCallbacksW = new MediaSessionsCallbacks(); + private final Vibrator mVibrator; + private final boolean mHasVibrator; + + private boolean mEnabled; + private boolean mDestroyed; + private VolumePolicy mVolumePolicy = new VolumePolicy(true, true, false, 400); + private boolean mShowDndTile = false; + + public VolumeDialogController(Context context, ComponentName component) { + mContext = context.getApplicationContext(); + Events.writeEvent(Events.EVENT_COLLECTION_STARTED); + mComponent = component; + mWorkerThread = new HandlerThread(VolumeDialogController.class.getSimpleName()); + mWorkerThread.start(); + mWorker = new W(mWorkerThread.getLooper()); + mMediaSessions = createMediaSessions(mContext, mWorkerThread.getLooper(), + mMediaSessionsCallbacksW); + mAudio = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + mNoMan = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); + mObserver = new SettingObserver(mWorker); + mObserver.init(); + mReceiver.init(); + mStreamTitles = mContext.getResources().getStringArray(R.array.volume_stream_titles); + mVibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE); + mHasVibrator = mVibrator != null && mVibrator.hasVibrator(); + } + + public void dismiss() { + mCallbacks.onDismissRequested(Events.DISMISS_REASON_VOLUME_CONTROLLER); + } + + public void register() { + try { + mAudio.setVolumeController(mVolumeController); + } catch (SecurityException e) { + Log.w(TAG, "Unable to set the volume controller", e); + return; + } + setVolumePolicy(mVolumePolicy); + showDndTile(mShowDndTile); + try { + mMediaSessions.init(); + } catch (SecurityException e) { + Log.w(TAG, "No access to media sessions", e); + } + } + + public void setVolumePolicy(VolumePolicy policy) { + mVolumePolicy = policy; + try { + mAudio.setVolumePolicy(mVolumePolicy); + } catch (NoSuchMethodError e) { + Log.w(TAG, "No volume policy api"); + } + } + + protected MediaSessions createMediaSessions(Context context, Looper looper, + MediaSessions.Callbacks callbacks) { + return new MediaSessions(context, looper, callbacks); + } + + public void destroy() { + if (D.BUG) Log.d(TAG, "destroy"); + if (mDestroyed) return; + mDestroyed = true; + Events.writeEvent(Events.EVENT_COLLECTION_STOPPED); + mMediaSessions.destroy(); + mObserver.destroy(); + mReceiver.destroy(); + mWorkerThread.quitSafely(); + } + + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println(VolumeDialogController.class.getSimpleName() + " state:"); + pw.print(" mEnabled: "); pw.println(mEnabled); + pw.print(" mDestroyed: "); pw.println(mDestroyed); + pw.print(" mVolumePolicy: "); pw.println(mVolumePolicy); + pw.print(" mEnabled: "); pw.println(mEnabled); + pw.print(" mShowDndTile: "); pw.println(mShowDndTile); + pw.print(" mHasVibrator: "); pw.println(mHasVibrator); + pw.print(" mRemoteStreams: "); pw.println(mMediaSessionsCallbacksW.mRemoteStreams + .values()); + pw.println(); + mMediaSessions.dump(pw); + } + + public void addCallback(Callbacks callback, Handler handler) { + mCallbacks.add(callback, handler); + } + + public void removeCallback(Callbacks callback) { + mCallbacks.remove(callback); + } + + public void getState() { + if (mDestroyed) return; + mWorker.sendEmptyMessage(W.GET_STATE); + } + + public void notifyVisible(boolean visible) { + if (mDestroyed) return; + mWorker.obtainMessage(W.NOTIFY_VISIBLE, visible ? 1 : 0, 0).sendToTarget(); + } + + public void userActivity() { + if (mDestroyed) return; + mWorker.removeMessages(W.USER_ACTIVITY); + mWorker.sendEmptyMessage(W.USER_ACTIVITY); + } + + public void setRingerMode(int value, boolean external) { + if (mDestroyed) return; + mWorker.obtainMessage(W.SET_RINGER_MODE, value, external ? 1 : 0).sendToTarget(); + } + + public void setZenMode(int value) { + if (mDestroyed) return; + mWorker.obtainMessage(W.SET_ZEN_MODE, value, 0).sendToTarget(); + } + + public void setExitCondition(Condition condition) { + if (mDestroyed) return; + mWorker.obtainMessage(W.SET_EXIT_CONDITION, condition).sendToTarget(); + } + + public void setStreamMute(int stream, boolean mute) { + if (mDestroyed) return; + mWorker.obtainMessage(W.SET_STREAM_MUTE, stream, mute ? 1 : 0).sendToTarget(); + } + + public void setStreamVolume(int stream, int level) { + if (mDestroyed) return; + mWorker.obtainMessage(W.SET_STREAM_VOLUME, stream, level).sendToTarget(); + } + + public void setActiveStream(int stream) { + if (mDestroyed) return; + mWorker.obtainMessage(W.SET_ACTIVE_STREAM, stream, 0).sendToTarget(); + } + + public void vibrate() { + if (mHasVibrator) { + mVibrator.vibrate(VIBRATE_HINT_DURATION); + } + } + + public boolean hasVibrator() { + return mHasVibrator; + } + + private void onNotifyVisibleW(boolean visible) { + if (mDestroyed) return; + mAudio.notifyVolumeControllerVisible(mVolumeController, visible); + if (!visible) { + if (updateActiveStreamW(-1)) { + mCallbacks.onStateChanged(mState); + } + } + } + + protected void onUserActivityW() { + // hook for subclasses + } + + private boolean checkRoutedToBluetoothW(int stream) { + boolean changed = false; + if (stream == AudioManager.STREAM_MUSIC) { + final boolean routedToBluetooth = + (mAudio.getDevicesForStream(AudioManager.STREAM_MUSIC) & + (AudioManager.DEVICE_OUT_BLUETOOTH_A2DP | + AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES | + AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER)) != 0; + changed |= updateStreamRoutedToBluetoothW(stream, routedToBluetooth); + } + return changed; + } + + private void onVolumeChangedW(int stream, int flags) { + final boolean showUI = (flags & AudioManager.FLAG_SHOW_UI) != 0; + final boolean fromKey = (flags & AudioManager.FLAG_FROM_KEY) != 0; + final boolean showVibrateHint = (flags & AudioManager.FLAG_SHOW_VIBRATE_HINT) != 0; + final boolean showSilentHint = (flags & AudioManager.FLAG_SHOW_SILENT_HINT) != 0; + boolean changed = false; + if (showUI) { + changed |= updateActiveStreamW(stream); + } + changed |= updateStreamLevelW(stream, mAudio.getLastAudibleStreamVolume(stream)); + changed |= checkRoutedToBluetoothW(showUI ? AudioManager.STREAM_MUSIC : stream); + if (changed) { + mCallbacks.onStateChanged(mState); + } + if (showUI) { + mCallbacks.onShowRequested(Events.SHOW_REASON_VOLUME_CHANGED); + } + if (showVibrateHint) { + mCallbacks.onShowVibrateHint(); + } + if (showSilentHint) { + mCallbacks.onShowSilentHint(); + } + if (changed && fromKey) { + Events.writeEvent(Events.EVENT_KEY); + } + } + + private boolean updateActiveStreamW(int activeStream) { + if (activeStream == mState.activeStream) return false; + mState.activeStream = activeStream; + Events.writeEvent(Events.EVENT_ACTIVE_STREAM_CHANGED, activeStream); + if (D.BUG) Log.d(TAG, "updateActiveStreamW " + activeStream); + final int s = activeStream < DYNAMIC_STREAM_START_INDEX ? activeStream : -1; + if (D.BUG) Log.d(TAG, "forceVolumeControlStream " + s); + mAudio.forceVolumeControlStream(s); + return true; + } + + private StreamState streamStateW(int stream) { + StreamState ss = mState.states.get(stream); + if (ss == null) { + ss = new StreamState(); + mState.states.put(stream, ss); + } + return ss; + } + + private void onGetStateW() { + for (int stream : STREAMS) { + updateStreamLevelW(stream, mAudio.getLastAudibleStreamVolume(stream)); + streamStateW(stream).levelMin = mAudio.getStreamMinVolume(stream); + streamStateW(stream).levelMax = mAudio.getStreamMaxVolume(stream); + updateStreamMuteW(stream, mAudio.isStreamMute(stream)); + final StreamState ss = streamStateW(stream); + ss.muteSupported = mAudio.isStreamAffectedByMute(stream); + ss.name = mStreamTitles[stream]; + checkRoutedToBluetoothW(stream); + } + updateRingerModeExternalW(mAudio.getRingerMode()); + updateZenModeW(); + updateEffectsSuppressorW(mNoMan.getEffectsSuppressor()); + updateExitConditionW(); + mCallbacks.onStateChanged(mState); + } + + private boolean updateStreamRoutedToBluetoothW(int stream, boolean routedToBluetooth) { + final StreamState ss = streamStateW(stream); + if (ss.routedToBluetooth == routedToBluetooth) return false; + ss.routedToBluetooth = routedToBluetooth; + if (D.BUG) Log.d(TAG, "updateStreamRoutedToBluetoothW stream=" + stream + + " routedToBluetooth=" + routedToBluetooth); + return true; + } + + private boolean updateStreamLevelW(int stream, int level) { + final StreamState ss = streamStateW(stream); + if (ss.level == level) return false; + ss.level = level; + if (isLogWorthy(stream)) { + Events.writeEvent(Events.EVENT_LEVEL_CHANGED, stream, level); + } + return true; + } + + private static boolean isLogWorthy(int stream) { + switch (stream) { + case AudioSystem.STREAM_ALARM: + case AudioSystem.STREAM_BLUETOOTH_SCO: + case AudioSystem.STREAM_MUSIC: + case AudioSystem.STREAM_RING: + case AudioSystem.STREAM_SYSTEM: + case AudioSystem.STREAM_VOICE_CALL: + return true; + } + return false; + } + + private boolean updateStreamMuteW(int stream, boolean muted) { + final StreamState ss = streamStateW(stream); + if (ss.muted == muted) return false; + ss.muted = muted; + if (isLogWorthy(stream)) { + Events.writeEvent(Events.EVENT_MUTE_CHANGED, stream, muted); + } + if (muted && isRinger(stream)) { + updateRingerModeInternalW(mAudio.getRingerModeInternal()); + } + return true; + } + + private static boolean isRinger(int stream) { + return stream == AudioManager.STREAM_RING || stream == AudioManager.STREAM_NOTIFICATION; + } + + private boolean updateExitConditionW() { + final Condition exitCondition = mNoMan.getZenModeCondition(); + if (Objects.equals(mState.exitCondition, exitCondition)) return false; + mState.exitCondition = exitCondition; + return true; + } + + private boolean updateEffectsSuppressorW(ComponentName effectsSuppressor) { + if (Objects.equals(mState.effectsSuppressor, effectsSuppressor)) return false; + mState.effectsSuppressor = effectsSuppressor; + mState.effectsSuppressorName = getApplicationName(mContext, mState.effectsSuppressor); + Events.writeEvent(Events.EVENT_SUPPRESSOR_CHANGED, mState.effectsSuppressor, + mState.effectsSuppressorName); + return true; + } + + private static String getApplicationName(Context context, ComponentName component) { + if (component == null) return null; + final PackageManager pm = context.getPackageManager(); + final String pkg = component.getPackageName(); + try { + final ApplicationInfo ai = pm.getApplicationInfo(pkg, 0); + final String rt = Objects.toString(ai.loadLabel(pm), "").trim(); + if (rt.length() > 0) { + return rt; + } + } catch (NameNotFoundException e) {} + return pkg; + } + + private boolean updateZenModeW() { + final int zen = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF); + if (mState.zenMode == zen) return false; + mState.zenMode = zen; + Events.writeEvent(Events.EVENT_ZEN_MODE_CHANGED, zen); + return true; + } + + private boolean updateRingerModeExternalW(int rm) { + if (rm == mState.ringerModeExternal) return false; + mState.ringerModeExternal = rm; + Events.writeEvent(Events.EVENT_EXTERNAL_RINGER_MODE_CHANGED, rm); + return true; + } + + private boolean updateRingerModeInternalW(int rm) { + if (rm == mState.ringerModeInternal) return false; + mState.ringerModeInternal = rm; + Events.writeEvent(Events.EVENT_INTERNAL_RINGER_MODE_CHANGED, rm); + return true; + } + + private void onSetRingerModeW(int mode, boolean external) { + if (external) { + mAudio.setRingerMode(mode); + } else { + mAudio.setRingerModeInternal(mode); + } + } + + private void onSetStreamMuteW(int stream, boolean mute) { + mAudio.adjustStreamVolume(stream, mute ? AudioManager.ADJUST_MUTE + : AudioManager.ADJUST_UNMUTE, 0); + } + + private void onSetStreamVolumeW(int stream, int level) { + if (D.BUG) Log.d(TAG, "onSetStreamVolume " + stream + " level=" + level); + if (stream >= DYNAMIC_STREAM_START_INDEX) { + mMediaSessionsCallbacksW.setStreamVolume(stream, level); + return; + } + mAudio.setStreamVolume(stream, level, 0); + } + + private void onSetActiveStreamW(int stream) { + boolean changed = updateActiveStreamW(stream); + if (changed) { + mCallbacks.onStateChanged(mState); + } + } + + private void onSetExitConditionW(Condition condition) { + mNoMan.setZenModeCondition(condition); + } + + private void onSetZenModeW(int mode) { + if (D.BUG) Log.d(TAG, "onSetZenModeW " + mode); + mNoMan.setZenMode(mode); + } + + private void onDismissRequestedW(int reason) { + mCallbacks.onDismissRequested(reason); + } + + public void showDndTile(boolean visible) { + if (D.BUG) Log.d(TAG, "showDndTile"); + mContext.sendBroadcast(new Intent("com.android.systemui.dndtile.SET_VISIBLE") + .putExtra("visible", visible)); + } + + private final class VC extends IVolumeController.Stub { + private final String TAG = VolumeDialogController.TAG + ".VC"; + + @Override + public void displaySafeVolumeWarning(int flags) throws RemoteException { + // noop + } + + @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; + + 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); + case USER_ACTIVITY: onUserActivityW(); + } + } + } + + 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(); + } + }); + } + } + } + + + private final class SettingObserver extends ContentObserver { + private final Uri SERVICE_URI = Settings.Secure.getUriFor( + Settings.Secure.VOLUME_CONTROLLER_SERVICE_COMPONENT); + private final Uri ZEN_MODE_URI = + Settings.Global.getUriFor(Settings.Global.ZEN_MODE); + private final Uri ZEN_MODE_CONFIG_URI = + Settings.Global.getUriFor(Settings.Global.ZEN_MODE_CONFIG_ETAG); + + public SettingObserver(Handler handler) { + super(handler); + } + + public void init() { + mContext.getContentResolver().registerContentObserver(SERVICE_URI, false, this); + mContext.getContentResolver().registerContentObserver(ZEN_MODE_URI, false, this); + mContext.getContentResolver().registerContentObserver(ZEN_MODE_CONFIG_URI, false, this); + onChange(true, SERVICE_URI); + } + + public void destroy() { + mContext.getContentResolver().unregisterContentObserver(this); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + boolean changed = false; + if (SERVICE_URI.equals(uri)) { + final String setting = Settings.Secure.getString(mContext.getContentResolver(), + Settings.Secure.VOLUME_CONTROLLER_SERVICE_COMPONENT); + final boolean enabled = setting != null && mComponent != null + && mComponent.equals(ComponentName.unflattenFromString(setting)); + if (enabled == mEnabled) return; + if (enabled) { + register(); + } + mEnabled = enabled; + } + if (ZEN_MODE_URI.equals(uri)) { + changed = updateZenModeW(); + } + if (ZEN_MODE_CONFIG_URI.equals(uri)) { + changed = updateExitConditionW(); + } + if (changed) { + mCallbacks.onStateChanged(mState); + } + } + } + + private final class Receiver extends BroadcastReceiver { + + public void init() { + final IntentFilter filter = new IntentFilter(); + filter.addAction(AudioManager.VOLUME_CHANGED_ACTION); + filter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION); + filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); + filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION); + filter.addAction(AudioManager.STREAM_MUTE_CHANGED_ACTION); + filter.addAction(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED); + filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); + filter.addAction(Intent.ACTION_SCREEN_OFF); + mContext.registerReceiver(this, filter, null, mWorker); + } + + public void destroy() { + mContext.unregisterReceiver(this); + } + + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + boolean changed = false; + if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) { + final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); + final int level = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1); + final int oldLevel = intent + .getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, -1); + if (D.BUG) Log.d(TAG, "onReceive VOLUME_CHANGED_ACTION stream=" + stream + + " level=" + level + " oldLevel=" + oldLevel); + changed = updateStreamLevelW(stream, level); + } else if (action.equals(AudioManager.STREAM_DEVICES_CHANGED_ACTION)) { + final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); + final int devices = intent + .getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_DEVICES, -1); + final int oldDevices = intent + .getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_DEVICES, -1); + if (D.BUG) Log.d(TAG, "onReceive STREAM_DEVICES_CHANGED_ACTION stream=" + + stream + " devices=" + devices + " oldDevices=" + oldDevices); + changed = checkRoutedToBluetoothW(stream); + } else if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) { + final int rm = intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1); + if (D.BUG) Log.d(TAG, "onReceive RINGER_MODE_CHANGED_ACTION rm=" + + Util.ringerModeToString(rm)); + changed = updateRingerModeExternalW(rm); + } else if (action.equals(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION)) { + final int rm = intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1); + if (D.BUG) Log.d(TAG, "onReceive INTERNAL_RINGER_MODE_CHANGED_ACTION rm=" + + Util.ringerModeToString(rm)); + changed = updateRingerModeInternalW(rm); + } else if (action.equals(AudioManager.STREAM_MUTE_CHANGED_ACTION)) { + final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); + final boolean muted = intent + .getBooleanExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, false); + if (D.BUG) Log.d(TAG, "onReceive STREAM_MUTE_CHANGED_ACTION stream=" + stream + + " muted=" + muted); + changed = updateStreamMuteW(stream, muted); + } else if (action.equals(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED)) { + if (D.BUG) Log.d(TAG, "onReceive ACTION_EFFECTS_SUPPRESSOR_CHANGED"); + changed = updateEffectsSuppressorW(mNoMan.getEffectsSuppressor()); + } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) { + if (D.BUG) Log.d(TAG, "onReceive ACTION_CONFIGURATION_CHANGED"); + mCallbacks.onConfigurationChanged(); + } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { + if (D.BUG) Log.d(TAG, "onReceive ACTION_SCREEN_OFF"); + mCallbacks.onScreenOff(); + } + if (changed) { + mCallbacks.onStateChanged(mState); + } + } + } + + private final class MediaSessionsCallbacks implements MediaSessions.Callbacks { + private final HashMap<Token, Integer> mRemoteStreams = new HashMap<>(); + + private int mNextStream = DYNAMIC_STREAM_START_INDEX; + + @Override + public void onRemoteUpdate(Token token, String name, PlaybackInfo pi) { + if (!mRemoteStreams.containsKey(token)) { + mRemoteStreams.put(token, mNextStream); + if (D.BUG) Log.d(TAG, "onRemoteUpdate: " + name + " is stream " + mNextStream); + mNextStream++; + } + final int stream = mRemoteStreams.get(token); + boolean changed = mState.states.indexOfKey(stream) < 0; + final StreamState ss = streamStateW(stream); + ss.dynamic = true; + ss.levelMin = 0; + ss.levelMax = pi.getMaxVolume(); + if (ss.level != pi.getCurrentVolume()) { + ss.level = pi.getCurrentVolume(); + changed = true; + } + if (!Objects.equals(ss.name, name)) { + ss.name = name; + changed = true; + } + if (changed) { + if (D.BUG) Log.d(TAG, "onRemoteUpdate: " + name + ": " + ss.level + + " of " + ss.levelMax); + mCallbacks.onStateChanged(mState); + } + } + + @Override + public void onRemoteVolumeChanged(Token token, int flags) { + final int stream = mRemoteStreams.get(token); + final boolean showUI = (flags & AudioManager.FLAG_SHOW_UI) != 0; + boolean changed = updateActiveStreamW(stream); + if (showUI) { + changed |= checkRoutedToBluetoothW(AudioManager.STREAM_MUSIC); + } + if (changed) { + mCallbacks.onStateChanged(mState); + } + if (showUI) { + mCallbacks.onShowRequested(Events.SHOW_REASON_REMOTE_VOLUME_CHANGED); + } + } + + @Override + public void onRemoteRemoved(Token token) { + final int stream = mRemoteStreams.get(token); + mState.states.remove(stream); + if (mState.activeStream == stream) { + updateActiveStreamW(-1); + } + mCallbacks.onStateChanged(mState); + } + + public void setStreamVolume(int stream, int level) { + final Token t = findToken(stream); + if (t == null) { + Log.w(TAG, "setStreamVolume: No token found for stream: " + stream); + return; + } + mMediaSessions.setVolume(t, level); + } + + private Token findToken(int stream) { + for (Map.Entry<Token, Integer> entry : mRemoteStreams.entrySet()) { + if (entry.getValue().equals(stream)) { + return entry.getKey(); + } + } + return null; + } + } + + public static final class StreamState { + public boolean dynamic; + public int level; + public int levelMin; + public int levelMax; + public boolean muted; + public boolean muteSupported; + public String name; + public boolean routedToBluetooth; + + public StreamState copy() { + final StreamState rt = new StreamState(); + rt.dynamic = dynamic; + rt.level = level; + rt.levelMin = levelMin; + rt.levelMax = levelMax; + rt.muted = muted; + rt.muteSupported = muteSupported; + rt.name = name; + rt.routedToBluetooth = routedToBluetooth; + return rt; + } + } + + public static final class State { + public static int NO_ACTIVE_STREAM = -1; + + public final SparseArray<StreamState> states = new SparseArray<StreamState>(); + + public int ringerModeInternal; + public int ringerModeExternal; + public int zenMode; + public ComponentName effectsSuppressor; + public String effectsSuppressorName; + public Condition exitCondition; + public int activeStream = NO_ACTIVE_STREAM; + + public State copy() { + final State rt = new State(); + for (int i = 0; i < states.size(); i++) { + rt.states.put(states.keyAt(i), states.valueAt(i).copy()); + } + rt.ringerModeExternal = ringerModeExternal; + rt.ringerModeInternal = ringerModeInternal; + rt.zenMode = zenMode; + if (effectsSuppressor != null) rt.effectsSuppressor = effectsSuppressor.clone(); + rt.effectsSuppressorName = effectsSuppressorName; + if (exitCondition != null) rt.exitCondition = exitCondition.copy(); + rt.activeStream = activeStream; + return rt; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("{"); + for (int i = 0; i < states.size(); i++) { + if (i > 0) sb.append(','); + final int stream = states.keyAt(i); + final StreamState ss = states.valueAt(i); + sb.append(AudioSystem.streamToString(stream)).append(":").append(ss.level) + .append("[").append(ss.levelMin).append("..").append(ss.levelMax); + if (ss.muted) sb.append(" [MUTED]"); + } + sb.append(",ringerModeExternal:").append(ringerModeExternal); + sb.append(",ringerModeInternal:").append(ringerModeInternal); + sb.append(",zenMode:").append(zenMode); + sb.append(",effectsSuppressor:").append(effectsSuppressor); + sb.append(",effectsSuppressorName:").append(effectsSuppressorName); + sb.append(",exitCondition:").append(exitCondition); + sb.append(",activeStream:").append(activeStream); + return sb.append('}').toString(); + } + } + + public interface Callbacks { + void onShowRequested(int reason); + void onDismissRequested(int reason); + void onStateChanged(State state); + void onLayoutDirectionChanged(int layoutDirection); + void onConfigurationChanged(); + void onShowVibrateHint(); + void onShowSilentHint(); + void onScreenOff(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java index acdcfc1..50d5f7a 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java @@ -37,7 +37,6 @@ import android.graphics.PixelFormat; import android.graphics.drawable.ColorDrawable; import android.media.AudioAttributes; import android.media.AudioManager; -import android.media.AudioService; import android.media.AudioSystem; import android.media.RingtoneManager; import android.media.ToneGenerator; @@ -88,7 +87,7 @@ public class VolumePanel extends Handler implements DemoMode { private static final String TAG = "VolumePanel"; private static boolean LOGD = Log.isLoggable(TAG, Log.DEBUG); - private static final int PLAY_SOUND_DELAY = AudioService.PLAY_SOUND_DELAY; + private static final int PLAY_SOUND_DELAY = AudioSystem.PLAY_SOUND_DELAY; /** * The delay before vibrating. This small period exists so if the user is @@ -126,8 +125,6 @@ public class VolumePanel extends Handler implements DemoMode { private static final int MSG_NOTIFICATION_EFFECTS_SUPPRESSOR_CHANGED = 15; private static final int MSG_INTERNAL_RINGER_MODE_CHANGED = 16; - // Pseudo stream type for master volume - private static final int STREAM_MASTER = -100; // Pseudo stream type for remote volume private static final int STREAM_REMOTE_MUSIC = -200; @@ -155,10 +152,6 @@ public class VolumePanel extends Handler implements DemoMode { private int mLastRingerProgress = 0; private int mDemoIcon; - // True if we want to play tones on the system stream when the master stream is specified. - private final boolean mPlayMasterStreamTones; - - /** Volume panel content view */ private final View mView; /** Dialog hosting the panel */ @@ -214,12 +207,6 @@ public class VolumePanel extends Handler implements DemoMode { com.android.systemui.R.drawable.ic_ringer_audible, com.android.systemui.R.drawable.ic_ringer_mute, true), - // for now, use media resources for master volume - MasterStream(STREAM_MASTER, - R.string.volume_icon_description_media, //FIXME should have its own description - IC_AUDIO_VOL, - IC_AUDIO_VOL_MUTE, - false), RemoteStream(STREAM_REMOTE_MUSIC, R.string.volume_icon_description_media, //FIXME should have its own description com.android.systemui.R.drawable.ic_audio_remote, @@ -250,7 +237,6 @@ public class VolumePanel extends Handler implements DemoMode { StreamResources.MediaStream, StreamResources.NotificationStream, StreamResources.AlarmStream, - StreamResources.MasterStream, StreamResources.RemoteStream }; @@ -267,6 +253,7 @@ public class VolumePanel extends Handler implements DemoMode { int iconRes; int iconMuteRes; int iconSuppressedRes; + int minVolume; } // Synchronize when accessing this @@ -351,6 +338,17 @@ public class VolumePanel extends Handler implements DemoMode { }; } + protected LayoutParams getDialogLayoutParams(Window window, Resources res) { + final LayoutParams lp = window.getAttributes(); + lp.token = null; + lp.y = res.getDimensionPixelOffset(com.android.systemui.R.dimen.volume_panel_top); + lp.type = LayoutParams.TYPE_STATUS_BAR_PANEL; + lp.format = PixelFormat.TRANSLUCENT; + lp.windowAnimations = com.android.systemui.R.style.VolumePanelAnimation; + lp.setTitle(TAG); + return lp; + } + public VolumePanel(Context context, ZenModeController zenController) { mTag = String.format("%s.%08x", TAG, hashCode()); mContext = context; @@ -361,15 +359,6 @@ public class VolumePanel extends Handler implements DemoMode { mSecondaryIconTransition = new SecondaryIconTransition(); mIconPulser = new IconPulser(context); - // For now, only show master volume if master volume is supported - final Resources res = context.getResources(); - final boolean useMasterVolume = res.getBoolean(R.bool.config_useMasterVolume); - if (useMasterVolume) { - for (int i = 0; i < STREAMS.length; i++) { - StreamResources streamRes = STREAMS[i]; - streamRes.show = (streamRes.streamType == STREAM_MASTER); - } - } if (LOGD) Log.d(mTag, "new VolumePanel"); mDisabledAlpha = 0.5f; @@ -395,7 +384,7 @@ public class VolumePanel extends Handler implements DemoMode { final Window window = mDialog.getWindow(); window.requestFeature(Window.FEATURE_NO_TITLE); mDialog.setCanceledOnTouchOutside(true); - mDialog.setContentView(com.android.systemui.R.layout.volume_dialog); + mDialog.setContentView(com.android.systemui.R.layout.volume_panel_dialog); mDialog.setOnDismissListener(new OnDismissListener() { @Override public void onDismiss(DialogInterface dialog) { @@ -409,14 +398,8 @@ public class VolumePanel extends Handler implements DemoMode { mDialog.create(); - final LayoutParams lp = window.getAttributes(); - lp.token = null; - lp.y = res.getDimensionPixelOffset(com.android.systemui.R.dimen.volume_panel_top); - lp.type = LayoutParams.TYPE_STATUS_BAR_PANEL; - lp.format = PixelFormat.TRANSLUCENT; - lp.windowAnimations = com.android.systemui.R.style.VolumePanelAnimation; - lp.setTitle(TAG); - window.setAttributes(lp); + final Resources res = context.getResources(); + window.setAttributes(getDialogLayoutParams(window, res)); updateWidth(); @@ -444,16 +427,12 @@ public class VolumePanel extends Handler implements DemoMode { mHasVibrator = mVibrator != null && mVibrator.hasVibrator(); mVoiceCapable = context.getResources().getBoolean(R.bool.config_voice_capable); - if (mZenController != null && !useMasterVolume) { + if (mZenController != null) { mZenModeAvailable = mZenController.isZenAvailable(); mNotificationEffectsSuppressor = mZenController.getEffectsSuppressor(); mZenController.addCallback(mZenCallback); } - final boolean masterVolumeOnly = res.getBoolean(R.bool.config_useMasterVolume); - final boolean masterVolumeKeySounds = res.getBoolean(R.bool.config_useVolumeKeySounds); - mPlayMasterStreamTones = masterVolumeOnly && masterVolumeKeySounds; - registerReceiver(); } @@ -486,7 +465,6 @@ public class VolumePanel extends Handler implements DemoMode { pw.print(" mDisabledAlpha="); pw.println(mDisabledAlpha); pw.print(" mLastRingerMode="); pw.println(mLastRingerMode); pw.print(" mLastRingerProgress="); pw.println(mLastRingerProgress); - pw.print(" mPlayMasterStreamTones="); pw.println(mPlayMasterStreamTones); pw.print(" isShowing()="); pw.println(isShowing()); pw.print(" mCallback="); pw.println(mCallback); pw.print(" sConfirmSafeVolumeDialog="); @@ -573,9 +551,7 @@ public class VolumePanel extends Handler implements DemoMode { } private boolean isMuted(int streamType) { - if (streamType == STREAM_MASTER) { - return mAudioManager.isMasterMute(); - } else if (streamType == STREAM_REMOTE_MUSIC) { + if (streamType == STREAM_REMOTE_MUSIC) { // TODO do we need to support a distinct mute property for remote? return false; } else { @@ -583,10 +559,16 @@ public class VolumePanel extends Handler implements DemoMode { } } + private int getStreamMinVolume(int streamType) { + if (streamType == STREAM_REMOTE_MUSIC) { + return 0; + } else { + return mAudioManager.getStreamMinVolume(streamType); + } + } + private int getStreamMaxVolume(int streamType) { - if (streamType == STREAM_MASTER) { - return mAudioManager.getMasterMaxVolume(); - } else if (streamType == STREAM_REMOTE_MUSIC) { + if (streamType == STREAM_REMOTE_MUSIC) { if (mStreamControls != null) { StreamControl sc = mStreamControls.get(streamType); if (sc != null && sc.controller != null) { @@ -601,9 +583,7 @@ public class VolumePanel extends Handler implements DemoMode { } private int getStreamVolume(int streamType) { - if (streamType == STREAM_MASTER) { - return mAudioManager.getMasterVolume(); - } else if (streamType == STREAM_REMOTE_MUSIC) { + if (streamType == STREAM_REMOTE_MUSIC) { if (mStreamControls != null) { StreamControl sc = mStreamControls.get(streamType); if (sc != null && sc.controller != null) { @@ -613,7 +593,7 @@ public class VolumePanel extends Handler implements DemoMode { } return -1; } else { - return mAudioManager.getStreamVolume(streamType); + return mAudioManager.getLastAudibleStreamVolume(streamType); } } @@ -625,11 +605,7 @@ public class VolumePanel extends Handler implements DemoMode { Log.w(mTag, "Adjusting remote volume without a controller!"); } } else if (getStreamVolume(sc.streamType) != index) { - if (sc.streamType == STREAM_MASTER) { - mAudioManager.setMasterVolume(index, flags); - } else { - mAudioManager.setStreamVolume(sc.streamType, index, flags); - } + mAudioManager.setStreamVolume(sc.streamType, index, flags); } } @@ -694,9 +670,8 @@ public class VolumePanel extends Handler implements DemoMode { } }); } - final int plusOne = (streamType == AudioSystem.STREAM_BLUETOOTH_SCO || - streamType == AudioSystem.STREAM_VOICE_CALL) ? 1 : 0; - sc.seekbarView.setMax(getStreamMaxVolume(streamType) + plusOne); + sc.minVolume = getStreamMinVolume(streamType); + sc.seekbarView.setMax(getStreamMaxVolume(streamType) - sc.minVolume); sc.seekbarView.setOnSeekBarChangeListener(mSeekListener); sc.seekbarView.setTag(sc); mStreamControls.put(streamType, sc); @@ -739,7 +714,7 @@ public class VolumePanel extends Handler implements DemoMode { if (progress < 0) { progress = getStreamVolume(sc.streamType); } - sc.seekbarView.setProgress(progress); + sc.seekbarView.setProgress(progress - sc.minVolume); if (isRinger) { mLastRingerProgress = progress; } @@ -830,7 +805,7 @@ public class VolumePanel extends Handler implements DemoMode { sc.icon.setAlpha(mDisabledAlpha); sc.icon.setClickable(false); } else if (fixedVolume || - (sc.streamType != mAudioManager.getMasterStreamType() && !isRinger && muted) || + (sc.streamType != mAudioManager.getUiSoundsStreamType() && !isRinger && muted) || (sSafetyWarning != null)) { sc.seekbarView.setEnabled(false); } else { @@ -974,10 +949,6 @@ public class VolumePanel extends Handler implements DemoMode { obtainMessage(MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN).sendToTarget(); } - public void postMasterVolumeChanged(int flags) { - postVolumeChanged(STREAM_MASTER, flags); - } - public void postMuteChanged(int streamType, int flags) { if (hasMessages(MSG_VOLUME_CHANGED)) return; synchronized (this) { @@ -989,10 +960,6 @@ public class VolumePanel extends Handler implements DemoMode { obtainMessage(MSG_MUTE_CHANGED, streamType, flags).sendToTarget(); } - public void postMasterMuteChanged(int flags) { - postMuteChanged(STREAM_MASTER, flags); - } - public void postDisplaySafeVolumeWarning(int flags) { if (hasMessages(MSG_DISPLAY_SAFE_VOLUME_WARNING)) return; obtainMessage(MSG_DISPLAY_SAFE_VOLUME_WARNING, flags, 0).sendToTarget(); @@ -1012,7 +979,7 @@ public class VolumePanel extends Handler implements DemoMode { } private static String streamToString(int stream) { - return AudioService.streamToString(stream); + return AudioSystem.streamToString(stream); } /** @@ -1075,7 +1042,7 @@ public class VolumePanel extends Handler implements DemoMode { // get max volume for progress bar - int max = getStreamMaxVolume(streamType); + int max = getStreamMaxVolume(streamType) - getStreamMinVolume(streamType); StreamControl sc = mStreamControls.get(streamType); switch (streamType) { @@ -1102,17 +1069,6 @@ public class VolumePanel extends Handler implements DemoMode { break; } - case AudioManager.STREAM_VOICE_CALL: { - /* - * For in-call voice call volume, there is no inaudible volume. - * Rescale the UI control so the progress bar doesn't go all - * the way to zero and don't show the mute icon. - */ - index++; - max++; - break; - } - case AudioManager.STREAM_ALARM: { break; } @@ -1126,17 +1082,6 @@ public class VolumePanel extends Handler implements DemoMode { break; } - case AudioManager.STREAM_BLUETOOTH_SCO: { - /* - * For in-call voice call volume, there is no inaudible volume. - * Rescale the UI control so the progress bar doesn't go all - * the way to zero and don't show the mute icon. - */ - index++; - max++; - break; - } - case STREAM_REMOTE_MUSIC: { if (controller == null && sc != null) { // If we weren't passed one try using the last one set. @@ -1189,9 +1134,7 @@ public class VolumePanel extends Handler implements DemoMode { if (!isShowing()) { int stream = (streamType == STREAM_REMOTE_MUSIC) ? -1 : streamType; // when the stream is for remote playback, use -1 to reset the stream type evaluation - if (stream != STREAM_MASTER) { - mAudioManager.forceVolumeControlStream(stream); - } + mAudioManager.forceVolumeControlStream(stream); mDialog.show(); if (mCallback != null) { mCallback.onVisible(true); @@ -1357,16 +1300,6 @@ public class VolumePanel extends Handler implements DemoMode { * Lock on this VolumePanel instance as long as you use the returned ToneGenerator. */ private ToneGenerator getOrCreateToneGenerator(int streamType) { - if (streamType == STREAM_MASTER) { - // For devices that use the master volume setting only but still want to - // play a volume-changed tone, direct the master volume pseudostream to - // the system stream's tone generator. - if (mPlayMasterStreamTones) { - streamType = AudioManager.STREAM_SYSTEM; - } else { - return null; - } - } synchronized (this) { if (mToneGenerators[streamType] == null) { try { @@ -1546,7 +1479,7 @@ public class VolumePanel extends Handler implements DemoMode { final Object tag = seekBar.getTag(); if (fromUser && tag instanceof StreamControl) { StreamControl sc = (StreamControl) tag; - setStreamVolume(sc, progress, + setStreamVolume(sc, progress + sc.minVolume, AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE); } resetTimeout(); diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanelComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanelComponent.java new file mode 100644 index 0000000..fa6ea9e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanelComponent.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume; + +import android.content.Context; +import android.content.res.Configuration; +import android.media.AudioManager; +import android.media.IRemoteVolumeController; +import android.media.IVolumeController; +import android.media.VolumePolicy; +import android.media.session.ISessionController; +import android.media.session.MediaController; +import android.media.session.MediaSessionManager; +import android.os.Bundle; +import android.os.Handler; +import android.os.RemoteException; + +import com.android.systemui.R; +import com.android.systemui.SystemUI; +import com.android.systemui.keyguard.KeyguardViewMediator; +import com.android.systemui.qs.tiles.DndTile; +import com.android.systemui.statusbar.phone.PhoneStatusBar; +import com.android.systemui.statusbar.policy.ZenModeController; + +import java.io.FileDescriptor; +import java.io.PrintWriter; + +/** + * Implementation of VolumeComponent backed by the old volume panel. + */ +public class VolumePanelComponent implements VolumeComponent { + + private final SystemUI mSysui; + private final Context mContext; + private final Handler mHandler; + private final VolumeController mVolumeController; + private final RemoteVolumeController mRemoteVolumeController; + private final AudioManager mAudioManager; + private final MediaSessionManager mMediaSessionManager; + + private VolumePanel mPanel; + private int mDismissDelay; + + public VolumePanelComponent(SystemUI sysui, Context context, Handler handler, + ZenModeController controller) { + mSysui = sysui; + mContext = context; + mHandler = handler; + mAudioManager = context.getSystemService(AudioManager.class); + mMediaSessionManager = context.getSystemService(MediaSessionManager.class); + mVolumeController = new VolumeController(); + mRemoteVolumeController = new RemoteVolumeController(); + mDismissDelay = mContext.getResources().getInteger(R.integer.volume_panel_dismiss_delay); + mPanel = new VolumePanel(mContext, controller); + mPanel.setCallback(new VolumePanel.Callback() { + @Override + public void onZenSettings() { + mHandler.removeCallbacks(mStartZenSettings); + mHandler.post(mStartZenSettings); + } + + @Override + public void onInteraction() { + final KeyguardViewMediator kvm = mSysui.getComponent(KeyguardViewMediator.class); + if (kvm != null) { + kvm.userActivity(); + } + } + + @Override + public void onVisible(boolean visible) { + if (mAudioManager != null && mVolumeController != null) { + mAudioManager.notifyVolumeControllerVisible(mVolumeController, visible); + } + } + }); + } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (mPanel != null) { + mPanel.dump(fd, pw, args); + } + } + + public void register() { + mAudioManager.setVolumeController(mVolumeController); + mAudioManager.setVolumePolicy(VolumePolicy.DEFAULT); + mMediaSessionManager.setRemoteVolumeController(mRemoteVolumeController); + DndTile.setVisible(mContext, false); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + if (mPanel != null) { + mPanel.onConfigurationChanged(newConfig); + } + } + + @Override + public ZenModeController getZenController() { + return mPanel.getZenController(); + } + + @Override + public void dispatchDemoCommand(String command, Bundle args) { + mPanel.dispatchDemoCommand(command, args); + } + + @Override + public void dismissNow() { + mPanel.postDismiss(0); + } + + private final Runnable mStartZenSettings = new Runnable() { + @Override + public void run() { + mSysui.getComponent(PhoneStatusBar.class).startActivityDismissingKeyguard( + ZenModePanel.ZEN_SETTINGS, true /* onlyProvisioned */, true /* dismissShade */); + mPanel.postDismiss(mDismissDelay); + } + }; + + private final class RemoteVolumeController extends IRemoteVolumeController.Stub { + @Override + public void remoteVolumeChanged(ISessionController binder, int flags) + throws RemoteException { + MediaController controller = new MediaController(mContext, binder); + mPanel.postRemoteVolumeChanged(controller, flags); + } + + @Override + public void updateRemoteController(ISessionController session) throws RemoteException { + mPanel.postRemoteSliderVisibility(session != null); + // TODO stash default session in case the slider can be opened other + // than by remoteVolumeChanged. + } + } + + /** For now, simply host an unmodified base volume panel in this process. */ + private final class VolumeController extends IVolumeController.Stub { + + @Override + public void displaySafeVolumeWarning(int flags) throws RemoteException { + mPanel.postDisplaySafeVolumeWarning(flags); + } + + @Override + public void volumeChanged(int streamType, int flags) + throws RemoteException { + mPanel.postVolumeChanged(streamType, flags); + } + + @Override + public void masterMuteChanged(int flags) throws RemoteException { + // no-op + } + + @Override + public void setLayoutDirection(int layoutDirection) + throws RemoteException { + mPanel.postLayoutDirection(layoutDirection); + } + + @Override + public void dismiss() throws RemoteException { + dismissNow(); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java index 7102c2a..387aed0 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java @@ -1,216 +1,231 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.systemui.volume; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; import android.content.res.Configuration; -import android.database.ContentObserver; import android.media.AudioManager; -import android.media.IRemoteVolumeController; -import android.media.IVolumeController; -import android.media.session.ISessionController; -import android.media.session.MediaController; import android.media.session.MediaSessionManager; -import android.net.Uri; -import android.os.Bundle; import android.os.Handler; -import android.os.RemoteException; +import android.os.SystemProperties; import android.provider.Settings; +import android.text.TextUtils; import android.util.Log; import com.android.systemui.R; import com.android.systemui.SystemUI; -import com.android.systemui.keyguard.KeyguardViewMediator; -import com.android.systemui.statusbar.phone.PhoneStatusBar; +import com.android.systemui.qs.tiles.DndTile; +import com.android.systemui.statusbar.ServiceMonitor; +import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.statusbar.policy.ZenModeControllerImpl; import java.io.FileDescriptor; import java.io.PrintWriter; -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - public class VolumeUI extends SystemUI { private static final String TAG = "VolumeUI"; - private static final String SETTING = "systemui_volume_controller"; // for testing - private static final Uri SETTING_URI = Settings.Global.getUriFor(SETTING); - private static final int DEFAULT = 1; // enabled by default + private static boolean LOGD = Log.isLoggable(TAG, Log.DEBUG); + + private static final boolean USE_OLD_VOLUME = SystemProperties.getBoolean("volume.old", false); private final Handler mHandler = new Handler(); + private final Receiver mReceiver = new Receiver(); + private final RestorationNotification mRestorationNotification = new RestorationNotification(); private boolean mEnabled; private AudioManager mAudioManager; + private NotificationManager mNotificationManager; private MediaSessionManager mMediaSessionManager; - private VolumeController mVolumeController; - private RemoteVolumeController mRemoteVolumeController; + private ServiceMonitor mVolumeControllerService; - private VolumePanel mPanel; - private int mDismissDelay; + private VolumePanelComponent mOldVolume; + private VolumeDialogComponent mNewVolume; @Override public void start() { mEnabled = mContext.getResources().getBoolean(R.bool.enable_volume_ui); if (!mEnabled) return; mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + mNotificationManager = + (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); mMediaSessionManager = (MediaSessionManager) mContext .getSystemService(Context.MEDIA_SESSION_SERVICE); - initPanel(); - mVolumeController = new VolumeController(); - mRemoteVolumeController = new RemoteVolumeController(); - putComponent(VolumeComponent.class, mVolumeController); - updateController(); - mContext.getContentResolver().registerContentObserver(SETTING_URI, false, mObserver); + final ZenModeController zenController = new ZenModeControllerImpl(mContext, mHandler); + mOldVolume = new VolumePanelComponent(this, mContext, mHandler, zenController); + mNewVolume = new VolumeDialogComponent(this, mContext, null, zenController); + putComponent(VolumeComponent.class, getVolumeComponent()); + mReceiver.start(); + mVolumeControllerService = new ServiceMonitor(TAG, LOGD, + mContext, Settings.Secure.VOLUME_CONTROLLER_SERVICE_COMPONENT, + new ServiceMonitorCallbacks()); + mVolumeControllerService.start(); + } + + private VolumeComponent getVolumeComponent() { + return USE_OLD_VOLUME ? mOldVolume : mNewVolume; } @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - if (mPanel != null) { - mPanel.onConfigurationChanged(newConfig); - } + if (!mEnabled) return; + getVolumeComponent().onConfigurationChanged(newConfig); } @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.print("mEnabled="); pw.println(mEnabled); - if (mPanel != null) { - mPanel.dump(fd, pw, args); - } + if (!mEnabled) return; + pw.print("mVolumeControllerService="); pw.println(mVolumeControllerService.getComponent()); + getVolumeComponent().dump(fd, pw, args); } - private void updateController() { - if (Settings.Global.getInt(mContext.getContentResolver(), SETTING, DEFAULT) != 0) { - Log.d(TAG, "Registering volume controller"); - mAudioManager.setVolumeController(mVolumeController); - mMediaSessionManager.setRemoteVolumeController(mRemoteVolumeController); + private void setDefaultVolumeController(boolean register) { + if (register) { + DndTile.setVisible(mContext, false); + if (LOGD) Log.d(TAG, "Registering default volume controller"); + getVolumeComponent().register(); } else { - Log.d(TAG, "Unregistering volume controller"); + if (LOGD) Log.d(TAG, "Unregistering default volume controller"); mAudioManager.setVolumeController(null); mMediaSessionManager.setRemoteVolumeController(null); } } - private void initPanel() { - mDismissDelay = mContext.getResources().getInteger(R.integer.volume_panel_dismiss_delay); - mPanel = new VolumePanel(mContext, new ZenModeControllerImpl(mContext, mHandler)); - mPanel.setCallback(new VolumePanel.Callback() { - @Override - public void onZenSettings() { - mHandler.removeCallbacks(mStartZenSettings); - mHandler.post(mStartZenSettings); - } - - @Override - public void onInteraction() { - final KeyguardViewMediator kvm = getComponent(KeyguardViewMediator.class); - if (kvm != null) { - kvm.userActivity(); - } + private String getAppLabel(ComponentName component) { + final String pkg = component.getPackageName(); + try { + final ApplicationInfo ai = mContext.getPackageManager().getApplicationInfo(pkg, 0); + final String rt = mContext.getPackageManager().getApplicationLabel(ai).toString(); + if (!TextUtils.isEmpty(rt)) { + return rt; } + } catch (Exception e) { + Log.w(TAG, "Error loading app label", e); + } + return pkg; + } + private void showServiceActivationDialog(final ComponentName component) { + final SystemUIDialog d = new SystemUIDialog(mContext); + d.setMessage(mContext.getString(R.string.volumeui_prompt_message, getAppLabel(component))); + d.setPositiveButton(R.string.volumeui_prompt_allow, new OnClickListener() { @Override - public void onVisible(boolean visible) { - if (mAudioManager != null && mVolumeController != null) { - mAudioManager.notifyVolumeControllerVisible(mVolumeController, visible); - } + public void onClick(DialogInterface dialog, int which) { + mVolumeControllerService.setComponent(component); } }); + d.setNegativeButton(R.string.volumeui_prompt_deny, null); + d.show(); } - private final ContentObserver mObserver = new ContentObserver(mHandler) { - public void onChange(boolean selfChange, Uri uri) { - if (SETTING_URI.equals(uri)) { - updateController(); - } - } - }; - - private final Runnable mStartZenSettings = new Runnable() { - @Override - public void run() { - getComponent(PhoneStatusBar.class).startActivityDismissingKeyguard( - ZenModePanel.ZEN_SETTINGS, true /* onlyProvisioned */, true /* dismissShade */); - mPanel.postDismiss(mDismissDelay); - } - }; - - /** For now, simply host an unmodified base volume panel in this process. */ - private final class VolumeController extends IVolumeController.Stub implements VolumeComponent { - - @Override - public void displaySafeVolumeWarning(int flags) throws RemoteException { - mPanel.postDisplaySafeVolumeWarning(flags); - } - - @Override - public void volumeChanged(int streamType, int flags) - throws RemoteException { - mPanel.postVolumeChanged(streamType, flags); - } - + private final class ServiceMonitorCallbacks implements ServiceMonitor.Callbacks { @Override - public void masterVolumeChanged(int flags) throws RemoteException { - mPanel.postMasterVolumeChanged(flags); - } - - @Override - public void masterMuteChanged(int flags) throws RemoteException { - mPanel.postMasterMuteChanged(flags); + public void onNoService() { + if (LOGD) Log.d(TAG, "onNoService"); + setDefaultVolumeController(true); + mRestorationNotification.hide(); + if (!mVolumeControllerService.isPackageAvailable()) { + mVolumeControllerService.setComponent(null); + } } @Override - public void setLayoutDirection(int layoutDirection) - throws RemoteException { - mPanel.postLayoutDirection(layoutDirection); + public long onServiceStartAttempt() { + if (LOGD) Log.d(TAG, "onServiceStartAttempt"); + // poke the setting to update the uid + mVolumeControllerService.setComponent(mVolumeControllerService.getComponent()); + setDefaultVolumeController(false); + getVolumeComponent().dismissNow(); + mRestorationNotification.show(); + return 0; } + } - @Override - public void dismiss() throws RemoteException { - dismissNow(); - } + private final class Receiver extends BroadcastReceiver { + private static final String ENABLE = "com.android.systemui.vui.ENABLE"; + private static final String DISABLE = "com.android.systemui.vui.DISABLE"; + private static final String EXTRA_COMPONENT = "component"; - @Override - public ZenModeController getZenController() { - return mPanel.getZenController(); + public void start() { + final IntentFilter filter = new IntentFilter(); + filter.addAction(ENABLE); + filter.addAction(DISABLE); + mContext.registerReceiver(this, filter, null, mHandler); } @Override - public void dispatchDemoCommand(String command, Bundle args) { - mPanel.dispatchDemoCommand(command, args); - } - - @Override - public void dismissNow() { - mPanel.postDismiss(0); + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + final ComponentName component = intent.getParcelableExtra(EXTRA_COMPONENT); + final boolean current = component.equals(mVolumeControllerService.getComponent()); + if (ENABLE.equals(action) && component != null) { + if (!current) { + showServiceActivationDialog(component); + } + } + if (DISABLE.equals(action) && component != null) { + if (current) { + mVolumeControllerService.setComponent(null); + } + } } } - private final class RemoteVolumeController extends IRemoteVolumeController.Stub { - - @Override - public void remoteVolumeChanged(ISessionController binder, int flags) - throws RemoteException { - MediaController controller = new MediaController(mContext, binder); - mPanel.postRemoteVolumeChanged(controller, flags); + private final class RestorationNotification { + public void hide() { + mNotificationManager.cancel(R.id.notification_volumeui); } - @Override - public void updateRemoteController(ISessionController session) throws RemoteException { - mPanel.postRemoteSliderVisibility(session != null); - // TODO stash default session in case the slider can be opened other - // than by remoteVolumeChanged. + public void show() { + final ComponentName component = mVolumeControllerService.getComponent(); + if (component == null) { + Log.w(TAG, "Not showing restoration notification, component not active"); + return; + } + final Intent intent = new Intent(Receiver.DISABLE) + .putExtra(Receiver.EXTRA_COMPONENT, component); + mNotificationManager.notify(R.id.notification_volumeui, + new Notification.Builder(mContext) + .setSmallIcon(R.drawable.ic_ringer_audible) + .setWhen(0) + .setShowWhen(false) + .setOngoing(true) + .setContentTitle(mContext.getString( + R.string.volumeui_notification_title, getAppLabel(component))) + .setContentText(mContext.getString(R.string.volumeui_notification_text)) + .setContentIntent(PendingIntent.getBroadcast(mContext, 0, intent, + PendingIntent.FLAG_UPDATE_CURRENT)) + .setPriority(Notification.PRIORITY_MIN) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .setColor(mContext.getColor( + com.android.internal.R.color.system_notification_accent_color)) + .build()); } } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java b/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java new file mode 100644 index 0000000..f99eb6d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.volume; + +import android.animation.LayoutTransition; +import android.animation.ValueAnimator; +import android.content.Context; +import android.content.res.Resources; +import android.provider.Settings.Global; +import android.service.notification.Condition; +import android.util.AttributeSet; +import android.util.Log; +import android.util.TypedValue; +import android.view.View; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.LinearLayout; +import android.widget.Switch; +import android.widget.TextView; + +import com.android.systemui.R; +import com.android.systemui.statusbar.policy.ZenModeController; + +/** + * Switch bar + zen mode panel (conditions) attached to the bottom of the volume dialog. + */ +public class ZenFooter extends LinearLayout { + private static final String TAG = Util.logTag(ZenFooter.class); + + private final Context mContext; + private final float mSecondaryAlpha; + private final LayoutTransition mLayoutTransition; + + private ZenModeController mController; + private Switch mSwitch; + private ZenModePanel mZenModePanel; + private View mZenModePanelButtons; + private View mZenModePanelMoreButton; + private View mZenModePanelDoneButton; + private View mSwitchBar; + private View mSwitchBarIcon; + private View mSummary; + private TextView mSummaryLine1; + private TextView mSummaryLine2; + private boolean mFooterExpanded; + private int mZen = -1; + private Callback mCallback; + + public ZenFooter(Context context, AttributeSet attrs) { + super(context, attrs); + mContext = context; + mSecondaryAlpha = getFloat(context.getResources(), R.dimen.volume_secondary_alpha); + mLayoutTransition = new LayoutTransition(); + mLayoutTransition.setDuration(new ValueAnimator().getDuration() / 2); + mLayoutTransition.disableTransitionType(LayoutTransition.DISAPPEARING); + mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING); + } + + private static float getFloat(Resources r, int resId) { + final TypedValue tv = new TypedValue(); + r.getValue(resId, tv, true); + return tv.getFloat(); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mSwitchBar = findViewById(R.id.volume_zen_switch_bar); + mSwitchBarIcon = findViewById(R.id.volume_zen_switch_bar_icon); + mSwitch = (Switch) findViewById(R.id.volume_zen_switch); + mZenModePanel = (ZenModePanel) findViewById(R.id.zen_mode_panel); + mZenModePanelButtons = findViewById(R.id.volume_zen_mode_panel_buttons); + mZenModePanelMoreButton = findViewById(R.id.volume_zen_mode_panel_more); + mZenModePanelDoneButton = findViewById(R.id.volume_zen_mode_panel_done); + mSummary = findViewById(R.id.volume_zen_panel_summary); + mSummaryLine1 = (TextView) findViewById(R.id.volume_zen_panel_summary_line_1); + mSummaryLine2 = (TextView) findViewById(R.id.volume_zen_panel_summary_line_2); + } + + public void init(ZenModeController controller, Callback callback) { + mCallback = callback; + mController = controller; + mZenModePanel.init(controller); + mZenModePanel.setEmbedded(true); + mSwitch.setOnCheckedChangeListener(mCheckedListener); + mController.addCallback(new ZenModeController.Callback() { + @Override + public void onZenChanged(int zen) { + setZen(zen); + } + @Override + public void onExitConditionChanged(Condition exitCondition) { + update(); + } + }); + mSwitchBar.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + mSwitch.setChecked(!mSwitch.isChecked()); + } + }); + mZenModePanelMoreButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (mCallback != null) { + mCallback.onSettingsClicked(); + } + } + }); + mZenModePanelDoneButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (mCallback != null) { + mCallback.onDoneClicked(); + } + } + }); + mZen = mController.getZen(); + update(); + } + + private void setZen(int zen) { + if (mZen == zen) return; + mZen = zen; + update(); + } + + public boolean isZen() { + return isZenPriority() || isZenAlarms() || isZenNone(); + } + + private boolean isZenPriority() { + return mZen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + } + + private boolean isZenAlarms() { + return mZen == Global.ZEN_MODE_ALARMS; + } + + private boolean isZenNone() { + return mZen == Global.ZEN_MODE_NO_INTERRUPTIONS; + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + setLayoutTransition(null); + setFooterExpanded(false); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + setLayoutTransition(mLayoutTransition); + } + + private boolean setFooterExpanded(boolean expanded) { + if (mFooterExpanded == expanded) return false; + mFooterExpanded = expanded; + update(); + if (mCallback != null) { + mCallback.onFooterExpanded(); + } + return true; + } + + public boolean isFooterExpanded() { + return mFooterExpanded; + } + + public void update() { + final boolean isZen = isZen(); + mSwitch.setOnCheckedChangeListener(null); + mSwitch.setChecked(isZen); + mSwitch.setOnCheckedChangeListener(mCheckedListener); + Util.setVisOrGone(mZenModePanel, isZen && mFooterExpanded); + Util.setVisOrGone(mZenModePanelButtons, isZen && mFooterExpanded); + Util.setVisOrGone(mSummary, isZen && !mFooterExpanded); + mSwitchBarIcon.setAlpha(isZen ? 1 : mSecondaryAlpha); + final String line1 = + isZenPriority() ? mContext.getString(R.string.interruption_level_priority) + : isZenAlarms() ? mContext.getString(R.string.interruption_level_alarms) + : isZenNone() ? mContext.getString(R.string.interruption_level_none) + : null; + Util.setText(mSummaryLine1, line1); + Util.setText(mSummaryLine2, mZenModePanel.getExitConditionText()); + } + + private final OnCheckedChangeListener mCheckedListener = new OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (D.BUG) Log.d(TAG, "onCheckedChanged " + isChecked); + if (isChecked != isZen()) { + final int newZen = isChecked ? Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS + : Global.ZEN_MODE_OFF; + mZen = newZen; // this one's optimistic + setFooterExpanded(isChecked); + mController.setZen(newZen); + } + } + }; + + public interface Callback { + void onFooterExpanded(); + void onSettingsClicked(); + void onDoneClicked(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java index d40a2c0..cb6c29f 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java +++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java @@ -19,12 +19,10 @@ package com.android.systemui.volume; import android.animation.LayoutTransition; import android.animation.LayoutTransition.TransitionListener; import android.app.ActivityManager; -import android.app.NotificationManager; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; -import android.content.res.Resources; import android.net.Uri; import android.os.AsyncTask; import android.os.Handler; @@ -85,24 +83,26 @@ public class ZenModePanel extends LinearLayout { private final int mSubheadWarningColor; private final int mSubheadColor; private final Interpolator mInterpolator; - private final int mMaxConditions; - private final int mMaxOptionalConditions; - private final boolean mCountdownConditionSupported; - private final int mFirstConditionIndex; private final TransitionHelper mTransitionHelper = new TransitionHelper(); private final Uri mForeverId; private String mTag = TAG + "/" + Integer.toHexString(System.identityHashCode(this)); private SegmentedButtons mZenButtons; + private ViewGroup mZenButtonsContainer; private View mZenSubhead; private TextView mZenSubheadCollapsed; private TextView mZenSubheadExpanded; + private View mZenEmbeddedDivider; private View mMoreSettings; private LinearLayout mZenConditions; private Callback mCallback; private ZenModeController mController; + private boolean mCountdownConditionSupported; + private int mMaxConditions; + private int mMaxOptionalConditions; + private int mFirstConditionIndex; private boolean mRequestingConditions; private Condition mExitCondition; private String mExitConditionText; @@ -115,6 +115,7 @@ public class ZenModePanel extends LinearLayout { private Condition mSessionExitCondition; private Condition[] mConditions; private Condition mTimeCondition; + private boolean mEmbedded; public ZenModePanel(Context context, AttributeSet attrs) { super(context, attrs); @@ -122,19 +123,10 @@ public class ZenModePanel extends LinearLayout { mPrefs = new Prefs(); mInflater = LayoutInflater.from(mContext.getApplicationContext()); mIconPulser = new IconPulser(mContext); - final Resources res = mContext.getResources(); - mSubheadWarningColor = res.getColor(R.color.system_warning_color); - mSubheadColor = res.getColor(R.color.qs_subhead); + mSubheadWarningColor = context.getColor(R.color.system_warning_color); + mSubheadColor = context.getColor(R.color.qs_subhead); mInterpolator = AnimationUtils.loadInterpolator(mContext, com.android.internal.R.interpolator.fast_out_slow_in); - mCountdownConditionSupported = NotificationManager.from(mContext) - .isSystemConditionProviderEnabled(ZenModeConfig.COUNTDOWN_PATH); - final int countdownDelta = mCountdownConditionSupported ? 1 : 0; - mFirstConditionIndex = COUNTDOWN_CONDITION_INDEX + countdownDelta; - final int minConditions = 1 /*forever*/ + countdownDelta; - mMaxConditions = MathUtils.constrain(res.getInteger(R.integer.zen_mode_max_conditions), - minConditions, 100); - mMaxOptionalConditions = mMaxConditions - minConditions; mForeverId = Condition.newId(mContext).appendPath("forever").build(); if (DEBUG) Log.d(mTag, "new ZenModePanel"); } @@ -149,26 +141,45 @@ public class ZenModePanel extends LinearLayout { pw.print(" mExpanded="); pw.println(mExpanded); pw.print(" mSessionZen="); pw.println(mSessionZen); pw.print(" mAttachedZen="); pw.println(mAttachedZen); + pw.print(" mEmbedded="); pw.println(mEmbedded); mTransitionHelper.dump(fd, pw, args); } + public void setEmbedded(boolean embedded) { + if (mEmbedded == embedded) return; + mEmbedded = embedded; + mZenButtonsContainer.setLayoutTransition(mEmbedded ? null : newLayoutTransition(null)); + setLayoutTransition(mEmbedded ? null : newLayoutTransition(null)); + if (mEmbedded) { + mZenButtonsContainer.setBackground(null); + } else { + mZenButtonsContainer.setBackgroundResource(R.drawable.qs_background_secondary); + } + mZenButtons.getChildAt(3).setVisibility(mEmbedded ? GONE : VISIBLE); + mZenEmbeddedDivider.setVisibility(mEmbedded ? VISIBLE : GONE); + setExpanded(mEmbedded); + updateWidgets(); + } + @Override protected void onFinishInflate() { super.onFinishInflate(); mZenButtons = (SegmentedButtons) findViewById(R.id.zen_buttons); - mZenButtons.addButton(R.string.interruption_level_none, R.drawable.ic_zen_none, + mZenButtons.addButton(R.string.interruption_level_none_twoline, Global.ZEN_MODE_NO_INTERRUPTIONS); - mZenButtons.addButton(R.string.interruption_level_priority, R.drawable.ic_zen_important, + mZenButtons.addButton(R.string.interruption_level_alarms_twoline, + Global.ZEN_MODE_ALARMS); + mZenButtons.addButton(R.string.interruption_level_priority_twoline, Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS); - mZenButtons.addButton(R.string.interruption_level_all, R.drawable.ic_zen_all, - Global.ZEN_MODE_OFF); + mZenButtons.addButton(R.string.interruption_level_all, Global.ZEN_MODE_OFF); mZenButtons.setCallback(mZenButtonsCallback); - final ViewGroup zenButtonsContainer = (ViewGroup) findViewById(R.id.zen_buttons_container); - zenButtonsContainer.setLayoutTransition(newLayoutTransition(null)); + mZenButtonsContainer = (ViewGroup) findViewById(R.id.zen_buttons_container); + mZenButtonsContainer.setLayoutTransition(newLayoutTransition(null)); mZenSubhead = findViewById(R.id.zen_subhead); + mZenEmbeddedDivider = findViewById(R.id.zen_embedded_divider); mZenSubheadCollapsed = (TextView) findViewById(R.id.zen_subhead_collapsed); mZenSubheadCollapsed.setOnClickListener(new View.OnClickListener() { @@ -192,9 +203,6 @@ public class ZenModePanel extends LinearLayout { Interaction.register(mMoreSettings, mInteractionCallback); mZenConditions = (LinearLayout) findViewById(R.id.zen_conditions); - for (int i = 0; i < mMaxConditions; i++) { - mZenConditions.addView(mInflater.inflate(R.layout.zen_mode_condition, this, false)); - } setLayoutTransition(newLayoutTransition(mTransitionHelper)); } @@ -234,7 +242,9 @@ public class ZenModePanel extends LinearLayout { mAttachedZen = -1; mSessionZen = -1; setSessionExitCondition(null); - setExpanded(false); + if (!mEmbedded) { + setExpanded(false); + } setRequestingConditions(false); mTransitionHelper.clear(); } @@ -266,6 +276,7 @@ public class ZenModePanel extends LinearLayout { private void setExpanded(boolean expanded) { if (expanded == mExpanded) return; + if (DEBUG) Log.d(mTag, "setExpanded " + expanded); mExpanded = expanded; if (mExpanded) { ensureSelection(); @@ -306,6 +317,16 @@ public class ZenModePanel extends LinearLayout { public void init(ZenModeController controller) { mController = controller; + mCountdownConditionSupported = mController.isCountdownConditionSupported(); + final int countdownDelta = mCountdownConditionSupported ? 1 : 0; + mFirstConditionIndex = COUNTDOWN_CONDITION_INDEX + countdownDelta; + final int minConditions = 1 /*forever*/ + countdownDelta; + mMaxConditions = MathUtils.constrain(mContext.getResources() + .getInteger(R.integer.zen_mode_max_conditions), minConditions, 100); + mMaxOptionalConditions = mMaxConditions - minConditions; + for (int i = 0; i < mMaxConditions; i++) { + mZenConditions.addView(mInflater.inflate(R.layout.zen_mode_condition, this, false)); + } setExitCondition(mController.getExitCondition()); refreshExitConditionText(); mSessionZen = getSelectedZen(-1); @@ -339,6 +360,10 @@ public class ZenModePanel extends LinearLayout { return condition == null ? null : condition.copy(); } + public String getExitConditionText() { + return mExitConditionText; + } + private void refreshExitConditionText() { if (mExitCondition == null) { mExitConditionText = foreverSummary(); @@ -363,7 +388,7 @@ public class ZenModePanel extends LinearLayout { private void handleUpdateZen(int zen) { if (mSessionZen != -1 && mSessionZen != zen) { - setExpanded(zen != Global.ZEN_MODE_OFF); + setExpanded(mEmbedded || zen != Global.ZEN_MODE_OFF); mSessionZen = zen; } mZenButtons.setSelectedValue(zen); @@ -405,11 +430,11 @@ public class ZenModePanel extends LinearLayout { final boolean expanded = !mHidden && mExpanded; mZenButtons.setVisibility(mHidden ? GONE : VISIBLE); - mZenSubhead.setVisibility(!mHidden && !zenOff ? VISIBLE : GONE); + mZenSubhead.setVisibility(!mHidden && !zenOff && !mEmbedded ? VISIBLE : GONE); mZenSubheadExpanded.setVisibility(expanded ? VISIBLE : GONE); mZenSubheadCollapsed.setVisibility(!expanded ? VISIBLE : GONE); mMoreSettings.setVisibility(zenImportant && expanded ? VISIBLE : GONE); - mZenConditions.setVisibility(!zenOff && expanded ? VISIBLE : GONE); + mZenConditions.setVisibility(mEmbedded || !zenOff && expanded ? VISIBLE : GONE); if (zenNone) { mZenSubheadExpanded.setText(R.string.zen_no_interruptions_with_warning); @@ -696,7 +721,10 @@ public class ZenModePanel extends LinearLayout { case Global.ZEN_MODE_NO_INTERRUPTIONS: modeText = mContext.getString(R.string.zen_no_interruptions); break; - default: + case Global.ZEN_MODE_ALARMS: + modeText = mContext.getString(R.string.zen_alarms); + break; + default: return; } announceForAccessibility(mContext.getString(R.string.zen_mode_and_condition, modeText, |
