summaryrefslogtreecommitdiffstats
path: root/packages/SystemUI/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/SystemUI/src')
-rwxr-xr-xpackages/SystemUI/src/com/android/systemui/BatteryMeterView.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/SearchPanelCircleView.java592
-rw-r--r--packages/SystemUI/src/com/android/systemui/SearchPanelView.java345
-rw-r--r--packages/SystemUI/src/com/android/systemui/assist/AssistGestureManager.java292
-rw-r--r--packages/SystemUI/src/com/android/systemui/assist/AssistOrbContainer.java155
-rw-r--r--packages/SystemUI/src/com/android/systemui/assist/AssistOrbView.java285
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHostView.java59
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java84
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/DelegateViewHelper.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java117
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java49
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/D.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPanel.java)10
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/Events.java190
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java378
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/Prefs.java69
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/SafetyWarningDialog.java109
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/SegmentedButtons.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/SpTexts.java78
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/Util.java167
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java1069
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java120
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java988
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java89
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumePanelComponent.java184
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java153
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java221
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java25
39 files changed, 4609 insertions, 1396 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index 2e95498..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;
@@ -80,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;
@@ -245,6 +252,13 @@ public class BatteryMeterView extends View implements DemoMode,
mBoltPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
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) {
@@ -309,14 +323,30 @@ public class BatteryMeterView extends View implements DemoMode,
return color;
}
- public void setIconTint(int tint) {
- mIconTint = tint;
- mFramePaint.setColorFilter(new PorterDuffColorFilter(tint, PorterDuff.Mode.SRC_ATOP));
- mBoltPaint.setColor(tint);
- mChargeColor = tint;
+ 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/SearchPanelCircleView.java b/packages/SystemUI/src/com/android/systemui/SearchPanelCircleView.java
deleted file mode 100644
index f33e2b8..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(context.getColor(R.color.search_panel_circle_color));
- mRipplePaint.setColor(context.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/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/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 79600f5..ff6a45a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -40,6 +40,7 @@ public class DndTile extends QSTile<QSTile.BooleanState> {
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;
@@ -52,7 +53,7 @@ public class DndTile extends QSTile<QSTile.BooleanState> {
super(host);
mController = host.getZenModeController();
mDetailAdapter = new DndDetailAdapter();
- mVisible = getSharedPrefs(mContext).getBoolean(PREF_KEY_VISIBLE, false);
+ mVisible = isVisible(host.getContext());
mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_SET_VISIBLE));
}
@@ -65,6 +66,14 @@ public class DndTile extends QSTile<QSTile.BooleanState> {
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;
@@ -103,6 +112,12 @@ public class DndTile extends QSTile<QSTile.BooleanState> {
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);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index fc8f8a5..011c02e 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;
@@ -49,8 +47,6 @@ 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;
@@ -78,9 +74,9 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
RecentsResizeTaskDialog mResizeTaskDebugDialog;
// Search AppWidget
- RecentsAppWidgetHost mAppWidgetHost;
AppWidgetProviderInfo mSearchAppWidgetInfo;
- AppWidgetHostView mSearchAppWidgetHostView;
+ RecentsAppWidgetHost mAppWidgetHost;
+ RecentsAppWidgetHostView mSearchAppWidgetHostView;
// Runnables to finish the Recents activity
FinishRecentsRunnable mFinishLaunchHomeRunnable;
@@ -245,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();
@@ -295,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);
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/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index abed7a5..1377975 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -34,6 +34,7 @@ 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;
@@ -69,7 +70,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
ArrayList<TaskStack> mStacks;
List<TaskStackView> mTaskStackViews = new ArrayList<>();
- View mSearchBar;
+ RecentsAppWidgetHostView mSearchBar;
RecentsViewCallbacks mCb;
public RecentsView(Context context) {
@@ -278,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
@@ -294,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 */
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 9d349ab..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(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 55bdcac..f75dd73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -71,7 +71,6 @@ 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.WindowManager;
import android.view.WindowManagerGlobal;
@@ -79,7 +78,6 @@ 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;
@@ -90,7 +88,6 @@ 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;
@@ -132,7 +129,6 @@ public abstract class BaseStatusBar extends SystemUI implements
protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023;
protected static final int MSG_SHOW_NEXT_AFFILIATED_TASK = 1024;
protected static final int MSG_SHOW_PREV_AFFILIATED_TASK = 1025;
- protected static final int MSG_CLOSE_SEARCH_PANEL = 1027;
protected static final int MSG_SHOW_HEADS_UP = 1028;
protected static final int MSG_HIDE_HEADS_UP = 1029;
protected static final int MSG_ESCALATE_HEADS_UP = 1030;
@@ -164,9 +160,6 @@ public abstract class BaseStatusBar extends SystemUI implements
protected HeadsUpNotificationView mHeadsUpNotificationView;
protected int mHeadsUpNotificationDecay;
- // Search panel
- protected SearchPanelView mSearchPanelView;
-
protected int mCurrentUserId = 0;
final protected SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>();
@@ -1043,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();
}
@@ -1263,35 +1212,7 @@ 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;
}
}
@@ -1935,7 +1856,7 @@ public abstract class BaseStatusBar extends SystemUI implements
protected abstract void setAreThereNotifications();
protected abstract void updateNotifications();
- protected abstract boolean shouldDisableNavbarGestures();
+ public abstract boolean shouldDisableNavbarGestures();
public abstract void addNotification(StatusBarNotification notification,
RankingMap ranking, Entry oldEntry);
@@ -2241,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 8f88e73..7aa9a90 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -96,8 +96,6 @@ 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();
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/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
index 7286907..a82afcf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
@@ -59,9 +59,10 @@ public class SignalClusterView
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;
@@ -115,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);
@@ -273,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 {
@@ -317,15 +321,17 @@ 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) {
- boolean changed = tint != mIconTint;
+ public void setIconTint(int tint, float darkIntensity) {
+ boolean changed = tint != mIconTint || darkIntensity != mDarkIntensity;
mIconTint = tint;
+ mDarkIntensity = darkIntensity;
if (changed && isAttachedToWindow()) {
applyIconTint();
}
@@ -333,14 +339,19 @@ public class SignalClusterView
private void applyIconTint() {
setTint(mVpn, mIconTint);
- setTint(mWifi, mIconTint);
- setTint(mNoSims, 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);
+ 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));
@@ -354,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)
@@ -366,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);
@@ -385,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));
@@ -401,8 +416,8 @@ public class SignalClusterView
}
}
- public void setIconTint(int tint) {
- setTint(mMobile, tint);
+ public void setIconTint(int tint, float darkIntensity) {
+ applyDarkIntensity(darkIntensity, mMobile, mMobileDark);
setTint(mMobileType, tint);
}
}
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 12ff399..c62ad66 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -50,6 +50,8 @@ import com.android.systemui.R;
import com.android.systemui.statusbar.BaseStatusBar;
import com.android.systemui.statusbar.DelegateViewHelper;
import com.android.systemui.statusbar.policy.DeadZone;
+import com.android.systemui.statusbar.policy.KeyButtonView;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -195,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);
}
@@ -261,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() {
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 2c389fb..ad78f6a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -49,7 +49,6 @@ import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.graphics.Canvas;
-import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.PixelFormat;
import android.graphics.Point;
@@ -121,6 +120,7 @@ import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.qs.QSPanel;
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;
@@ -225,8 +225,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
/** Allow some time inbetween the long press for back and recents. */
private static final int LOCK_TO_APP_GESTURE_TOLERENCE = 200;
- private int mLightModeIconColor;
-
PhoneStatusBarPolicy mIconPolicy;
// These are no longer handled by the policy, because we need custom strategies for them
@@ -323,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()) {
@@ -535,7 +535,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
updateDisplaySize();
mScrimSrcModeEnabled = mContext.getResources().getBoolean(
R.bool.config_status_bar_scrim_behind_use_src);
- mLightModeIconColor = mContext.getColor(R.color.light_mode_icon_color);
super.start(); // calls createAndAddWindows()
@@ -642,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);
}
@@ -834,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);
@@ -953,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() {
@@ -1036,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;
}
};
@@ -1083,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
@@ -2060,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);
@@ -2324,9 +2272,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
boolean allowLight = isTransparentBar && !mBatteryController.isPowerSave();
boolean light = (vis & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0;
- mIconController.setIconTint(
- (allowLight && light) ? mLightModeIconColor : Color.WHITE);
-
+ mIconController.setIconsDark(allowLight && light);
}
// restore the recents bit
if (wasRecentsVisible) {
@@ -2989,7 +2935,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
};
@Override
- protected boolean shouldDisableNavbarGestures() {
+ public boolean shouldDisableNavbarGestures() {
return !isDeviceProvisioned()
|| mExpandedVisible
|| (mDisabled & StatusBarManager.DISABLE_SEARCH) != 0;
@@ -3058,6 +3004,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mHandlerThread = null;
}
mContext.unregisterReceiver(mBroadcastReceiver);
+ mAssistGestureManager.destroy();
}
private boolean mDemoModeAllowed;
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 2236aae..ac93ced 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -199,7 +199,7 @@ public class PhoneStatusBarPolicy {
int volumeIconId = 0;
String volumeDescription = null;
- if (DndTile.isVisible(mContext)) {
+ 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);
@@ -218,7 +218,7 @@ public class PhoneStatusBarPolicy {
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 &&
+ } 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/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index c49f620..45da297 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -16,11 +16,11 @@
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.graphics.PorterDuff;
import android.os.Bundle;
import android.os.Handler;
import android.os.SystemClock;
@@ -74,12 +74,16 @@ public class StatusBarIconController {
private int mIconHPadding;
private int mIconTint = Color.WHITE;
+ private float mDarkIntensity;
private boolean mTransitionPending;
private boolean mTintChangePending;
- private int mPendingIconTint;
+ private float mPendingDarkIntensity;
private ValueAnimator mTintAnimator;
+ private int mDarkModeIconColorSingleTone;
+ private int mLightModeIconColorSingleTone;
+
private final Handler mHandler;
private boolean mTransitionDeferring;
private long mTransitionDeferringStartTime;
@@ -111,6 +115,8 @@ public class StatusBarIconController {
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();
}
@@ -296,30 +302,31 @@ public class StatusBarIconController {
}
}
- public void setIconTint(int tint) {
+ public void setIconsDark(boolean dark) {
if (mTransitionPending) {
- deferIconTintChange(tint);
+ deferIconTintChange(dark ? 1.0f : 0.0f);
} else if (mTransitionDeferring) {
- animateIconTint(tint,
+ animateIconTint(dark ? 1.0f : 0.0f,
Math.max(0, mTransitionDeferringStartTime - SystemClock.uptimeMillis()),
mTransitionDeferringDuration);
} else {
- animateIconTint(tint, 0 /* delay */, DEFAULT_TINT_ANIMATION_DURATION);
+ animateIconTint(dark ? 1.0f : 0.0f, 0 /* delay */, DEFAULT_TINT_ANIMATION_DURATION);
}
}
- private void animateIconTint(int targetTint, long delay, long duration) {
+ private void animateIconTint(float targetDarkIntensity, long delay,
+ long duration) {
if (mTintAnimator != null) {
mTintAnimator.cancel();
}
- if (mIconTint == targetTint) {
+ if (mDarkIntensity == targetDarkIntensity) {
return;
}
- mTintAnimator = ValueAnimator.ofArgb(mIconTint, targetTint);
+ mTintAnimator = ValueAnimator.ofFloat(mDarkIntensity, targetDarkIntensity);
mTintAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
- setIconTintInternal((Integer) animation.getAnimatedValue());
+ setIconTintInternal((Float) animation.getAnimatedValue());
}
});
mTintAnimator.setDuration(duration);
@@ -327,17 +334,20 @@ public class StatusBarIconController {
mTintAnimator.setInterpolator(mFastOutSlowIn);
mTintAnimator.start();
}
- private void setIconTintInternal(int tint) {
- mIconTint = tint;
+
+ private void setIconTintInternal(float darkIntensity) {
+ mDarkIntensity = darkIntensity;
+ mIconTint = (int) ArgbEvaluator.getInstance().evaluate(darkIntensity,
+ mLightModeIconColorSingleTone, mDarkModeIconColorSingleTone);
applyIconTint();
}
- private void deferIconTintChange(int tint) {
- if (mTintChangePending && tint == mPendingIconTint) {
+ private void deferIconTintChange(float darkIntensity) {
+ if (mTintChangePending && darkIntensity == mPendingDarkIntensity) {
return;
}
mTintChangePending = true;
- mPendingIconTint = tint;
+ mPendingDarkIntensity = darkIntensity;
}
private void applyIconTint() {
@@ -345,9 +355,9 @@ public class StatusBarIconController {
StatusBarIconView v = (StatusBarIconView) mStatusIcons.getChildAt(i);
v.setImageTintList(ColorStateList.valueOf(mIconTint));
}
- mSignalCluster.setIconTint(mIconTint);
+ mSignalCluster.setIconTint(mIconTint, mDarkIntensity);
mMoreIcon.setImageTintList(ColorStateList.valueOf(mIconTint));
- mBatteryMeterView.setIconTint(mIconTint);
+ mBatteryMeterView.setDarkIntensity(mDarkIntensity);
mClock.setTextColor(mIconTint);
applyNotificationIconsTint();
}
@@ -358,7 +368,6 @@ public class StatusBarIconController {
boolean isPreL = Boolean.TRUE.equals(v.getTag(R.id.icon_is_pre_L));
boolean colorize = !isPreL || isGrayscale(v);
if (colorize) {
- v.setImageTintMode(PorterDuff.Mode.SRC_ATOP);
v.setImageTintList(ColorStateList.valueOf(mIconTint));
}
}
@@ -381,7 +390,7 @@ public class StatusBarIconController {
public void appTransitionCancelled() {
if (mTransitionPending && mTintChangePending) {
mTintChangePending = false;
- animateIconTint(mPendingIconTint, 0 /* delay */, DEFAULT_TINT_ANIMATION_DURATION);
+ animateIconTint(mPendingDarkIntensity, 0 /* delay */, DEFAULT_TINT_ANIMATION_DURATION);
}
mTransitionPending = false;
}
@@ -389,7 +398,7 @@ public class StatusBarIconController {
public void appTransitionStarting(long startTime, long duration) {
if (mTransitionPending && mTintChangePending) {
mTintChangePending = false;
- animateIconTint(mPendingIconTint,
+ animateIconTint(mPendingDarkIntensity,
Math.max(0, startTime - SystemClock.uptimeMillis()),
duration);
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 a18daed..6bc51fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
@@ -48,6 +48,7 @@ public class KeyButtonView extends ImageView {
private int mTouchSlop;
private boolean mSupportsLongpress = true;
private AudioManager mAudioManager;
+ private boolean mGestureAborted;
private final Runnable mCheckLongPress = new Runnable() {
public void run() {
@@ -126,10 +127,15 @@ public class KeyButtonView extends ImageView {
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) {
@@ -203,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/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index d1e1b71..c272e48 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -106,12 +106,6 @@ public class TvStatusBar extends BaseStatusBar {
}
@Override
- protected WindowManager.LayoutParams getSearchLayoutParams(
- LayoutParams layoutParams) {
- return null;
- }
-
- @Override
protected void setAreThereNotifications() {
}
@@ -120,7 +114,7 @@ public class TvStatusBar extends BaseStatusBar {
}
@Override
- protected boolean shouldDisableNavbarGestures() {
+ public boolean shouldDisableNavbarGestures() {
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPanel.java b/packages/SystemUI/src/com/android/systemui/volume/D.java
index 272c321..db7c853 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/D.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,8 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar;
+package com.android.systemui.volume;
-public interface StatusBarPanel {
- public boolean isInContentArea(int x, int y);
+import android.util.Log;
+
+class D {
+ public static boolean BUG = Log.isLoggable("volume", Log.DEBUG);
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/Events.java b/packages/SystemUI/src/com/android/systemui/volume/Events.java
new file mode 100644
index 0000000..12dca94
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/Events.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume;
+
+import android.media.AudioManager;
+import android.media.AudioSystem;
+import android.provider.Settings.Global;
+import android.util.Log;
+
+import com.android.systemui.volume.VolumeDialogController.State;
+
+import java.util.Arrays;
+
+/**
+ * Interesting events related to the volume.
+ */
+public class Events {
+ private static final String TAG = Util.logTag(Events.class);
+
+ public static final int EVENT_SHOW_DIALOG = 0; // (reason|int) (keyguard|bool)
+ public static final int EVENT_DISMISS_DIALOG = 1; // (reason|int)
+ public static final int EVENT_ACTIVE_STREAM_CHANGED = 2; // (stream|int)
+ public static final int EVENT_EXPAND = 3; // (expand|bool)
+ public static final int EVENT_KEY = 4;
+ public static final int EVENT_COLLECTION_STARTED = 5;
+ public static final int EVENT_COLLECTION_STOPPED = 6;
+ public static final int EVENT_ICON_CLICK = 7; // (stream|int) (icon_state|int)
+ public static final int EVENT_SETTINGS_CLICK = 8;
+ public static final int EVENT_TOUCH_LEVEL_CHANGED = 9; // (stream|int) (level|int)
+ public static final int EVENT_LEVEL_CHANGED = 10; // (stream|int) (level|int)
+ public static final int EVENT_INTERNAL_RINGER_MODE_CHANGED = 11; // (mode|int)
+ public static final int EVENT_EXTERNAL_RINGER_MODE_CHANGED = 12; // (mode|int)
+ public static final int EVENT_ZEN_MODE_CHANGED = 13; // (mode|int)
+ public static final int EVENT_SUPPRESSOR_CHANGED = 14; // (component|string) (name|string)
+ public static final int EVENT_MUTE_CHANGED = 15; // (stream|int) (muted|bool)
+
+ private static final String[] EVENT_TAGS = {
+ "show_dialog",
+ "dismiss_dialog",
+ "active_stream_changed",
+ "expand",
+ "key",
+ "collection_started",
+ "collection_stopped",
+ "icon_click",
+ "settings_click",
+ "touch_level_changed",
+ "level_changed",
+ "internal_ringer_mode_changed",
+ "external_ringer_mode_changed",
+ "zen_mode_changed",
+ "suppressor_changed",
+ "mute_changed",
+ };
+
+ public static final int DISMISS_REASON_UNKNOWN = 0;
+ public static final int DISMISS_REASON_TOUCH_OUTSIDE = 1;
+ public static final int DISMISS_REASON_VOLUME_CONTROLLER = 2;
+ public static final int DISMISS_REASON_TIMEOUT = 3;
+ public static final int DISMISS_REASON_SCREEN_OFF = 4;
+ public static final int DISMISS_REASON_SETTINGS_CLICKED = 5;
+ public static final int DISMISS_REASON_DONE_CLICKED = 6;
+ public static final String[] DISMISS_REASONS = {
+ "unknown",
+ "touch_outside",
+ "volume_controller",
+ "timeout",
+ "screen_off",
+ "settings_clicked",
+ "done_clicked",
+ };
+
+ public static final int SHOW_REASON_UNKNOWN = 0;
+ public static final int SHOW_REASON_VOLUME_CHANGED = 1;
+ public static final int SHOW_REASON_REMOTE_VOLUME_CHANGED = 2;
+ public static final String[] SHOW_REASONS = {
+ "unknown",
+ "volume_changed",
+ "remote_volume_changed"
+ };
+
+ public static final int ICON_STATE_UNKNOWN = 0;
+ public static final int ICON_STATE_UNMUTE = 1;
+ public static final int ICON_STATE_MUTE = 2;
+ public static final int ICON_STATE_VIBRATE = 3;
+
+ public static Callback sCallback;
+
+ public static void writeEvent(int tag, Object... list) {
+ final long time = System.currentTimeMillis();
+ final StringBuilder sb = new StringBuilder("writeEvent ").append(EVENT_TAGS[tag]);
+ if (list != null && list.length > 0) {
+ sb.append(" ");
+ switch (tag) {
+ case EVENT_SHOW_DIALOG:
+ sb.append(SHOW_REASONS[(Integer) list[0]]).append(" keyguard=").append(list[1]);
+ break;
+ case EVENT_EXPAND:
+ sb.append(list[0]);
+ break;
+ case EVENT_DISMISS_DIALOG:
+ sb.append(DISMISS_REASONS[(Integer) list[0]]);
+ break;
+ case EVENT_ACTIVE_STREAM_CHANGED:
+ sb.append(AudioSystem.streamToString((Integer) list[0]));
+ break;
+ case EVENT_ICON_CLICK:
+ sb.append(AudioSystem.streamToString((Integer) list[0])).append(' ')
+ .append(iconStateToString((Integer) list[1]));
+ break;
+ case EVENT_TOUCH_LEVEL_CHANGED:
+ case EVENT_LEVEL_CHANGED:
+ case EVENT_MUTE_CHANGED:
+ sb.append(AudioSystem.streamToString((Integer) list[0])).append(' ')
+ .append(list[1]);
+ break;
+ case EVENT_INTERNAL_RINGER_MODE_CHANGED:
+ case EVENT_EXTERNAL_RINGER_MODE_CHANGED:
+ sb.append(ringerModeToString((Integer) list[0]));
+ break;
+ case EVENT_ZEN_MODE_CHANGED:
+ sb.append(zenModeToString((Integer) list[0]));
+ break;
+ case EVENT_SUPPRESSOR_CHANGED:
+ sb.append(list[0]).append(' ').append(list[1]);
+ break;
+ default:
+ sb.append(Arrays.asList(list));
+ break;
+ }
+ }
+ Log.i(TAG, sb.toString());
+ if (sCallback != null) {
+ sCallback.writeEvent(time, tag, list);
+ }
+ }
+
+ public static void writeState(long time, State state) {
+ if (sCallback != null) {
+ sCallback.writeState(time, state);
+ }
+ }
+
+ private static String iconStateToString(int iconState) {
+ switch (iconState) {
+ case ICON_STATE_UNMUTE: return "unmute";
+ case ICON_STATE_MUTE: return "mute";
+ case ICON_STATE_VIBRATE: return "vibrate";
+ default: return "unknown_state_" + iconState;
+ }
+ }
+
+ private static String ringerModeToString(int ringerMode) {
+ switch (ringerMode) {
+ case AudioManager.RINGER_MODE_SILENT: return "silent";
+ case AudioManager.RINGER_MODE_VIBRATE: return "vibrate";
+ case AudioManager.RINGER_MODE_NORMAL: return "normal";
+ default: return "unknown";
+ }
+ }
+
+ private static String zenModeToString(int zenMode) {
+ switch (zenMode) {
+ case Global.ZEN_MODE_OFF: return "off";
+ case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: return "important_interruptions";
+ case Global.ZEN_MODE_ALARMS: return "alarms";
+ case Global.ZEN_MODE_NO_INTERRUPTIONS: return "no_interruptions";
+ default: return "unknown";
+ }
+ }
+
+ public interface Callback {
+ void writeEvent(long time, int tag, Object[] list);
+ void writeState(long time, State state);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java b/packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java
new file mode 100644
index 0000000..712ea27
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.media.IRemoteVolumeController;
+import android.media.MediaMetadata;
+import android.media.session.ISessionController;
+import android.media.session.MediaController;
+import android.media.session.MediaController.PlaybackInfo;
+import android.media.session.MediaSession.QueueItem;
+import android.media.session.MediaSession.Token;
+import android.media.session.MediaSessionManager;
+import android.media.session.MediaSessionManager.OnActiveSessionsChangedListener;
+import android.media.session.PlaybackState;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Convenience client for all media session updates. Provides a callback interface for events
+ * related to remote media sessions.
+ */
+public class MediaSessions {
+ private static final String TAG = Util.logTag(MediaSessions.class);
+
+ private static final boolean USE_SERVICE_LABEL = false;
+
+ private final Context mContext;
+ private final H mHandler;
+ private final MediaSessionManager mMgr;
+ private final Map<Token, MediaControllerRecord> mRecords = new HashMap<>();
+ private final Callbacks mCallbacks;
+
+ private boolean mInit;
+
+ public MediaSessions(Context context, Looper looper, Callbacks callbacks) {
+ mContext = context;
+ mHandler = new H(looper);
+ mMgr = (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
+ mCallbacks = callbacks;
+ }
+
+ public void dump(PrintWriter writer) {
+ writer.println(getClass().getSimpleName() + " state:");
+ writer.print(" mInit: "); writer.println(mInit);
+ writer.print(" mRecords.size: "); writer.println(mRecords.size());
+ int i = 0;
+ for (MediaControllerRecord r : mRecords.values()) {
+ dump(++i, writer, r.controller);
+ }
+ }
+
+ public void init() {
+ if (D.BUG) Log.d(TAG, "init");
+ // will throw if no permission
+ mMgr.addOnActiveSessionsChangedListener(mSessionsListener, null, mHandler);
+ mInit = true;
+ postUpdateSessions();
+ mMgr.setRemoteVolumeController(mRvc);
+ }
+
+ protected void postUpdateSessions() {
+ if (!mInit) return;
+ mHandler.sendEmptyMessage(H.UPDATE_SESSIONS);
+ }
+
+ public void destroy() {
+ if (D.BUG) Log.d(TAG, "destroy");
+ mInit = false;
+ mMgr.removeOnActiveSessionsChangedListener(mSessionsListener);
+ }
+
+ public void setVolume(Token token, int level) {
+ final MediaControllerRecord r = mRecords.get(token);
+ if (r == null) {
+ Log.w(TAG, "setVolume: No record found for token " + token);
+ return;
+ }
+ if (D.BUG) Log.d(TAG, "Setting level to " + level);
+ r.controller.setVolumeTo(level, 0);
+ }
+
+ private void onRemoteVolumeChangedH(ISessionController session, int flags) {
+ final MediaController controller = new MediaController(mContext, session);
+ if (D.BUG) Log.d(TAG, "remoteVolumeChangedH " + controller.getPackageName() + " "
+ + Util.audioManagerFlagsToString(flags));
+ final Token token = controller.getSessionToken();
+ mCallbacks.onRemoteVolumeChanged(token, flags);
+ }
+
+ private void onUpdateRemoteControllerH(ISessionController session) {
+ final MediaController controller = session != null ? new MediaController(mContext, session)
+ : null;
+ final String pkg = controller != null ? controller.getPackageName() : null;
+ if (D.BUG) Log.d(TAG, "updateRemoteControllerH " + pkg);
+ // this may be our only indication that a remote session is changed, refresh
+ postUpdateSessions();
+ }
+
+ protected void onActiveSessionsUpdatedH(List<MediaController> controllers) {
+ if (D.BUG) Log.d(TAG, "onActiveSessionsUpdatedH n=" + controllers.size());
+ final Set<Token> toRemove = new HashSet<Token>(mRecords.keySet());
+ for (MediaController controller : controllers) {
+ final Token token = controller.getSessionToken();
+ final PlaybackInfo pi = controller.getPlaybackInfo();
+ toRemove.remove(token);
+ if (!mRecords.containsKey(token)) {
+ final MediaControllerRecord r = new MediaControllerRecord(controller);
+ r.name = getControllerName(controller);
+ mRecords.put(token, r);
+ controller.registerCallback(r, mHandler);
+ }
+ final MediaControllerRecord r = mRecords.get(token);
+ final boolean remote = isRemote(pi);
+ if (remote) {
+ updateRemoteH(token, r.name, pi);
+ r.sentRemote = true;
+ }
+ }
+ for (Token t : toRemove) {
+ final MediaControllerRecord r = mRecords.get(t);
+ r.controller.unregisterCallback(r);
+ mRecords.remove(t);
+ if (D.BUG) Log.d(TAG, "Removing " + r.name + " sentRemote=" + r.sentRemote);
+ if (r.sentRemote) {
+ mCallbacks.onRemoteRemoved(t);
+ r.sentRemote = false;
+ }
+ }
+ }
+
+ private static boolean isRemote(PlaybackInfo pi) {
+ return pi != null && pi.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE;
+ }
+
+ protected String getControllerName(MediaController controller) {
+ final PackageManager pm = mContext.getPackageManager();
+ final String pkg = controller.getPackageName();
+ try {
+ if (USE_SERVICE_LABEL) {
+ final List<ResolveInfo> ris = pm.queryIntentServices(
+ new Intent("android.media.MediaRouteProviderService").setPackage(pkg), 0);
+ if (ris != null) {
+ for (ResolveInfo ri : ris) {
+ if (ri.serviceInfo == null) continue;
+ if (pkg.equals(ri.serviceInfo.packageName)) {
+ final String serviceLabel =
+ Objects.toString(ri.serviceInfo.loadLabel(pm), "").trim();
+ if (serviceLabel.length() > 0) {
+ return serviceLabel;
+ }
+ }
+ }
+ }
+ }
+ final ApplicationInfo ai = pm.getApplicationInfo(pkg, 0);
+ final String appLabel = Objects.toString(ai.loadLabel(pm), "").trim();
+ if (appLabel.length() > 0) {
+ return appLabel;
+ }
+ } catch (NameNotFoundException e) { }
+ return pkg;
+ }
+
+ private void updateRemoteH(Token token, String name, PlaybackInfo pi) {
+ if (mCallbacks != null) {
+ mCallbacks.onRemoteUpdate(token, name, pi);
+ }
+ }
+
+ private static void dump(int n, PrintWriter writer, MediaController c) {
+ writer.println(" Controller " + n + ": " + c.getPackageName());
+ final Bundle extras = c.getExtras();
+ final long flags = c.getFlags();
+ final MediaMetadata mm = c.getMetadata();
+ final PlaybackInfo pi = c.getPlaybackInfo();
+ final PlaybackState playbackState = c.getPlaybackState();
+ final List<QueueItem> queue = c.getQueue();
+ final CharSequence queueTitle = c.getQueueTitle();
+ final int ratingType = c.getRatingType();
+ final PendingIntent sessionActivity = c.getSessionActivity();
+
+ writer.println(" PlaybackState: " + Util.playbackStateToString(playbackState));
+ writer.println(" PlaybackInfo: " + Util.playbackInfoToString(pi));
+ if (mm != null) {
+ writer.println(" MediaMetadata.desc=" + mm.getDescription());
+ }
+ writer.println(" RatingType: " + ratingType);
+ writer.println(" Flags: " + flags);
+ if (extras != null) {
+ writer.println(" Extras:");
+ for (String key : extras.keySet()) {
+ writer.println(" " + key + "=" + extras.get(key));
+ }
+ }
+ if (queueTitle != null) {
+ writer.println(" QueueTitle: " + queueTitle);
+ }
+ if (queue != null && !queue.isEmpty()) {
+ writer.println(" Queue:");
+ for (QueueItem qi : queue) {
+ writer.println(" " + qi);
+ }
+ }
+ if (pi != null) {
+ writer.println(" sessionActivity: " + sessionActivity);
+ }
+ }
+
+ public static void dumpMediaSessions(Context context) {
+ final MediaSessionManager mgr = (MediaSessionManager) context
+ .getSystemService(Context.MEDIA_SESSION_SERVICE);
+ try {
+ final List<MediaController> controllers = mgr.getActiveSessions(null);
+ final int N = controllers.size();
+ if (D.BUG) Log.d(TAG, N + " controllers");
+ for (int i = 0; i < N; i++) {
+ final StringWriter sw = new StringWriter();
+ final PrintWriter pw = new PrintWriter(sw, true);
+ dump(i + 1, pw, controllers.get(i));
+ if (D.BUG) Log.d(TAG, sw.toString());
+ }
+ } catch (SecurityException e) {
+ Log.w(TAG, "Not allowed to get sessions", e);
+ }
+ }
+
+ private final class MediaControllerRecord extends MediaController.Callback {
+ private final MediaController controller;
+
+ private boolean sentRemote;
+ private String name;
+
+ private MediaControllerRecord(MediaController controller) {
+ this.controller = controller;
+ }
+
+ private String cb(String method) {
+ return method + " " + controller.getPackageName() + " ";
+ }
+
+ @Override
+ public void onAudioInfoChanged(PlaybackInfo info) {
+ if (D.BUG) Log.d(TAG, cb("onAudioInfoChanged") + Util.playbackInfoToString(info)
+ + " sentRemote=" + sentRemote);
+ final boolean remote = isRemote(info);
+ if (!remote && sentRemote) {
+ mCallbacks.onRemoteRemoved(controller.getSessionToken());
+ sentRemote = false;
+ } else if (remote) {
+ updateRemoteH(controller.getSessionToken(), name, info);
+ sentRemote = true;
+ }
+ }
+
+ @Override
+ public void onExtrasChanged(Bundle extras) {
+ if (D.BUG) Log.d(TAG, cb("onExtrasChanged") + extras);
+ }
+
+ @Override
+ public void onMetadataChanged(MediaMetadata metadata) {
+ if (D.BUG) Log.d(TAG, cb("onMetadataChanged") + Util.mediaMetadataToString(metadata));
+ }
+
+ @Override
+ public void onPlaybackStateChanged(PlaybackState state) {
+ if (D.BUG) Log.d(TAG, cb("onPlaybackStateChanged") + Util.playbackStateToString(state));
+ }
+
+ @Override
+ public void onQueueChanged(List<QueueItem> queue) {
+ if (D.BUG) Log.d(TAG, cb("onQueueChanged") + queue);
+ }
+
+ @Override
+ public void onQueueTitleChanged(CharSequence title) {
+ if (D.BUG) Log.d(TAG, cb("onQueueTitleChanged") + title);
+ }
+
+ @Override
+ public void onSessionDestroyed() {
+ if (D.BUG) Log.d(TAG, cb("onSessionDestroyed"));
+ }
+
+ @Override
+ public void onSessionEvent(String event, Bundle extras) {
+ if (D.BUG) Log.d(TAG, cb("onSessionEvent") + "event=" + event + " extras=" + extras);
+ }
+ }
+
+ private final OnActiveSessionsChangedListener mSessionsListener =
+ new OnActiveSessionsChangedListener() {
+ @Override
+ public void onActiveSessionsChanged(List<MediaController> controllers) {
+ onActiveSessionsUpdatedH(controllers);
+ }
+ };
+
+ private final IRemoteVolumeController mRvc = new IRemoteVolumeController.Stub() {
+ @Override
+ public void remoteVolumeChanged(ISessionController session, int flags)
+ throws RemoteException {
+ mHandler.obtainMessage(H.REMOTE_VOLUME_CHANGED, flags, 0, session).sendToTarget();
+ }
+
+ @Override
+ public void updateRemoteController(final ISessionController session)
+ throws RemoteException {
+ mHandler.obtainMessage(H.UPDATE_REMOTE_CONTROLLER, session).sendToTarget();
+ }
+ };
+
+ private final class H extends Handler {
+ private static final int UPDATE_SESSIONS = 1;
+ private static final int REMOTE_VOLUME_CHANGED = 2;
+ private static final int UPDATE_REMOTE_CONTROLLER = 3;
+
+ private H(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case UPDATE_SESSIONS:
+ onActiveSessionsUpdatedH(mMgr.getActiveSessions(null));
+ break;
+ case REMOTE_VOLUME_CHANGED:
+ onRemoteVolumeChangedH((ISessionController) msg.obj, msg.arg1);
+ break;
+ case UPDATE_REMOTE_CONTROLLER:
+ onUpdateRemoteControllerH((ISessionController) msg.obj);
+ break;
+ }
+ }
+ }
+
+ public interface Callbacks {
+ void onRemoteUpdate(Token token, String name, PlaybackInfo pi);
+ void onRemoteRemoved(Token t);
+ void onRemoteVolumeChanged(Token token, int flags);
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/Prefs.java b/packages/SystemUI/src/com/android/systemui/volume/Prefs.java
new file mode 100644
index 0000000..58bc9f4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/Prefs.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.preference.PreferenceManager;
+
+/**
+ * Configuration for the volume dialog + related policy.
+ */
+public class Prefs {
+
+ public static final String PREF_ENABLE_PROTOTYPE = "pref_enable_prototype"; // not persistent
+ public static final String PREF_SHOW_ALARMS = "pref_show_alarms";
+ public static final String PREF_SHOW_SYSTEM = "pref_show_system";
+ public static final String PREF_SHOW_HEADERS = "pref_show_headers";
+ public static final String PREF_SHOW_FAKE_REMOTE_1 = "pref_show_fake_remote_1";
+ public static final String PREF_SHOW_FAKE_REMOTE_2 = "pref_show_fake_remote_2";
+ public static final String PREF_SHOW_FOOTER = "pref_show_footer";
+ public static final String PREF_ZEN_FOOTER = "pref_zen_footer";
+ public static final String PREF_ENABLE_AUTOMUTE = "pref_enable_automute";
+ public static final String PREF_ENABLE_SILENT_MODE = "pref_enable_silent_mode";
+ public static final String PREF_DEBUG_LOGGING = "pref_debug_logging";
+ public static final String PREF_SEND_LOGS = "pref_send_logs";
+ public static final String PREF_ADJUST_SYSTEM = "pref_adjust_system";
+ public static final String PREF_ADJUST_VOICE_CALLS = "pref_adjust_voice_calls";
+ public static final String PREF_ADJUST_BLUETOOTH_SCO = "pref_adjust_bluetooth_sco";
+ public static final String PREF_ADJUST_MEDIA = "pref_adjust_media";
+ public static final String PREF_ADJUST_ALARMS = "pref_adjust_alarms";
+ public static final String PREF_ADJUST_NOTIFICATION = "pref_adjust_notification";
+
+ public static final boolean DEFAULT_SHOW_HEADERS = true;
+ public static final boolean DEFAULT_SHOW_FOOTER = true;
+ public static final boolean DEFAULT_ENABLE_AUTOMUTE = true;
+ public static final boolean DEFAULT_ENABLE_SILENT_MODE = true;
+ public static final boolean DEFAULT_ZEN_FOOTER = true;
+
+ public static void unregisterCallbacks(Context c, OnSharedPreferenceChangeListener listener) {
+ prefs(c).unregisterOnSharedPreferenceChangeListener(listener);
+ }
+
+ public static void registerCallbacks(Context c, OnSharedPreferenceChangeListener listener) {
+ prefs(c).registerOnSharedPreferenceChangeListener(listener);
+ }
+
+ private static SharedPreferences prefs(Context context) {
+ return PreferenceManager.getDefaultSharedPreferences(context);
+ }
+
+ public static boolean get(Context context, String key, boolean def) {
+ return prefs(context).getBoolean(key, def);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/SafetyWarningDialog.java b/packages/SystemUI/src/com/android/systemui/volume/SafetyWarningDialog.java
new file mode 100644
index 0000000..04640a2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/SafetyWarningDialog.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.WindowManager;
+
+import com.android.systemui.statusbar.phone.SystemUIDialog;
+
+abstract public class SafetyWarningDialog extends SystemUIDialog
+ implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener {
+
+ private static final String TAG = Util.logTag(SafetyWarningDialog.class);
+
+ private static final int KEY_CONFIRM_ALLOWED_AFTER = 1000; // milliseconds
+
+ private final Context mContext;
+ private final AudioManager mAudioManager;
+
+ private long mShowTime;
+ private boolean mNewVolumeUp;
+
+ public SafetyWarningDialog(Context context, AudioManager audioManager) {
+ super(context);
+ mContext = context;
+ mAudioManager = audioManager;
+
+ getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
+ setMessage(mContext.getString(com.android.internal.R.string.safe_media_volume_warning));
+ setButton(DialogInterface.BUTTON_POSITIVE,
+ mContext.getString(com.android.internal.R.string.yes), this);
+ setButton(DialogInterface.BUTTON_NEGATIVE,
+ mContext.getString(com.android.internal.R.string.no), (OnClickListener) null);
+ setOnDismissListener(this);
+
+ final IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+ context.registerReceiver(mReceiver, filter);
+ }
+
+ abstract protected void cleanUp();
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_VOLUME_UP && event.getRepeatCount() == 0) {
+ mNewVolumeUp = true;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_VOLUME_UP && mNewVolumeUp
+ && (System.currentTimeMillis() - mShowTime) > KEY_CONFIRM_ALLOWED_AFTER) {
+ if (D.BUG) Log.d(TAG, "Confirmed warning via VOLUME_UP");
+ mAudioManager.disableSafeMediaVolume();
+ dismiss();
+ }
+ return super.onKeyUp(keyCode, event);
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ mAudioManager.disableSafeMediaVolume();
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ mShowTime = System.currentTimeMillis();
+ }
+
+ @Override
+ public void onDismiss(DialogInterface unused) {
+ mContext.unregisterReceiver(mReceiver);
+ cleanUp();
+ }
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
+ if (D.BUG) Log.d(TAG, "Received ACTION_CLOSE_SYSTEM_DIALOGS");
+ cancel();
+ cleanUp();
+ }
+ }
+ };
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/volume/SegmentedButtons.java b/packages/SystemUI/src/com/android/systemui/volume/SegmentedButtons.java
index 5f5b881..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.getColor(selected
- ? R.color.segmented_button_selected : R.color.segmented_button_unselected));
}
fireOnSelected();
}
- public void addButton(int labelResId, int iconResId, Object value) {
+ public void addButton(int labelResId, Object value) {
final Button b = (Button) mInflater.inflate(R.layout.segmented_button, this, false);
b.setTag(LABEL_RES_KEY, labelResId);
b.setText(labelResId);
- b.setCompoundDrawablesWithIntrinsicBounds(0, iconResId, 0, 0);
final LayoutParams lp = (LayoutParams) b.getLayoutParams();
if (getChildCount() == 0) {
lp.leftMargin = lp.rightMargin = 0; // first button has no margin
diff --git a/packages/SystemUI/src/com/android/systemui/volume/SpTexts.java b/packages/SystemUI/src/com/android/systemui/volume/SpTexts.java
new file mode 100644
index 0000000..d8e53db
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/SpTexts.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.ArrayMap;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.View.OnAttachStateChangeListener;
+import android.widget.TextView;
+
+/**
+ * Capture initial sp values for registered textviews, and update properly when configuration
+ * changes.
+ */
+public class SpTexts {
+
+ private final Context mContext;
+ private final ArrayMap<TextView, Integer> mTexts = new ArrayMap<>();
+
+ public SpTexts(Context context) {
+ mContext = context;
+ }
+
+ public int add(final TextView text) {
+ if (text == null) return 0;
+ final Resources res = mContext.getResources();
+ final float fontScale = res.getConfiguration().fontScale;
+ final float density = res.getDisplayMetrics().density;
+ final float px = text.getTextSize();
+ final int sp = (int)(px / fontScale / density);
+ mTexts.put(text, sp);
+ text.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ }
+
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ setTextSizeH(text, sp);
+ }
+ });
+ return sp;
+ }
+
+ public void update() {
+ if (mTexts.isEmpty()) return;
+ mTexts.keyAt(0).post(mUpdateAll);
+ }
+
+ private void setTextSizeH(TextView text, int sp) {
+ text.setTextSize(TypedValue.COMPLEX_UNIT_SP, sp);
+ }
+
+ private final Runnable mUpdateAll = new Runnable() {
+ @Override
+ public void run() {
+ for (int i = 0; i < mTexts.size(); i++) {
+ setTextSizeH(mTexts.keyAt(i), mTexts.valueAt(i));
+ }
+ }
+ };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/Util.java b/packages/SystemUI/src/com/android/systemui/volume/Util.java
new file mode 100644
index 0000000..78baf67
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/Util.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume;
+
+import android.media.AudioManager;
+import android.media.MediaMetadata;
+import android.media.VolumeProvider;
+import android.media.session.MediaController.PlaybackInfo;
+import android.media.session.PlaybackState;
+import android.service.notification.ZenModeConfig.DowntimeInfo;
+import android.view.View;
+import android.widget.TextView;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * Static helpers for the volume dialog.
+ */
+class Util {
+
+ // Note: currently not shown (only used in the text footer)
+ private static final SimpleDateFormat HMMAA = new SimpleDateFormat("h:mm aa", Locale.US);
+
+ private static int[] AUDIO_MANAGER_FLAGS = new int[] {
+ AudioManager.FLAG_SHOW_UI,
+ AudioManager.FLAG_VIBRATE,
+ AudioManager.FLAG_PLAY_SOUND,
+ AudioManager.FLAG_ALLOW_RINGER_MODES,
+ AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE,
+ AudioManager.FLAG_SHOW_VIBRATE_HINT,
+ AudioManager.FLAG_SHOW_SILENT_HINT,
+ AudioManager.FLAG_FROM_KEY,
+ AudioManager.FLAG_SHOW_UI_WARNINGS,
+ };
+
+ private static String[] AUDIO_MANAGER_FLAG_NAMES = new String[] {
+ "SHOW_UI",
+ "VIBRATE",
+ "PLAY_SOUND",
+ "ALLOW_RINGER_MODES",
+ "REMOVE_SOUND_AND_VIBRATE",
+ "SHOW_VIBRATE_HINT",
+ "SHOW_SILENT_HINT",
+ "FROM_KEY",
+ "SHOW_UI_WARNINGS",
+ };
+
+ public static String logTag(Class<?> c) {
+ final String tag = "vol." + c.getSimpleName();
+ return tag.length() < 23 ? tag : tag.substring(0, 23);
+ }
+
+ public static String ringerModeToString(int ringerMode) {
+ switch (ringerMode) {
+ case AudioManager.RINGER_MODE_SILENT: return "RINGER_MODE_SILENT";
+ case AudioManager.RINGER_MODE_VIBRATE: return "RINGER_MODE_VIBRATE";
+ case AudioManager.RINGER_MODE_NORMAL: return "RINGER_MODE_NORMAL";
+ default: return "RINGER_MODE_UNKNOWN_" + ringerMode;
+ }
+ }
+
+ public static String mediaMetadataToString(MediaMetadata metadata) {
+ return metadata.getDescription().toString();
+ }
+
+ public static String playbackInfoToString(PlaybackInfo info) {
+ if (info == null) return null;
+ final String type = playbackInfoTypeToString(info.getPlaybackType());
+ final String vc = volumeProviderControlToString(info.getVolumeControl());
+ return String.format("PlaybackInfo[vol=%s,max=%s,type=%s,vc=%s],atts=%s",
+ info.getCurrentVolume(), info.getMaxVolume(), type, vc, info.getAudioAttributes());
+ }
+
+ public static String playbackInfoTypeToString(int type) {
+ switch (type) {
+ case PlaybackInfo.PLAYBACK_TYPE_LOCAL: return "LOCAL";
+ case PlaybackInfo.PLAYBACK_TYPE_REMOTE: return "REMOTE";
+ default: return "UNKNOWN_" + type;
+ }
+ }
+
+ public static String playbackStateStateToString(int state) {
+ switch (state) {
+ case PlaybackState.STATE_NONE: return "STATE_NONE";
+ case PlaybackState.STATE_STOPPED: return "STATE_STOPPED";
+ case PlaybackState.STATE_PAUSED: return "STATE_PAUSED";
+ case PlaybackState.STATE_PLAYING: return "STATE_PLAYING";
+ default: return "UNKNOWN_" + state;
+ }
+ }
+
+ public static String volumeProviderControlToString(int control) {
+ switch (control) {
+ case VolumeProvider.VOLUME_CONTROL_ABSOLUTE: return "VOLUME_CONTROL_ABSOLUTE";
+ case VolumeProvider.VOLUME_CONTROL_FIXED: return "VOLUME_CONTROL_FIXED";
+ case VolumeProvider.VOLUME_CONTROL_RELATIVE: return "VOLUME_CONTROL_RELATIVE";
+ default: return "VOLUME_CONTROL_UNKNOWN_" + control;
+ }
+ }
+
+ public static String playbackStateToString(PlaybackState playbackState) {
+ if (playbackState == null) return null;
+ return playbackStateStateToString(playbackState.getState()) + " " + playbackState;
+ }
+
+ public static String audioManagerFlagsToString(int value) {
+ return bitFieldToString(value, AUDIO_MANAGER_FLAGS, AUDIO_MANAGER_FLAG_NAMES);
+ }
+
+ private static String bitFieldToString(int value, int[] values, String[] names) {
+ if (value == 0) return "";
+ final StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < values.length; i++) {
+ if ((value & values[i]) != 0) {
+ if (sb.length() > 0) sb.append(',');
+ sb.append(names[i]);
+ }
+ value &= ~values[i];
+ }
+ if (value != 0) {
+ if (sb.length() > 0) sb.append(',');
+ sb.append("UNKNOWN_").append(value);
+ }
+ return sb.toString();
+ }
+
+ public static String getShortTime(long millis) {
+ return HMMAA.format(new Date(millis));
+ }
+
+ public static String getShortTime(DowntimeInfo info) {
+ return ((info.endHour + 1) % 12) + ":" + (info.endMinute < 10 ? " " : "") + info.endMinute;
+ }
+
+ public static void setText(TextView tv, CharSequence text) {
+ if (Objects.equals(tv.getText(), text)) return;
+ tv.setText(text);
+ }
+
+ public static final void setVisOrGone(View v, boolean vis) {
+ if (v == null || (v.getVisibility() == View.VISIBLE) == vis) return;
+ v.setVisibility(vis ? View.VISIBLE : View.GONE);
+ }
+
+ public static final void setVisOrInvis(View v, boolean vis) {
+ if (v == null || (v.getVisibility() == View.VISIBLE) == vis) return;
+ v.setVisibility(vis ? View.VISIBLE : View.INVISIBLE);
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java
index e3f8f3d..1f0ee57 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java
@@ -16,10 +16,18 @@
package com.android.systemui.volume;
+import android.content.res.Configuration;
+
import com.android.systemui.DemoMode;
import com.android.systemui.statusbar.policy.ZenModeController;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
public interface VolumeComponent extends DemoMode {
ZenModeController getZenController();
void dismissNow();
+ void onConfigurationChanged(Configuration newConfig);
+ void dump(FileDescriptor fd, PrintWriter pw, String[] args);
+ void register();
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
new file mode 100644
index 0000000..d8b3965
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
@@ -0,0 +1,1069 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume;
+
+import android.animation.LayoutTransition;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
+import android.app.Dialog;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
+import android.media.AudioSystem;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.provider.Settings.Global;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.DowntimeInfo;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.SparseBooleanArray;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLayoutChangeListener;
+import android.view.View.OnTouchListener;
+import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.volume.VolumeDialogController.State;
+import com.android.systemui.volume.VolumeDialogController.StreamState;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Visual presentation of the volume dialog.
+ *
+ * A client of VolumeDialogController and its state model.
+ *
+ * Methods ending in "H" must be called on the (ui) handler.
+ */
+public class VolumeDialog {
+ private static final String TAG = Util.logTag(VolumeDialog.class);
+
+ private static final long USER_ATTEMPT_GRACE_PERIOD = 1000;
+ private static final int WAIT_FOR_RIPPLE = 200;
+ private static final int UPDATE_ANIMATION_DURATION = 80;
+
+ private final Context mContext;
+ private final H mHandler = new H();
+ private final VolumeDialogController mController;
+
+ private final CustomDialog mDialog;
+ private final ViewGroup mDialogView;
+ private final ViewGroup mDialogContentView;
+ private final ImageButton mExpandButton;
+ private final TextView mFootlineText;
+ private final Button mFootlineAction;
+ private final View mSettingsButton;
+ private final View mFooter;
+ private final List<VolumeRow> mRows = new ArrayList<VolumeRow>();
+ private final SpTexts mSpTexts;
+ private final SparseBooleanArray mDynamic = new SparseBooleanArray();
+ private final KeyguardManager mKeyguard;
+ private final int mExpandButtonAnimationDuration;
+ private final View mTextFooter;
+ private final ZenFooter mZenFooter;
+ private final LayoutTransition mLayoutTransition;
+ private final Object mSafetyWarningLock = new Object();
+
+ private boolean mShowing;
+ private boolean mExpanded;
+ private int mActiveStream;
+ private boolean mShowHeaders = Prefs.DEFAULT_SHOW_HEADERS;
+ private boolean mShowFooter = Prefs.DEFAULT_SHOW_FOOTER;
+ private boolean mShowZenFooter = Prefs.DEFAULT_ZEN_FOOTER;
+ private boolean mAutomute = Prefs.DEFAULT_ENABLE_AUTOMUTE;
+ private boolean mSilentMode = Prefs.DEFAULT_ENABLE_SILENT_MODE;
+ private State mState;
+ private int mExpandButtonRes;
+ private boolean mExpanding;
+ private SafetyWarningDialog mSafetyWarning;
+
+ public VolumeDialog(Context context, VolumeDialogController controller,
+ ZenModeController zenModeController) {
+ mContext = context;
+ mController = controller;
+ mSpTexts = new SpTexts(mContext);
+ mKeyguard = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
+
+ mDialog = new CustomDialog(mContext);
+
+ final Window window = mDialog.getWindow();
+ window.requestFeature(Window.FEATURE_NO_TITLE);
+ window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+ window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+ window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+ | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+ mDialog.setCanceledOnTouchOutside(true);
+ final Resources res = mContext.getResources();
+ final WindowManager.LayoutParams lp = window.getAttributes();
+ lp.type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
+ lp.format = PixelFormat.TRANSLUCENT;
+ lp.setTitle(VolumeDialog.class.getSimpleName());
+ lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
+ lp.windowAnimations = R.style.VolumeDialogAnimations;
+ lp.y = res.getDimensionPixelSize(R.dimen.volume_offset_top);
+ lp.gravity = Gravity.TOP;
+ window.setAttributes(lp);
+
+ mDialog.setContentView(R.layout.volume_dialog);
+ mDialogView = (ViewGroup) mDialog.findViewById(R.id.volume_dialog);
+ mDialogContentView = (ViewGroup) mDialog.findViewById(R.id.volume_dialog_content);
+ mExpandButton = (ImageButton) mDialogView.findViewById(R.id.volume_expand_button);
+ mExpandButton.setOnClickListener(mClickExpand);
+ updateWindowWidthH();
+ updateExpandButtonH();
+ mLayoutTransition = new LayoutTransition();
+ mLayoutTransition.setDuration(new ValueAnimator().getDuration() / 2);
+ mLayoutTransition.disableTransitionType(LayoutTransition.DISAPPEARING);
+ mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
+ mDialogContentView.setLayoutTransition(mLayoutTransition);
+
+ addRow(AudioManager.STREAM_RING,
+ R.drawable.ic_volume_ringer, R.drawable.ic_volume_ringer_mute, true);
+ addRow(AudioManager.STREAM_MUSIC,
+ R.drawable.ic_volume_media, R.drawable.ic_volume_media_mute, true);
+ addRow(AudioManager.STREAM_ALARM,
+ R.drawable.ic_volume_alarm, R.drawable.ic_volume_alarm_mute, false);
+ addRow(AudioManager.STREAM_VOICE_CALL,
+ R.drawable.ic_volume_voice, R.drawable.ic_volume_voice, false);
+ addRow(AudioManager.STREAM_BLUETOOTH_SCO,
+ R.drawable.ic_volume_bt_sco, R.drawable.ic_volume_bt_sco, false);
+ addRow(AudioManager.STREAM_SYSTEM,
+ R.drawable.ic_volume_system, R.drawable.ic_volume_system_mute, false);
+
+ mTextFooter = mDialog.findViewById(R.id.volume_text_footer);
+ mFootlineText = (TextView) mDialog.findViewById(R.id.volume_footline_text);
+ mSpTexts.add(mFootlineText);
+ mFootlineAction = (Button) mDialog.findViewById(R.id.volume_footline_action_button);
+ mSpTexts.add(mFootlineAction);
+ mFooter = mDialog.findViewById(R.id.volume_footer);
+ mSettingsButton = mDialog.findViewById(R.id.volume_settings_button);
+ mSettingsButton.setOnClickListener(mClickSettings);
+ mExpandButtonAnimationDuration = res.getInteger(R.integer.volume_expand_animation_duration);
+ mZenFooter = (ZenFooter) mDialog.findViewById(R.id.volume_zen_footer);
+ mZenFooter.init(zenModeController, mZenFooterCallback);
+
+ controller.addCallback(mControllerCallbackH, mHandler);
+ controller.getState();
+ }
+
+ private void updateWindowWidthH() {
+ final ViewGroup.LayoutParams lp = mDialogView.getLayoutParams();
+ final DisplayMetrics dm = mContext.getResources().getDisplayMetrics();
+ if (D.BUG) Log.d(TAG, "updateWindowWidth dm.w=" + dm.widthPixels);
+ int w = dm.widthPixels;
+ final int max = mContext.getResources()
+ .getDimensionPixelSize(R.dimen.standard_notification_panel_width);
+ if (w > max) {
+ w = max;
+ }
+ w -= mContext.getResources().getDimensionPixelSize(R.dimen.notification_side_padding) * 2;
+ lp.width = w;
+ mDialogView.setLayoutParams(lp);
+ }
+
+ public void setStreamImportant(int stream, boolean important) {
+ mHandler.obtainMessage(H.SET_STREAM_IMPORTANT, stream, important ? 1 : 0).sendToTarget();
+ }
+
+ public void setShowHeaders(boolean showHeaders) {
+ if (showHeaders == mShowHeaders) return;
+ mShowHeaders = showHeaders;
+ mHandler.sendEmptyMessage(H.RECHECK_ALL);
+ }
+
+ public void setShowFooter(boolean show) {
+ if (mShowFooter == show) return;
+ mShowFooter = show;
+ mHandler.sendEmptyMessage(H.RECHECK_ALL);
+ }
+
+ public void setZenFooter(boolean zen) {
+ if (mShowZenFooter == zen) return;
+ mShowZenFooter = zen;
+ mHandler.sendEmptyMessage(H.RECHECK_ALL);
+ }
+
+ public void setAutomute(boolean automute) {
+ if (mAutomute == automute) return;
+ mAutomute = automute;
+ mHandler.sendEmptyMessage(H.RECHECK_ALL);
+ }
+
+ public void setSilentMode(boolean silentMode) {
+ if (mSilentMode == silentMode) return;
+ mSilentMode = silentMode;
+ mHandler.sendEmptyMessage(H.RECHECK_ALL);
+ }
+
+ private void addRow(int stream, int iconRes, int iconMuteRes, boolean important) {
+ final VolumeRow row = initRow(stream, iconRes, iconMuteRes, important);
+ if (!mRows.isEmpty()) {
+ final View v = new View(mContext);
+ final int h = mContext.getResources()
+ .getDimensionPixelSize(R.dimen.volume_slider_interspacing);
+ final LinearLayout.LayoutParams lp =
+ new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, h);
+ mDialogContentView.addView(v, mDialogContentView.getChildCount() - 1, lp);
+ row.space = v;
+ }
+ row.settingsButton.addOnLayoutChangeListener(new OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ if (D.BUG) Log.d(TAG, "onLayoutChange"
+ + " old=" + new Rect(oldLeft, oldTop, oldRight, oldBottom).toShortString()
+ + " new=" + new Rect(left,top,right,bottom).toShortString());
+ if (oldLeft != left || oldTop != top) {
+ for (int i = 0; i < mDialogContentView.getChildCount(); i++) {
+ final View c = mDialogContentView.getChildAt(i);
+ if (!c.isShown()) continue;
+ if (c == row.view) {
+ repositionExpandAnim(row);
+ }
+ return;
+ }
+ }
+ }
+ });
+ // add new row just before the footer
+ mDialogContentView.addView(row.view, mDialogContentView.getChildCount() - 1);
+ mRows.add(row);
+ }
+
+ private boolean isAttached() {
+ return mDialogContentView != null && mDialogContentView.isAttachedToWindow();
+ }
+
+ private VolumeRow getActiveRow() {
+ for (VolumeRow row : mRows) {
+ if (row.stream == mActiveStream) {
+ return row;
+ }
+ }
+ return mRows.get(0);
+ }
+
+ private VolumeRow findRow(int stream) {
+ for (VolumeRow row : mRows) {
+ if (row.stream == stream) return row;
+ }
+ return null;
+ }
+
+ private void repositionExpandAnim(VolumeRow row) {
+ final int[] loc = new int[2];
+ row.settingsButton.getLocationInWindow(loc);
+ final MarginLayoutParams mlp = (MarginLayoutParams) mDialogView.getLayoutParams();
+ final int x = loc[0] - mlp.leftMargin;
+ final int y = loc[1] - mlp.topMargin;
+ if (D.BUG) Log.d(TAG, "repositionExpandAnim x=" + x + " y=" + y);
+ mExpandButton.setTranslationX(x);
+ mExpandButton.setTranslationY(y);
+ }
+
+ public void dump(PrintWriter writer) {
+ writer.println(VolumeDialog.class.getSimpleName() + " state:");
+ writer.print(" mShowing: "); writer.println(mShowing);
+ writer.print(" mExpanded: "); writer.println(mExpanded);
+ writer.print(" mExpanding: "); writer.println(mExpanding);
+ writer.print(" mActiveStream: "); writer.println(mActiveStream);
+ writer.print(" mDynamic: "); writer.println(mDynamic);
+ writer.print(" mShowHeaders: "); writer.println(mShowHeaders);
+ writer.print(" mShowFooter: "); writer.println(mShowFooter);
+ writer.print(" mAutomute: "); writer.println(mAutomute);
+ writer.print(" mSilentMode: "); writer.println(mSilentMode);
+ }
+
+ private static int getImpliedLevel(SeekBar seekBar, int progress) {
+ final int m = seekBar.getMax();
+ final int n = m / 100 - 1;
+ final int level = progress == 0 ? 0
+ : progress == m ? (m / 100) : (1 + (int)((progress / (float) m) * n));
+ return level;
+ }
+
+ @SuppressLint("InflateParams")
+ private VolumeRow initRow(final int stream, int iconRes, int iconMuteRes, boolean important) {
+ final VolumeRow row = new VolumeRow();
+ row.stream = stream;
+ row.iconRes = iconRes;
+ row.iconMuteRes = iconMuteRes;
+ row.important = important;
+ row.view = mDialog.getLayoutInflater().inflate(R.layout.volume_dialog_row, null);
+ row.view.setTag(row);
+ row.header = (TextView) row.view.findViewById(R.id.volume_row_header);
+ mSpTexts.add(row.header);
+ row.slider = (SeekBar) row.view.findViewById(R.id.volume_row_slider);
+ row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row));
+
+ // forward events above the slider into the slider
+ row.view.setOnTouchListener(new OnTouchListener() {
+ private final Rect mSliderHitRect = new Rect();
+ private boolean mDragging;
+
+ @SuppressLint("ClickableViewAccessibility")
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ row.slider.getHitRect(mSliderHitRect);
+ if (!mDragging && event.getActionMasked() == MotionEvent.ACTION_DOWN
+ && event.getY() < mSliderHitRect.top) {
+ mDragging = true;
+ }
+ if (mDragging) {
+ event.offsetLocation(-mSliderHitRect.left, -mSliderHitRect.top);
+ row.slider.dispatchTouchEvent(event);
+ if (event.getActionMasked() == MotionEvent.ACTION_UP
+ || event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
+ mDragging = false;
+ }
+ return true;
+ }
+ return false;
+ }
+ });
+ row.icon = (ImageButton) row.view.findViewById(R.id.volume_row_icon);
+ row.icon.setImageResource(iconRes);
+ row.icon.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Events.writeEvent(Events.EVENT_ICON_CLICK, row.stream, row.iconState);
+ mController.setActiveStream(row.stream);
+ if (row.stream == AudioManager.STREAM_RING) {
+ final boolean hasVibrator = mController.hasVibrator();
+ if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) {
+ if (hasVibrator) {
+ mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, false);
+ } else {
+ final boolean wasZero = row.ss.level == 0;
+ mController.setStreamVolume(stream, wasZero ? row.lastAudibleLevel : 0);
+ }
+ } else {
+ mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false);
+ if (row.ss.level == 0) {
+ mController.setStreamVolume(stream, 1);
+ }
+ }
+ } else {
+ if (mAutomute && !row.ss.muteSupported) {
+ final boolean vmute = row.ss.level == 0;
+ mController.setStreamVolume(stream, vmute ? row.lastAudibleLevel : 0);
+ } else {
+ final boolean mute = !row.ss.muted;
+ mController.setStreamMute(stream, mute);
+ if (mAutomute) {
+ if (!mute && row.ss.level == 0) {
+ mController.setStreamVolume(stream, 1);
+ }
+ }
+ }
+ }
+ row.userAttempt = 0; // reset the grace period, slider should update immediately
+ }
+ });
+ row.settingsButton = (ImageButton) row.view.findViewById(R.id.volume_settings_button);
+ row.settingsButton.setOnClickListener(mClickSettings);
+ return row;
+ }
+
+ public void destroy() {
+ mController.removeCallback(mControllerCallbackH);
+ }
+
+ public void show(int reason) {
+ mHandler.obtainMessage(H.SHOW, reason, 0).sendToTarget();
+ }
+
+ public void dismiss(int reason) {
+ mHandler.obtainMessage(H.DISMISS, reason, 0).sendToTarget();
+ }
+
+ protected void onSettingsClickedH() {
+ // hook for subclasses
+ }
+
+ protected void onZenSettingsClickedH() {
+ // hook for subclasses
+ }
+
+ private void showH(int reason) {
+ mHandler.removeMessages(H.SHOW);
+ mHandler.removeMessages(H.DISMISS);
+ rescheduleTimeoutH();
+ if (mShowing) return;
+ mShowing = true;
+ mDialog.show();
+ Events.writeEvent(Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked());
+ mController.notifyVisible(true);
+ }
+
+ protected void rescheduleTimeoutH() {
+ mHandler.removeMessages(H.DISMISS);
+ final int timeout = computeTimeoutH();
+ if (D.BUG) Log.d(TAG, "rescheduleTimeout " + timeout);
+ mHandler.sendMessageDelayed(mHandler
+ .obtainMessage(H.DISMISS, Events.DISMISS_REASON_TIMEOUT, 0), timeout);
+ mController.userActivity();
+ }
+
+ private int computeTimeoutH() {
+ if (mZenFooter != null && mZenFooter.isFooterExpanded()) return 10000;
+ if (mSafetyWarning != null) return 5000;
+ if (mExpanded || mExpanding) return 5000;
+ if (mActiveStream == AudioManager.STREAM_MUSIC) return 1500;
+ return 3000;
+ }
+
+ protected void dismissH(int reason) {
+ mHandler.removeMessages(H.DISMISS);
+ mHandler.removeMessages(H.SHOW);
+ if (!mShowing) return;
+ mShowing = false;
+ mDialog.dismiss();
+ Events.writeEvent(Events.EVENT_DISMISS_DIALOG, reason);
+ setExpandedH(false);
+ mController.notifyVisible(false);
+ synchronized (mSafetyWarningLock) {
+ if (mSafetyWarning != null) {
+ if (D.BUG) Log.d(TAG, "SafetyWarning dismissed");
+ mSafetyWarning.dismiss();
+ }
+ }
+ }
+
+ private void setExpandedH(boolean expanded) {
+ if (mExpanded == expanded) return;
+ mExpanded = expanded;
+ mExpanding = isAttached();
+ if (D.BUG) Log.d(TAG, "setExpandedH " + expanded);
+ updateRowsH();
+ if (mExpanding) {
+ final Drawable d = mExpandButton.getDrawable();
+ if (d instanceof AnimatedVectorDrawable) {
+ // workaround to reset drawable
+ final AnimatedVectorDrawable avd = (AnimatedVectorDrawable) d.getConstantState()
+ .newDrawable();
+ mExpandButton.setImageDrawable(avd);
+ avd.start();
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mExpanding = false;
+ updateExpandButtonH();
+ rescheduleTimeoutH();
+ }
+ }, mExpandButtonAnimationDuration);
+ }
+ }
+ rescheduleTimeoutH();
+ }
+
+ private void updateExpandButtonH() {
+ mExpandButton.setClickable(!mExpanding);
+ if (mExpanding && isAttached()) return;
+ final int res = mExpanded ? R.drawable.ic_volume_collapse_animation
+ : R.drawable.ic_volume_expand_animation;
+ if (res == mExpandButtonRes) return;
+ mExpandButtonRes = res;
+ mExpandButton.setImageResource(res);
+ }
+
+ private boolean isVisibleH(VolumeRow row, boolean isActive) {
+ return mExpanded && row.view.getVisibility() == View.VISIBLE
+ || (mExpanded && (row.important || isActive))
+ || !mExpanded && isActive;
+ }
+
+ private void updateRowsH() {
+ final VolumeRow activeRow = getActiveRow();
+ updateFooterH();
+ updateExpandButtonH();
+ final boolean footerVisible = mFooter.getVisibility() == View.VISIBLE;
+ if (!mShowing) {
+ trimObsoleteH();
+ }
+ // first, find the last visible row
+ VolumeRow lastVisible = null;
+ for (VolumeRow row : mRows) {
+ final boolean isActive = row == activeRow;
+ if (isVisibleH(row, isActive)) {
+ lastVisible = row;
+ }
+ }
+ // apply changes to all rows
+ for (VolumeRow row : mRows) {
+ final boolean isActive = row == activeRow;
+ final boolean visible = isVisibleH(row, isActive);
+ Util.setVisOrGone(row.view, visible);
+ Util.setVisOrGone(row.space, visible && mExpanded);
+ final int expandButtonRes = mExpanded ? R.drawable.ic_volume_settings : 0;
+ if (expandButtonRes != row.cachedExpandButtonRes) {
+ row.cachedExpandButtonRes = expandButtonRes;
+ if (expandButtonRes == 0) {
+ row.settingsButton.setImageDrawable(null);
+ } else {
+ row.settingsButton.setImageResource(expandButtonRes);
+ }
+ }
+ Util.setVisOrInvis(row.settingsButton,
+ mExpanded && (!footerVisible && row == lastVisible));
+ row.header.setAlpha(mExpanded && isActive ? 1 : 0.5f);
+ }
+ }
+
+ private void trimObsoleteH() {
+ for (int i = mRows.size() -1; i >= 0; i--) {
+ final VolumeRow row = mRows.get(i);
+ if (row.ss == null || !row.ss.dynamic) continue;
+ if (!mDynamic.get(row.stream)) {
+ mRows.remove(i);
+ mDialogContentView.removeView(row.view);
+ mDialogContentView.removeView(row.space);
+ }
+ }
+ }
+
+ private void onStateChangedH(State state) {
+ mState = state;
+ mDynamic.clear();
+ // add any new dynamic rows
+ for (int i = 0; i < state.states.size(); i++) {
+ final int stream = state.states.keyAt(i);
+ final StreamState ss = state.states.valueAt(i);
+ if (!ss.dynamic) continue;
+ mDynamic.put(stream, true);
+ if (findRow(stream) == null) {
+ addRow(stream, R.drawable.ic_volume_remote, R.drawable.ic_volume_remote_mute, true);
+ }
+ }
+
+ if (mActiveStream != state.activeStream) {
+ mActiveStream = state.activeStream;
+ updateRowsH();
+ rescheduleTimeoutH();
+ }
+ for (VolumeRow row : mRows) {
+ updateVolumeRowH(row);
+ }
+ updateFooterH();
+ }
+
+ private void updateTextFooterH() {
+ final boolean zen = mState.zenMode != Global.ZEN_MODE_OFF;
+ final boolean wasVisible = mFooter.getVisibility() == View.VISIBLE;
+ Util.setVisOrGone(mTextFooter, mExpanded && mShowFooter && (zen || mShowing && wasVisible));
+ if (mTextFooter.getVisibility() == View.VISIBLE) {
+ String text = null;
+ String action = null;
+ if (mState.exitCondition != null) {
+ final long countdown = ZenModeConfig.tryParseCountdownConditionId(mState
+ .exitCondition.id);
+ if (countdown != 0) {
+ text = mContext.getString(R.string.volume_dnd_ends_at,
+ Util.getShortTime(countdown));
+ action = mContext.getString(R.string.volume_end_now);
+ } else {
+ final DowntimeInfo info = ZenModeConfig.tryParseDowntimeConditionId(mState.
+ exitCondition.id);
+ if (info != null) {
+ text = mContext.getString(R.string.volume_dnd_ends_at,
+ Util.getShortTime(info));
+ action = mContext.getString(R.string.volume_end_now);
+ }
+ }
+ }
+ if (text == null) {
+ text = mContext.getString(R.string.volume_dnd_is_on);
+ }
+ if (action == null) {
+ action = mContext.getString(R.string.volume_turn_off);
+ }
+ Util.setText(mFootlineText, text);
+ Util.setText(mFootlineAction, action);
+ mFootlineAction.setOnClickListener(mTurnOffDnd);
+ }
+ Util.setVisOrGone(mFootlineText, zen);
+ Util.setVisOrGone(mFootlineAction, zen);
+ }
+
+ private void updateFooterH() {
+ if (!mShowFooter) {
+ Util.setVisOrGone(mFooter, false);
+ return;
+ }
+ if (mShowZenFooter) {
+ Util.setVisOrGone(mTextFooter, false);
+ final boolean ringActive = mActiveStream == AudioManager.STREAM_RING;
+ Util.setVisOrGone(mZenFooter, mZenFooter.isZen() && ringActive
+ || mShowing && (mExpanded || mZenFooter.getVisibility() == View.VISIBLE));
+ mZenFooter.update();
+ } else {
+ Util.setVisOrGone(mZenFooter, false);
+ updateTextFooterH();
+ }
+ }
+
+ private void updateVolumeRowH(VolumeRow row) {
+ if (mState == null) return;
+ final StreamState ss = mState.states.get(row.stream);
+ if (ss == null) return;
+ row.ss = ss;
+ if (ss.level > 0) {
+ row.lastAudibleLevel = ss.level;
+ }
+ final boolean isRingStream = row.stream == AudioManager.STREAM_RING;
+ final boolean isSystemStream = row.stream == AudioManager.STREAM_SYSTEM;
+ final boolean isRingVibrate = isRingStream
+ && mState.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE;
+ final boolean isNoned = (isRingStream || isSystemStream)
+ && mState.zenMode == Global.ZEN_MODE_NO_INTERRUPTIONS;
+ final boolean isLimited = isRingStream
+ && mState.zenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+
+ // update slider max
+ final int max = ss.levelMax * 100;
+ if (max != row.slider.getMax()) {
+ row.slider.setMax(max);
+ }
+
+ // update header visible
+ if (row.cachedShowHeaders != mShowHeaders) {
+ row.cachedShowHeaders = mShowHeaders;
+ Util.setVisOrGone(row.header, mShowHeaders);
+ }
+
+ // update header text
+ final String text;
+ if (isNoned) {
+ text = mContext.getString(R.string.volume_stream_muted_dnd, ss.name);
+ } else if (isRingVibrate && isLimited) {
+ text = mContext.getString(R.string.volume_stream_vibrate_dnd, ss.name);
+ } else if (isRingVibrate) {
+ text = mContext.getString(R.string.volume_stream_vibrate, ss.name);
+ } else if (ss.muted || mAutomute && ss.level == 0) {
+ text = mContext.getString(R.string.volume_stream_muted, ss.name);
+ } else if (isLimited) {
+ text = mContext.getString(R.string.volume_stream_limited_dnd, ss.name);
+ } else {
+ text = ss.name;
+ }
+ Util.setText(row.header, text);
+
+ // update icon
+ final boolean iconEnabled = mAutomute || ss.muteSupported;
+ row.icon.setEnabled(iconEnabled);
+ row.icon.setAlpha(iconEnabled ? 1 : 0.5f);
+ final int iconRes =
+ isRingVibrate ? R.drawable.ic_volume_ringer_vibrate
+ : ss.routedToBluetooth ?
+ (ss.muted ? R.drawable.ic_volume_bt_mute : R.drawable.ic_volume_bt)
+ : mAutomute && ss.level == 0 ? row.iconMuteRes
+ : (ss.muted ? row.iconMuteRes : row.iconRes);
+ if (iconRes != row.cachedIconRes) {
+ if (row.cachedIconRes != 0 && isRingVibrate) {
+ mController.vibrate();
+ }
+ row.cachedIconRes = iconRes;
+ row.icon.setImageResource(iconRes);
+ }
+ row.iconState =
+ iconRes == R.drawable.ic_volume_ringer_vibrate ? Events.ICON_STATE_VIBRATE
+ : (iconRes == R.drawable.ic_volume_bt_mute || iconRes == row.iconMuteRes)
+ ? Events.ICON_STATE_MUTE
+ : (iconRes == R.drawable.ic_volume_bt || iconRes == row.iconRes)
+ ? Events.ICON_STATE_UNMUTE
+ : Events.ICON_STATE_UNKNOWN;
+
+ // update slider
+ updateVolumeRowSliderH(row);
+ }
+
+ private void updateVolumeRowSliderH(VolumeRow row) {
+ if (row.tracking) {
+ return; // don't update if user is sliding
+ }
+ final int progress = row.slider.getProgress();
+ final int level = getImpliedLevel(row.slider, progress);
+ final boolean rowVisible = row.view.getVisibility() == View.VISIBLE;
+ final boolean inGracePeriod = (SystemClock.uptimeMillis() - row.userAttempt)
+ < USER_ATTEMPT_GRACE_PERIOD;
+ mHandler.removeMessages(H.RECHECK, row);
+ if (mShowing && rowVisible && inGracePeriod) {
+ if (D.BUG) Log.d(TAG, "inGracePeriod");
+ mHandler.sendMessageAtTime(mHandler.obtainMessage(H.RECHECK, row),
+ row.userAttempt + USER_ATTEMPT_GRACE_PERIOD);
+ return; // don't update if visible and in grace period
+ }
+ final int vlevel = row.ss.muted ? 0 : row.ss.level;
+ if (vlevel == level) {
+ if (mShowing && rowVisible) {
+ return; // don't clamp if visible
+ }
+ }
+ final int newProgress = vlevel * 100;
+ if (progress != newProgress) {
+ if (mShowing && rowVisible) {
+ // animate!
+ if (row.anim != null && row.anim.isRunning()
+ && row.animTargetProgress == newProgress) {
+ return; // already animating to the target progress
+ }
+ // start/update animation
+ if (row.anim == null) {
+ row.anim = ObjectAnimator.ofInt(row.slider, "progress", progress, newProgress);
+ row.anim.setInterpolator(new DecelerateInterpolator());
+ } else {
+ row.anim.cancel();
+ row.anim.setIntValues(progress, newProgress);
+ }
+ row.animTargetProgress = newProgress;
+ row.anim.setDuration(UPDATE_ANIMATION_DURATION);
+ row.anim.start();
+ } else {
+ // update slider directly to clamped value
+ if (row.anim != null) {
+ row.anim.cancel();
+ }
+ row.slider.setProgress(newProgress);
+ }
+ if (mAutomute) {
+ if (vlevel == 0 && !row.ss.muted && row.stream == AudioManager.STREAM_MUSIC) {
+ mController.setStreamMute(row.stream, true);
+ }
+ }
+ }
+ }
+
+ private void recheckH(VolumeRow row) {
+ if (row == null) {
+ if (D.BUG) Log.d(TAG, "recheckH ALL");
+ trimObsoleteH();
+ for (VolumeRow r : mRows) {
+ updateVolumeRowH(r);
+ }
+ } else {
+ if (D.BUG) Log.d(TAG, "recheckH " + row.stream);
+ updateVolumeRowH(row);
+ }
+ }
+
+ private void setStreamImportantH(int stream, boolean important) {
+ for (VolumeRow row : mRows) {
+ if (row.stream == stream) {
+ row.important = important;
+ return;
+ }
+ }
+ }
+
+ private void showSafetyWarningH(int flags) {
+ if ((flags & (AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_SHOW_UI_WARNINGS)) != 0
+ || mShowing) {
+ synchronized (mSafetyWarningLock) {
+ if (mSafetyWarning != null) {
+ return;
+ }
+ mSafetyWarning = new SafetyWarningDialog(mContext, mController.getAudioManager()) {
+ @Override
+ protected void cleanUp() {
+ synchronized (mSafetyWarningLock) {
+ mSafetyWarning = null;
+ }
+ recheckH(null);
+ }
+ };
+ mSafetyWarning.show();
+ }
+ recheckH(null);
+ }
+ rescheduleTimeoutH();
+ }
+
+ private final VolumeDialogController.Callbacks mControllerCallbackH
+ = new VolumeDialogController.Callbacks() {
+ @Override
+ public void onShowRequested(int reason) {
+ showH(reason);
+ }
+
+ @Override
+ public void onDismissRequested(int reason) {
+ dismissH(reason);
+ }
+
+ @Override
+ public void onScreenOff() {
+ dismissH(Events.DISMISS_REASON_SCREEN_OFF);
+ }
+
+ @Override
+ public void onStateChanged(State state) {
+ onStateChangedH(state);
+ }
+
+ @Override
+ public void onLayoutDirectionChanged(int layoutDirection) {
+ mDialogView.setLayoutDirection(layoutDirection);
+ }
+
+ @Override
+ public void onConfigurationChanged() {
+ updateWindowWidthH();
+ mSpTexts.update();
+ }
+
+ @Override
+ public void onShowVibrateHint() {
+ if (mSilentMode) {
+ mController.setRingerMode(AudioManager.RINGER_MODE_SILENT, false);
+ }
+ }
+
+ @Override
+ public void onShowSilentHint() {
+ if (mSilentMode) {
+ mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false);
+ }
+ }
+
+ @Override
+ public void onShowSafetyWarning(int flags) {
+ showSafetyWarningH(flags);
+ }
+ };
+
+ private final OnClickListener mClickExpand = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mExpanding) return;
+ final boolean newExpand = !mExpanded;
+ Events.writeEvent(Events.EVENT_EXPAND, v);
+ setExpandedH(newExpand);
+ }
+ };
+
+ private final OnClickListener mClickSettings = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mSettingsButton.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ Events.writeEvent(Events.EVENT_SETTINGS_CLICK);
+ onSettingsClickedH();
+ }
+ }, WAIT_FOR_RIPPLE);
+ }
+ };
+
+ private final View.OnClickListener mTurnOffDnd = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mSettingsButton.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mController.setZenMode(Global.ZEN_MODE_OFF);
+ }
+ }, WAIT_FOR_RIPPLE);
+ }
+ };
+
+ private final ZenFooter.Callback mZenFooterCallback = new ZenFooter.Callback() {
+ @Override
+ public void onFooterExpanded() {
+ mHandler.sendEmptyMessage(H.RESCHEDULE_TIMEOUT);
+ }
+
+ @Override
+ public void onSettingsClicked() {
+ dismiss(Events.DISMISS_REASON_SETTINGS_CLICKED);
+ onZenSettingsClickedH();
+ }
+
+ @Override
+ public void onDoneClicked() {
+ dismiss(Events.DISMISS_REASON_DONE_CLICKED);
+ }
+ };
+
+ private final class H extends Handler {
+ private static final int SHOW = 1;
+ private static final int DISMISS = 2;
+ private static final int RECHECK = 3;
+ private static final int RECHECK_ALL = 4;
+ private static final int SET_STREAM_IMPORTANT = 5;
+ private static final int RESCHEDULE_TIMEOUT = 6;
+
+ public H() {
+ super(Looper.getMainLooper());
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case SHOW: showH(msg.arg1); break;
+ case DISMISS: dismissH(msg.arg1); break;
+ case RECHECK: recheckH((VolumeRow) msg.obj); break;
+ case RECHECK_ALL: recheckH(null); break;
+ case SET_STREAM_IMPORTANT: setStreamImportantH(msg.arg1, msg.arg2 != 0); break;
+ case RESCHEDULE_TIMEOUT: rescheduleTimeoutH(); break;
+ }
+ }
+ }
+
+ private final class CustomDialog extends Dialog {
+ public CustomDialog(Context context) {
+ super(context);
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ rescheduleTimeoutH();
+ return super.dispatchTouchEvent(ev);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ mHandler.sendEmptyMessage(H.RECHECK_ALL);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (isShowing()) {
+ if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
+ dismissH(Events.DISMISS_REASON_TOUCH_OUTSIDE);
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ private final class VolumeSeekBarChangeListener implements OnSeekBarChangeListener {
+ private final VolumeRow mRow;
+
+ private VolumeSeekBarChangeListener(VolumeRow row) {
+ mRow = row;
+ }
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (mRow.ss == null) return;
+ if (D.BUG) Log.d(TAG, AudioSystem.streamToString(mRow.stream)
+ + " onProgressChanged " + progress + " fromUser=" + fromUser);
+ if (!fromUser) return;
+ if (mRow.ss.levelMin > 0) {
+ final int minProgress = mRow.ss.levelMin * 100;
+ if (progress < minProgress) {
+ seekBar.setProgress(minProgress);
+ }
+ }
+ final int userLevel = getImpliedLevel(seekBar, progress);
+ if (mRow.ss.level != userLevel || mRow.ss.muted && userLevel > 0) {
+ mRow.userAttempt = SystemClock.uptimeMillis();
+ if (mAutomute) {
+ if (mRow.stream != AudioManager.STREAM_RING) {
+ if (userLevel > 0 && mRow.ss.muted) {
+ mController.setStreamMute(mRow.stream, false);
+ }
+ if (userLevel == 0 && mRow.ss.muteSupported && !mRow.ss.muted) {
+ mController.setStreamMute(mRow.stream, true);
+ }
+ }
+ }
+ if (mRow.requestedLevel != userLevel) {
+ mController.setStreamVolume(mRow.stream, userLevel);
+ mRow.requestedLevel = userLevel;
+ Events.writeEvent(Events.EVENT_TOUCH_LEVEL_CHANGED, mRow.stream, userLevel);
+ }
+ }
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ if (D.BUG) Log.d(TAG, "onStartTrackingTouch"+ " " + mRow.stream);
+ mController.setActiveStream(mRow.stream);
+ mRow.tracking = true;
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ if (D.BUG) Log.d(TAG, "onStopTrackingTouch"+ " " + mRow.stream);
+ mRow.tracking = false;
+ mRow.userAttempt = SystemClock.uptimeMillis();
+ int userLevel = getImpliedLevel(seekBar, seekBar.getProgress());
+ if (mRow.ss.level != userLevel) {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(H.RECHECK, mRow),
+ USER_ATTEMPT_GRACE_PERIOD);
+ }
+ }
+ }
+
+ private static class VolumeRow {
+ private View view;
+ private View space;
+ private TextView header;
+ private ImageButton icon;
+ private SeekBar slider;
+ private ImageButton settingsButton;
+ private int stream;
+ private StreamState ss;
+ private long userAttempt; // last user-driven slider change
+ private boolean tracking; // tracking slider touch
+ private int requestedLevel;
+ private int iconRes;
+ private int iconMuteRes;
+ private boolean important;
+ private int cachedIconRes;
+ private int iconState; // from Events
+ private boolean cachedShowHeaders = Prefs.DEFAULT_SHOW_HEADERS;
+ private int cachedExpandButtonRes;
+ private ObjectAnimator anim; // slider progress animation for non-touch-related updates
+ private int animTargetProgress;
+ private int lastAudibleLevel = 1;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
new file mode 100644
index 0000000..741e498
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.media.AudioManager;
+import android.media.VolumePolicy;
+import android.os.Bundle;
+import android.os.Handler;
+
+import com.android.systemui.SystemUI;
+import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.qs.tiles.DndTile;
+import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.statusbar.policy.ZenModeController;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Implementation of VolumeComponent backed by the new volume dialog.
+ */
+public class VolumeDialogComponent implements VolumeComponent {
+ private final SystemUI mSysui;
+ private final Context mContext;
+ private final VolumeDialogController mController;
+ private final ZenModeController mZenModeController;
+ private final VolumeDialog mDialog;
+
+ public VolumeDialogComponent(SystemUI sysui, Context context, Handler handler,
+ ZenModeController zen) {
+ mSysui = sysui;
+ mContext = context;
+ mController = new VolumeDialogController(context, null) {
+ @Override
+ protected void onUserActivityW() {
+ sendUserActivity();
+ }
+ };
+ mZenModeController = zen;
+ mDialog = new VolumeDialog(context, mController, zen) {
+ @Override
+ protected void onZenSettingsClickedH() {
+ startZenSettings();
+ }
+ };
+ applyConfiguration();
+ }
+
+ private void sendUserActivity() {
+ final KeyguardViewMediator kvm = mSysui.getComponent(KeyguardViewMediator.class);
+ if (kvm != null) {
+ kvm.userActivity();
+ }
+ }
+
+ private void applyConfiguration() {
+ mDialog.setStreamImportant(AudioManager.STREAM_ALARM, true);
+ mDialog.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
+ mDialog.setShowHeaders(false);
+ mDialog.setShowFooter(true);
+ mDialog.setZenFooter(true);
+ mDialog.setAutomute(true);
+ mDialog.setSilentMode(false);
+ mController.setVolumePolicy(VolumePolicy.DEFAULT);
+ mController.showDndTile(false);
+ }
+
+ @Override
+ public ZenModeController getZenController() {
+ return mZenModeController;
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ // noop
+ }
+
+ @Override
+ public void dismissNow() {
+ mController.dismiss();
+ }
+
+ @Override
+ public void dispatchDemoCommand(String command, Bundle args) {
+ // noop
+ }
+
+ @Override
+ public void register() {
+ mController.register();
+ DndTile.setCombinedIcon(mContext, true);
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mController.dump(fd, pw, args);
+ mDialog.dump(pw);
+ }
+
+ private void startZenSettings() {
+ mSysui.getComponent(PhoneStatusBar.class).startActivityDismissingKeyguard(
+ ZenModePanel.ZEN_SETTINGS, true /* onlyProvisioned */, true /* dismissShade */);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
new file mode 100644
index 0000000..a3d9377
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
@@ -0,0 +1,988 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume;
+
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.database.ContentObserver;
+import android.media.AudioManager;
+import android.media.AudioSystem;
+import android.media.IVolumeController;
+import android.media.VolumePolicy;
+import android.media.session.MediaController.PlaybackInfo;
+import android.media.session.MediaSession.Token;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.Vibrator;
+import android.provider.Settings;
+import android.service.notification.Condition;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.systemui.R;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Source of truth for all state / events related to the volume dialog. No presentation.
+ *
+ * All work done on a dedicated background worker thread & associated worker.
+ *
+ * Methods ending in "W" must be called on the worker thread.
+ */
+public class VolumeDialogController {
+ private static final String TAG = Util.logTag(VolumeDialogController.class);
+
+ private static final int DYNAMIC_STREAM_START_INDEX = 100;
+ private static final int VIBRATE_HINT_DURATION = 50;
+
+ private static final int[] STREAMS = {
+ AudioSystem.STREAM_ALARM,
+ AudioSystem.STREAM_BLUETOOTH_SCO,
+ AudioSystem.STREAM_DTMF,
+ AudioSystem.STREAM_MUSIC,
+ AudioSystem.STREAM_NOTIFICATION,
+ AudioSystem.STREAM_RING,
+ AudioSystem.STREAM_SYSTEM,
+ AudioSystem.STREAM_SYSTEM_ENFORCED,
+ AudioSystem.STREAM_TTS,
+ AudioSystem.STREAM_VOICE_CALL,
+ };
+
+ private final HandlerThread mWorkerThread;
+ private final W mWorker;
+ private final Context mContext;
+ private final AudioManager mAudio;
+ private final NotificationManager mNoMan;
+ private final ComponentName mComponent;
+ private final SettingObserver mObserver;
+ private final Receiver mReceiver = new Receiver();
+ private final MediaSessions mMediaSessions;
+ private final VC mVolumeController = new VC();
+ private final C mCallbacks = new C();
+ private final State mState = new State();
+ private final String[] mStreamTitles;
+ private final MediaSessionsCallbacks mMediaSessionsCallbacksW = new MediaSessionsCallbacks();
+ private final Vibrator mVibrator;
+ private final boolean mHasVibrator;
+
+ private boolean mEnabled;
+ private boolean mDestroyed;
+ private VolumePolicy mVolumePolicy = new VolumePolicy(true, true, false, 400);
+ private boolean mShowDndTile = false;
+
+ public VolumeDialogController(Context context, ComponentName component) {
+ mContext = context.getApplicationContext();
+ Events.writeEvent(Events.EVENT_COLLECTION_STARTED);
+ mComponent = component;
+ mWorkerThread = new HandlerThread(VolumeDialogController.class.getSimpleName());
+ mWorkerThread.start();
+ mWorker = new W(mWorkerThread.getLooper());
+ mMediaSessions = createMediaSessions(mContext, mWorkerThread.getLooper(),
+ mMediaSessionsCallbacksW);
+ mAudio = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ mNoMan = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ mObserver = new SettingObserver(mWorker);
+ mObserver.init();
+ mReceiver.init();
+ mStreamTitles = mContext.getResources().getStringArray(R.array.volume_stream_titles);
+ mVibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
+ mHasVibrator = mVibrator != null && mVibrator.hasVibrator();
+ }
+
+ public AudioManager getAudioManager() {
+ return mAudio;
+ }
+
+ public void dismiss() {
+ mCallbacks.onDismissRequested(Events.DISMISS_REASON_VOLUME_CONTROLLER);
+ }
+
+ public void register() {
+ try {
+ mAudio.setVolumeController(mVolumeController);
+ } catch (SecurityException e) {
+ Log.w(TAG, "Unable to set the volume controller", e);
+ return;
+ }
+ setVolumePolicy(mVolumePolicy);
+ showDndTile(mShowDndTile);
+ try {
+ mMediaSessions.init();
+ } catch (SecurityException e) {
+ Log.w(TAG, "No access to media sessions", e);
+ }
+ }
+
+ public void setVolumePolicy(VolumePolicy policy) {
+ mVolumePolicy = policy;
+ try {
+ mAudio.setVolumePolicy(mVolumePolicy);
+ } catch (NoSuchMethodError e) {
+ Log.w(TAG, "No volume policy api");
+ }
+ }
+
+ protected MediaSessions createMediaSessions(Context context, Looper looper,
+ MediaSessions.Callbacks callbacks) {
+ return new MediaSessions(context, looper, callbacks);
+ }
+
+ public void destroy() {
+ if (D.BUG) Log.d(TAG, "destroy");
+ if (mDestroyed) return;
+ mDestroyed = true;
+ Events.writeEvent(Events.EVENT_COLLECTION_STOPPED);
+ mMediaSessions.destroy();
+ mObserver.destroy();
+ mReceiver.destroy();
+ mWorkerThread.quitSafely();
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println(VolumeDialogController.class.getSimpleName() + " state:");
+ pw.print(" mEnabled: "); pw.println(mEnabled);
+ pw.print(" mDestroyed: "); pw.println(mDestroyed);
+ pw.print(" mVolumePolicy: "); pw.println(mVolumePolicy);
+ pw.print(" mEnabled: "); pw.println(mEnabled);
+ pw.print(" mShowDndTile: "); pw.println(mShowDndTile);
+ pw.print(" mHasVibrator: "); pw.println(mHasVibrator);
+ pw.print(" mRemoteStreams: "); pw.println(mMediaSessionsCallbacksW.mRemoteStreams
+ .values());
+ pw.println();
+ mMediaSessions.dump(pw);
+ }
+
+ public void addCallback(Callbacks callback, Handler handler) {
+ mCallbacks.add(callback, handler);
+ }
+
+ public void removeCallback(Callbacks callback) {
+ mCallbacks.remove(callback);
+ }
+
+ public void getState() {
+ if (mDestroyed) return;
+ mWorker.sendEmptyMessage(W.GET_STATE);
+ }
+
+ public void notifyVisible(boolean visible) {
+ if (mDestroyed) return;
+ mWorker.obtainMessage(W.NOTIFY_VISIBLE, visible ? 1 : 0, 0).sendToTarget();
+ }
+
+ public void userActivity() {
+ if (mDestroyed) return;
+ mWorker.removeMessages(W.USER_ACTIVITY);
+ mWorker.sendEmptyMessage(W.USER_ACTIVITY);
+ }
+
+ public void setRingerMode(int value, boolean external) {
+ if (mDestroyed) return;
+ mWorker.obtainMessage(W.SET_RINGER_MODE, value, external ? 1 : 0).sendToTarget();
+ }
+
+ public void setZenMode(int value) {
+ if (mDestroyed) return;
+ mWorker.obtainMessage(W.SET_ZEN_MODE, value, 0).sendToTarget();
+ }
+
+ public void setExitCondition(Condition condition) {
+ if (mDestroyed) return;
+ mWorker.obtainMessage(W.SET_EXIT_CONDITION, condition).sendToTarget();
+ }
+
+ public void setStreamMute(int stream, boolean mute) {
+ if (mDestroyed) return;
+ mWorker.obtainMessage(W.SET_STREAM_MUTE, stream, mute ? 1 : 0).sendToTarget();
+ }
+
+ public void setStreamVolume(int stream, int level) {
+ if (mDestroyed) return;
+ mWorker.obtainMessage(W.SET_STREAM_VOLUME, stream, level).sendToTarget();
+ }
+
+ public void setActiveStream(int stream) {
+ if (mDestroyed) return;
+ mWorker.obtainMessage(W.SET_ACTIVE_STREAM, stream, 0).sendToTarget();
+ }
+
+ public void vibrate() {
+ if (mHasVibrator) {
+ mVibrator.vibrate(VIBRATE_HINT_DURATION);
+ }
+ }
+
+ public boolean hasVibrator() {
+ return mHasVibrator;
+ }
+
+ private void onNotifyVisibleW(boolean visible) {
+ if (mDestroyed) return;
+ mAudio.notifyVolumeControllerVisible(mVolumeController, visible);
+ if (!visible) {
+ if (updateActiveStreamW(-1)) {
+ mCallbacks.onStateChanged(mState);
+ }
+ }
+ }
+
+ protected void onUserActivityW() {
+ // hook for subclasses
+ }
+
+ private void onShowSafetyWarningW(int flags) {
+ mCallbacks.onShowSafetyWarning(flags);
+ }
+
+ private boolean checkRoutedToBluetoothW(int stream) {
+ boolean changed = false;
+ if (stream == AudioManager.STREAM_MUSIC) {
+ final boolean routedToBluetooth =
+ (mAudio.getDevicesForStream(AudioManager.STREAM_MUSIC) &
+ (AudioManager.DEVICE_OUT_BLUETOOTH_A2DP |
+ AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
+ AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER)) != 0;
+ changed |= updateStreamRoutedToBluetoothW(stream, routedToBluetooth);
+ }
+ return changed;
+ }
+
+ private void onVolumeChangedW(int stream, int flags) {
+ final boolean showUI = (flags & AudioManager.FLAG_SHOW_UI) != 0;
+ final boolean fromKey = (flags & AudioManager.FLAG_FROM_KEY) != 0;
+ final boolean showVibrateHint = (flags & AudioManager.FLAG_SHOW_VIBRATE_HINT) != 0;
+ final boolean showSilentHint = (flags & AudioManager.FLAG_SHOW_SILENT_HINT) != 0;
+ boolean changed = false;
+ if (showUI) {
+ changed |= updateActiveStreamW(stream);
+ }
+ changed |= updateStreamLevelW(stream, mAudio.getLastAudibleStreamVolume(stream));
+ changed |= checkRoutedToBluetoothW(showUI ? AudioManager.STREAM_MUSIC : stream);
+ if (changed) {
+ mCallbacks.onStateChanged(mState);
+ }
+ if (showUI) {
+ mCallbacks.onShowRequested(Events.SHOW_REASON_VOLUME_CHANGED);
+ }
+ if (showVibrateHint) {
+ mCallbacks.onShowVibrateHint();
+ }
+ if (showSilentHint) {
+ mCallbacks.onShowSilentHint();
+ }
+ if (changed && fromKey) {
+ Events.writeEvent(Events.EVENT_KEY);
+ }
+ }
+
+ private boolean updateActiveStreamW(int activeStream) {
+ if (activeStream == mState.activeStream) return false;
+ mState.activeStream = activeStream;
+ Events.writeEvent(Events.EVENT_ACTIVE_STREAM_CHANGED, activeStream);
+ if (D.BUG) Log.d(TAG, "updateActiveStreamW " + activeStream);
+ final int s = activeStream < DYNAMIC_STREAM_START_INDEX ? activeStream : -1;
+ if (D.BUG) Log.d(TAG, "forceVolumeControlStream " + s);
+ mAudio.forceVolumeControlStream(s);
+ return true;
+ }
+
+ private StreamState streamStateW(int stream) {
+ StreamState ss = mState.states.get(stream);
+ if (ss == null) {
+ ss = new StreamState();
+ mState.states.put(stream, ss);
+ }
+ return ss;
+ }
+
+ private void onGetStateW() {
+ for (int stream : STREAMS) {
+ updateStreamLevelW(stream, mAudio.getLastAudibleStreamVolume(stream));
+ streamStateW(stream).levelMin = mAudio.getStreamMinVolume(stream);
+ streamStateW(stream).levelMax = mAudio.getStreamMaxVolume(stream);
+ updateStreamMuteW(stream, mAudio.isStreamMute(stream));
+ final StreamState ss = streamStateW(stream);
+ ss.muteSupported = mAudio.isStreamAffectedByMute(stream);
+ ss.name = mStreamTitles[stream];
+ checkRoutedToBluetoothW(stream);
+ }
+ updateRingerModeExternalW(mAudio.getRingerMode());
+ updateZenModeW();
+ updateEffectsSuppressorW(mNoMan.getEffectsSuppressor());
+ updateExitConditionW();
+ mCallbacks.onStateChanged(mState);
+ }
+
+ private boolean updateStreamRoutedToBluetoothW(int stream, boolean routedToBluetooth) {
+ final StreamState ss = streamStateW(stream);
+ if (ss.routedToBluetooth == routedToBluetooth) return false;
+ ss.routedToBluetooth = routedToBluetooth;
+ if (D.BUG) Log.d(TAG, "updateStreamRoutedToBluetoothW stream=" + stream
+ + " routedToBluetooth=" + routedToBluetooth);
+ return true;
+ }
+
+ private boolean updateStreamLevelW(int stream, int level) {
+ final StreamState ss = streamStateW(stream);
+ if (ss.level == level) return false;
+ ss.level = level;
+ if (isLogWorthy(stream)) {
+ Events.writeEvent(Events.EVENT_LEVEL_CHANGED, stream, level);
+ }
+ return true;
+ }
+
+ private static boolean isLogWorthy(int stream) {
+ switch (stream) {
+ case AudioSystem.STREAM_ALARM:
+ case AudioSystem.STREAM_BLUETOOTH_SCO:
+ case AudioSystem.STREAM_MUSIC:
+ case AudioSystem.STREAM_RING:
+ case AudioSystem.STREAM_SYSTEM:
+ case AudioSystem.STREAM_VOICE_CALL:
+ return true;
+ }
+ return false;
+ }
+
+ private boolean updateStreamMuteW(int stream, boolean muted) {
+ final StreamState ss = streamStateW(stream);
+ if (ss.muted == muted) return false;
+ ss.muted = muted;
+ if (isLogWorthy(stream)) {
+ Events.writeEvent(Events.EVENT_MUTE_CHANGED, stream, muted);
+ }
+ if (muted && isRinger(stream)) {
+ updateRingerModeInternalW(mAudio.getRingerModeInternal());
+ }
+ return true;
+ }
+
+ private static boolean isRinger(int stream) {
+ return stream == AudioManager.STREAM_RING || stream == AudioManager.STREAM_NOTIFICATION;
+ }
+
+ private boolean updateExitConditionW() {
+ final Condition exitCondition = mNoMan.getZenModeCondition();
+ if (Objects.equals(mState.exitCondition, exitCondition)) return false;
+ mState.exitCondition = exitCondition;
+ return true;
+ }
+
+ private boolean updateEffectsSuppressorW(ComponentName effectsSuppressor) {
+ if (Objects.equals(mState.effectsSuppressor, effectsSuppressor)) return false;
+ mState.effectsSuppressor = effectsSuppressor;
+ mState.effectsSuppressorName = getApplicationName(mContext, mState.effectsSuppressor);
+ Events.writeEvent(Events.EVENT_SUPPRESSOR_CHANGED, mState.effectsSuppressor,
+ mState.effectsSuppressorName);
+ return true;
+ }
+
+ private static String getApplicationName(Context context, ComponentName component) {
+ if (component == null) return null;
+ final PackageManager pm = context.getPackageManager();
+ final String pkg = component.getPackageName();
+ try {
+ final ApplicationInfo ai = pm.getApplicationInfo(pkg, 0);
+ final String rt = Objects.toString(ai.loadLabel(pm), "").trim();
+ if (rt.length() > 0) {
+ return rt;
+ }
+ } catch (NameNotFoundException e) {}
+ return pkg;
+ }
+
+ private boolean updateZenModeW() {
+ final int zen = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF);
+ if (mState.zenMode == zen) return false;
+ mState.zenMode = zen;
+ Events.writeEvent(Events.EVENT_ZEN_MODE_CHANGED, zen);
+ return true;
+ }
+
+ private boolean updateRingerModeExternalW(int rm) {
+ if (rm == mState.ringerModeExternal) return false;
+ mState.ringerModeExternal = rm;
+ Events.writeEvent(Events.EVENT_EXTERNAL_RINGER_MODE_CHANGED, rm);
+ return true;
+ }
+
+ private boolean updateRingerModeInternalW(int rm) {
+ if (rm == mState.ringerModeInternal) return false;
+ mState.ringerModeInternal = rm;
+ Events.writeEvent(Events.EVENT_INTERNAL_RINGER_MODE_CHANGED, rm);
+ return true;
+ }
+
+ private void onSetRingerModeW(int mode, boolean external) {
+ if (external) {
+ mAudio.setRingerMode(mode);
+ } else {
+ mAudio.setRingerModeInternal(mode);
+ }
+ }
+
+ private void onSetStreamMuteW(int stream, boolean mute) {
+ mAudio.adjustStreamVolume(stream, mute ? AudioManager.ADJUST_MUTE
+ : AudioManager.ADJUST_UNMUTE, 0);
+ }
+
+ private void onSetStreamVolumeW(int stream, int level) {
+ if (D.BUG) Log.d(TAG, "onSetStreamVolume " + stream + " level=" + level);
+ if (stream >= DYNAMIC_STREAM_START_INDEX) {
+ mMediaSessionsCallbacksW.setStreamVolume(stream, level);
+ return;
+ }
+ mAudio.setStreamVolume(stream, level, 0);
+ }
+
+ private void onSetActiveStreamW(int stream) {
+ boolean changed = updateActiveStreamW(stream);
+ if (changed) {
+ mCallbacks.onStateChanged(mState);
+ }
+ }
+
+ private void onSetExitConditionW(Condition condition) {
+ mNoMan.setZenModeCondition(condition);
+ }
+
+ private void onSetZenModeW(int mode) {
+ if (D.BUG) Log.d(TAG, "onSetZenModeW " + mode);
+ mNoMan.setZenMode(mode);
+ }
+
+ private void onDismissRequestedW(int reason) {
+ mCallbacks.onDismissRequested(reason);
+ }
+
+ public void showDndTile(boolean visible) {
+ if (D.BUG) Log.d(TAG, "showDndTile");
+ mContext.sendBroadcast(new Intent("com.android.systemui.dndtile.SET_VISIBLE")
+ .putExtra("visible", visible));
+ }
+
+ private final class VC extends IVolumeController.Stub {
+ private final String TAG = VolumeDialogController.TAG + ".VC";
+
+ @Override
+ public void displaySafeVolumeWarning(int flags) throws RemoteException {
+ if (D.BUG) Log.d(TAG, "displaySafeVolumeWarning "
+ + Util.audioManagerFlagsToString(flags));
+ if (mDestroyed) return;
+ mWorker.obtainMessage(W.SHOW_SAFETY_WARNING, flags, 0).sendToTarget();
+ }
+
+ @Override
+ public void volumeChanged(int streamType, int flags) throws RemoteException {
+ if (D.BUG) Log.d(TAG, "volumeChanged " + AudioSystem.streamToString(streamType)
+ + " " + Util.audioManagerFlagsToString(flags));
+ if (mDestroyed) return;
+ mWorker.obtainMessage(W.VOLUME_CHANGED, streamType, flags).sendToTarget();
+ }
+
+ @Override
+ public void masterMuteChanged(int flags) throws RemoteException {
+ if (D.BUG) Log.d(TAG, "masterMuteChanged");
+ }
+
+ @Override
+ public void setLayoutDirection(int layoutDirection) throws RemoteException {
+ if (D.BUG) Log.d(TAG, "setLayoutDirection");
+ if (mDestroyed) return;
+ mWorker.obtainMessage(W.LAYOUT_DIRECTION_CHANGED, layoutDirection, 0).sendToTarget();
+ }
+
+ @Override
+ public void dismiss() throws RemoteException {
+ if (D.BUG) Log.d(TAG, "dismiss requested");
+ if (mDestroyed) return;
+ mWorker.obtainMessage(W.DISMISS_REQUESTED, Events.DISMISS_REASON_VOLUME_CONTROLLER, 0)
+ .sendToTarget();
+ mWorker.sendEmptyMessage(W.DISMISS_REQUESTED);
+ }
+ }
+
+ private final class W extends Handler {
+ private static final int VOLUME_CHANGED = 1;
+ private static final int DISMISS_REQUESTED = 2;
+ private static final int GET_STATE = 3;
+ private static final int SET_RINGER_MODE = 4;
+ private static final int SET_ZEN_MODE = 5;
+ private static final int SET_EXIT_CONDITION = 6;
+ private static final int SET_STREAM_MUTE = 7;
+ private static final int LAYOUT_DIRECTION_CHANGED = 8;
+ private static final int CONFIGURATION_CHANGED = 9;
+ private static final int SET_STREAM_VOLUME = 10;
+ private static final int SET_ACTIVE_STREAM = 11;
+ private static final int NOTIFY_VISIBLE = 12;
+ private static final int USER_ACTIVITY = 13;
+ private static final int SHOW_SAFETY_WARNING = 14;
+
+ W(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case VOLUME_CHANGED: onVolumeChangedW(msg.arg1, msg.arg2); break;
+ case DISMISS_REQUESTED: onDismissRequestedW(msg.arg1); break;
+ case GET_STATE: onGetStateW(); break;
+ case SET_RINGER_MODE: onSetRingerModeW(msg.arg1, msg.arg2 != 0); break;
+ case SET_ZEN_MODE: onSetZenModeW(msg.arg1); break;
+ case SET_EXIT_CONDITION: onSetExitConditionW((Condition) msg.obj); break;
+ case SET_STREAM_MUTE: onSetStreamMuteW(msg.arg1, msg.arg2 != 0); break;
+ case LAYOUT_DIRECTION_CHANGED: mCallbacks.onLayoutDirectionChanged(msg.arg1); break;
+ case CONFIGURATION_CHANGED: mCallbacks.onConfigurationChanged(); break;
+ case SET_STREAM_VOLUME: onSetStreamVolumeW(msg.arg1, msg.arg2); break;
+ case SET_ACTIVE_STREAM: onSetActiveStreamW(msg.arg1); break;
+ case NOTIFY_VISIBLE: onNotifyVisibleW(msg.arg1 != 0); break;
+ case USER_ACTIVITY: onUserActivityW(); break;
+ case SHOW_SAFETY_WARNING: onShowSafetyWarningW(msg.arg1); break;
+ }
+ }
+ }
+
+ private final class C implements Callbacks {
+ private final HashMap<Callbacks, Handler> mCallbackMap = new HashMap<>();
+
+ public void add(Callbacks callback, Handler handler) {
+ if (callback == null || handler == null) throw new IllegalArgumentException();
+ mCallbackMap.put(callback, handler);
+ }
+
+ public void remove(Callbacks callback) {
+ mCallbackMap.remove(callback);
+ }
+
+ @Override
+ public void onShowRequested(final int reason) {
+ for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+ entry.getValue().post(new Runnable() {
+ @Override
+ public void run() {
+ entry.getKey().onShowRequested(reason);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onDismissRequested(final int reason) {
+ for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+ entry.getValue().post(new Runnable() {
+ @Override
+ public void run() {
+ entry.getKey().onDismissRequested(reason);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onStateChanged(final State state) {
+ final long time = System.currentTimeMillis();
+ final State copy = state.copy();
+ for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+ entry.getValue().post(new Runnable() {
+ @Override
+ public void run() {
+ entry.getKey().onStateChanged(copy);
+ }
+ });
+ }
+ Events.writeState(time, copy);
+ }
+
+ @Override
+ public void onLayoutDirectionChanged(final int layoutDirection) {
+ for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+ entry.getValue().post(new Runnable() {
+ @Override
+ public void run() {
+ entry.getKey().onLayoutDirectionChanged(layoutDirection);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged() {
+ for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+ entry.getValue().post(new Runnable() {
+ @Override
+ public void run() {
+ entry.getKey().onConfigurationChanged();
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onShowVibrateHint() {
+ for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+ entry.getValue().post(new Runnable() {
+ @Override
+ public void run() {
+ entry.getKey().onShowVibrateHint();
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onShowSilentHint() {
+ for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+ entry.getValue().post(new Runnable() {
+ @Override
+ public void run() {
+ entry.getKey().onShowSilentHint();
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onScreenOff() {
+ for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+ entry.getValue().post(new Runnable() {
+ @Override
+ public void run() {
+ entry.getKey().onScreenOff();
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onShowSafetyWarning(final int flags) {
+ for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+ entry.getValue().post(new Runnable() {
+ @Override
+ public void run() {
+ entry.getKey().onShowSafetyWarning(flags);
+ }
+ });
+ }
+ }
+ }
+
+
+ private final class SettingObserver extends ContentObserver {
+ private final Uri SERVICE_URI = Settings.Secure.getUriFor(
+ Settings.Secure.VOLUME_CONTROLLER_SERVICE_COMPONENT);
+ private final Uri ZEN_MODE_URI =
+ Settings.Global.getUriFor(Settings.Global.ZEN_MODE);
+ private final Uri ZEN_MODE_CONFIG_URI =
+ Settings.Global.getUriFor(Settings.Global.ZEN_MODE_CONFIG_ETAG);
+
+ public SettingObserver(Handler handler) {
+ super(handler);
+ }
+
+ public void init() {
+ mContext.getContentResolver().registerContentObserver(SERVICE_URI, false, this);
+ mContext.getContentResolver().registerContentObserver(ZEN_MODE_URI, false, this);
+ mContext.getContentResolver().registerContentObserver(ZEN_MODE_CONFIG_URI, false, this);
+ onChange(true, SERVICE_URI);
+ }
+
+ public void destroy() {
+ mContext.getContentResolver().unregisterContentObserver(this);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ boolean changed = false;
+ if (SERVICE_URI.equals(uri)) {
+ final String setting = Settings.Secure.getString(mContext.getContentResolver(),
+ Settings.Secure.VOLUME_CONTROLLER_SERVICE_COMPONENT);
+ final boolean enabled = setting != null && mComponent != null
+ && mComponent.equals(ComponentName.unflattenFromString(setting));
+ if (enabled == mEnabled) return;
+ if (enabled) {
+ register();
+ }
+ mEnabled = enabled;
+ }
+ if (ZEN_MODE_URI.equals(uri)) {
+ changed = updateZenModeW();
+ }
+ if (ZEN_MODE_CONFIG_URI.equals(uri)) {
+ changed = updateExitConditionW();
+ }
+ if (changed) {
+ mCallbacks.onStateChanged(mState);
+ }
+ }
+ }
+
+ private final class Receiver extends BroadcastReceiver {
+
+ public void init() {
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
+ filter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION);
+ filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
+ filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
+ filter.addAction(AudioManager.STREAM_MUTE_CHANGED_ACTION);
+ filter.addAction(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED);
+ filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ mContext.registerReceiver(this, filter, null, mWorker);
+ }
+
+ public void destroy() {
+ mContext.unregisterReceiver(this);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ boolean changed = false;
+ if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
+ final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
+ final int level = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
+ final int oldLevel = intent
+ .getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, -1);
+ if (D.BUG) Log.d(TAG, "onReceive VOLUME_CHANGED_ACTION stream=" + stream
+ + " level=" + level + " oldLevel=" + oldLevel);
+ changed = updateStreamLevelW(stream, level);
+ } else if (action.equals(AudioManager.STREAM_DEVICES_CHANGED_ACTION)) {
+ final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
+ final int devices = intent
+ .getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_DEVICES, -1);
+ final int oldDevices = intent
+ .getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_DEVICES, -1);
+ if (D.BUG) Log.d(TAG, "onReceive STREAM_DEVICES_CHANGED_ACTION stream="
+ + stream + " devices=" + devices + " oldDevices=" + oldDevices);
+ changed = checkRoutedToBluetoothW(stream);
+ } else if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
+ final int rm = intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1);
+ if (D.BUG) Log.d(TAG, "onReceive RINGER_MODE_CHANGED_ACTION rm="
+ + Util.ringerModeToString(rm));
+ changed = updateRingerModeExternalW(rm);
+ } else if (action.equals(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION)) {
+ final int rm = intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1);
+ if (D.BUG) Log.d(TAG, "onReceive INTERNAL_RINGER_MODE_CHANGED_ACTION rm="
+ + Util.ringerModeToString(rm));
+ changed = updateRingerModeInternalW(rm);
+ } else if (action.equals(AudioManager.STREAM_MUTE_CHANGED_ACTION)) {
+ final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
+ final boolean muted = intent
+ .getBooleanExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, false);
+ if (D.BUG) Log.d(TAG, "onReceive STREAM_MUTE_CHANGED_ACTION stream=" + stream
+ + " muted=" + muted);
+ changed = updateStreamMuteW(stream, muted);
+ } else if (action.equals(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED)) {
+ if (D.BUG) Log.d(TAG, "onReceive ACTION_EFFECTS_SUPPRESSOR_CHANGED");
+ changed = updateEffectsSuppressorW(mNoMan.getEffectsSuppressor());
+ } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
+ if (D.BUG) Log.d(TAG, "onReceive ACTION_CONFIGURATION_CHANGED");
+ mCallbacks.onConfigurationChanged();
+ } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
+ if (D.BUG) Log.d(TAG, "onReceive ACTION_SCREEN_OFF");
+ mCallbacks.onScreenOff();
+ }
+ if (changed) {
+ mCallbacks.onStateChanged(mState);
+ }
+ }
+ }
+
+ private final class MediaSessionsCallbacks implements MediaSessions.Callbacks {
+ private final HashMap<Token, Integer> mRemoteStreams = new HashMap<>();
+
+ private int mNextStream = DYNAMIC_STREAM_START_INDEX;
+
+ @Override
+ public void onRemoteUpdate(Token token, String name, PlaybackInfo pi) {
+ if (!mRemoteStreams.containsKey(token)) {
+ mRemoteStreams.put(token, mNextStream);
+ if (D.BUG) Log.d(TAG, "onRemoteUpdate: " + name + " is stream " + mNextStream);
+ mNextStream++;
+ }
+ final int stream = mRemoteStreams.get(token);
+ boolean changed = mState.states.indexOfKey(stream) < 0;
+ final StreamState ss = streamStateW(stream);
+ ss.dynamic = true;
+ ss.levelMin = 0;
+ ss.levelMax = pi.getMaxVolume();
+ if (ss.level != pi.getCurrentVolume()) {
+ ss.level = pi.getCurrentVolume();
+ changed = true;
+ }
+ if (!Objects.equals(ss.name, name)) {
+ ss.name = name;
+ changed = true;
+ }
+ if (changed) {
+ if (D.BUG) Log.d(TAG, "onRemoteUpdate: " + name + ": " + ss.level
+ + " of " + ss.levelMax);
+ mCallbacks.onStateChanged(mState);
+ }
+ }
+
+ @Override
+ public void onRemoteVolumeChanged(Token token, int flags) {
+ final int stream = mRemoteStreams.get(token);
+ final boolean showUI = (flags & AudioManager.FLAG_SHOW_UI) != 0;
+ boolean changed = updateActiveStreamW(stream);
+ if (showUI) {
+ changed |= checkRoutedToBluetoothW(AudioManager.STREAM_MUSIC);
+ }
+ if (changed) {
+ mCallbacks.onStateChanged(mState);
+ }
+ if (showUI) {
+ mCallbacks.onShowRequested(Events.SHOW_REASON_REMOTE_VOLUME_CHANGED);
+ }
+ }
+
+ @Override
+ public void onRemoteRemoved(Token token) {
+ final int stream = mRemoteStreams.get(token);
+ mState.states.remove(stream);
+ if (mState.activeStream == stream) {
+ updateActiveStreamW(-1);
+ }
+ mCallbacks.onStateChanged(mState);
+ }
+
+ public void setStreamVolume(int stream, int level) {
+ final Token t = findToken(stream);
+ if (t == null) {
+ Log.w(TAG, "setStreamVolume: No token found for stream: " + stream);
+ return;
+ }
+ mMediaSessions.setVolume(t, level);
+ }
+
+ private Token findToken(int stream) {
+ for (Map.Entry<Token, Integer> entry : mRemoteStreams.entrySet()) {
+ if (entry.getValue().equals(stream)) {
+ return entry.getKey();
+ }
+ }
+ return null;
+ }
+ }
+
+ public static final class StreamState {
+ public boolean dynamic;
+ public int level;
+ public int levelMin;
+ public int levelMax;
+ public boolean muted;
+ public boolean muteSupported;
+ public String name;
+ public boolean routedToBluetooth;
+
+ public StreamState copy() {
+ final StreamState rt = new StreamState();
+ rt.dynamic = dynamic;
+ rt.level = level;
+ rt.levelMin = levelMin;
+ rt.levelMax = levelMax;
+ rt.muted = muted;
+ rt.muteSupported = muteSupported;
+ rt.name = name;
+ rt.routedToBluetooth = routedToBluetooth;
+ return rt;
+ }
+ }
+
+ public static final class State {
+ public static int NO_ACTIVE_STREAM = -1;
+
+ public final SparseArray<StreamState> states = new SparseArray<StreamState>();
+
+ public int ringerModeInternal;
+ public int ringerModeExternal;
+ public int zenMode;
+ public ComponentName effectsSuppressor;
+ public String effectsSuppressorName;
+ public Condition exitCondition;
+ public int activeStream = NO_ACTIVE_STREAM;
+
+ public State copy() {
+ final State rt = new State();
+ for (int i = 0; i < states.size(); i++) {
+ rt.states.put(states.keyAt(i), states.valueAt(i).copy());
+ }
+ rt.ringerModeExternal = ringerModeExternal;
+ rt.ringerModeInternal = ringerModeInternal;
+ rt.zenMode = zenMode;
+ if (effectsSuppressor != null) rt.effectsSuppressor = effectsSuppressor.clone();
+ rt.effectsSuppressorName = effectsSuppressorName;
+ if (exitCondition != null) rt.exitCondition = exitCondition.copy();
+ rt.activeStream = activeStream;
+ return rt;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("{");
+ for (int i = 0; i < states.size(); i++) {
+ if (i > 0) sb.append(',');
+ final int stream = states.keyAt(i);
+ final StreamState ss = states.valueAt(i);
+ sb.append(AudioSystem.streamToString(stream)).append(":").append(ss.level)
+ .append("[").append(ss.levelMin).append("..").append(ss.levelMax);
+ if (ss.muted) sb.append(" [MUTED]");
+ }
+ sb.append(",ringerModeExternal:").append(ringerModeExternal);
+ sb.append(",ringerModeInternal:").append(ringerModeInternal);
+ sb.append(",zenMode:").append(zenMode);
+ sb.append(",effectsSuppressor:").append(effectsSuppressor);
+ sb.append(",effectsSuppressorName:").append(effectsSuppressorName);
+ sb.append(",exitCondition:").append(exitCondition);
+ sb.append(",activeStream:").append(activeStream);
+ return sb.append('}').toString();
+ }
+ }
+
+ public interface Callbacks {
+ void onShowRequested(int reason);
+ void onDismissRequested(int reason);
+ void onStateChanged(State state);
+ void onLayoutDirectionChanged(int layoutDirection);
+ void onConfigurationChanged();
+ void onShowVibrateHint();
+ void onShowSilentHint();
+ void onScreenOff();
+ void onShowSafetyWarning(int flags);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
index d16b818..f16e9d2 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
@@ -52,7 +52,6 @@ import android.os.Message;
import android.os.Vibrator;
import android.util.Log;
import android.util.SparseArray;
-import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -72,7 +71,6 @@ import android.widget.TextView;
import com.android.internal.R;
import com.android.systemui.DemoMode;
-import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.ZenModeController;
import java.io.FileDescriptor;
@@ -264,80 +262,6 @@ public class VolumePanel extends Handler implements DemoMode {
private static AlertDialog sSafetyWarning;
private static Object sSafetyWarningLock = new Object();
- private static class SafetyWarning extends SystemUIDialog
- implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener {
- private final Context mContext;
- private final VolumePanel mVolumePanel;
- private final AudioManager mAudioManager;
-
- private boolean mNewVolumeUp;
-
- SafetyWarning(Context context, VolumePanel volumePanel, AudioManager audioManager) {
- super(context);
- mContext = context;
- mVolumePanel = volumePanel;
- mAudioManager = audioManager;
-
- setMessage(mContext.getString(com.android.internal.R.string.safe_media_volume_warning));
- setButton(DialogInterface.BUTTON_POSITIVE,
- mContext.getString(com.android.internal.R.string.yes), this);
- setButton(DialogInterface.BUTTON_NEGATIVE,
- mContext.getString(com.android.internal.R.string.no), (OnClickListener) null);
- setOnDismissListener(this);
-
- IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
- context.registerReceiver(mReceiver, filter);
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_VOLUME_UP && event.getRepeatCount() == 0) {
- mNewVolumeUp = true;
- }
- return super.onKeyDown(keyCode, event);
- }
-
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_VOLUME_UP && mNewVolumeUp) {
- if (LOGD) Log.d(TAG, "Confirmed warning via VOLUME_UP");
- mAudioManager.disableSafeMediaVolume();
- dismiss();
- }
- return super.onKeyUp(keyCode, event);
- }
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- mAudioManager.disableSafeMediaVolume();
- }
-
- @Override
- public void onDismiss(DialogInterface unused) {
- mContext.unregisterReceiver(mReceiver);
- cleanUp();
- }
-
- private void cleanUp() {
- synchronized (sSafetyWarningLock) {
- sSafetyWarning = null;
- }
- mVolumePanel.forceTimeout(0);
- mVolumePanel.updateStates();
- }
-
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
- if (LOGD) Log.d(TAG, "Received ACTION_CLOSE_SYSTEM_DIALOGS");
- cancel();
- cleanUp();
- }
- }
- };
- }
-
protected LayoutParams getDialogLayoutParams(Window window, Resources res) {
final LayoutParams lp = window.getAttributes();
lp.token = null;
@@ -384,7 +308,7 @@ public class VolumePanel extends Handler implements DemoMode {
final Window window = mDialog.getWindow();
window.requestFeature(Window.FEATURE_NO_TITLE);
mDialog.setCanceledOnTouchOutside(true);
- mDialog.setContentView(com.android.systemui.R.layout.volume_dialog);
+ mDialog.setContentView(com.android.systemui.R.layout.volume_panel_dialog);
mDialog.setOnDismissListener(new OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
@@ -1283,7 +1207,16 @@ public class VolumePanel extends Handler implements DemoMode {
if (sSafetyWarning != null) {
return;
}
- sSafetyWarning = new SafetyWarning(mContext, this, mAudioManager);
+ sSafetyWarning = new SafetyWarningDialog(mContext, mAudioManager) {
+ @Override
+ protected void cleanUp() {
+ synchronized (sSafetyWarningLock) {
+ sSafetyWarning = null;
+ }
+ forceTimeout(0);
+ updateStates();
+ }
+ };
sSafetyWarning.show();
}
updateStates();
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 ac08904..387aed0 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -29,25 +29,17 @@ import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
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 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.qs.tiles.DndTile;
import com.android.systemui.statusbar.ServiceMonitor;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.statusbar.policy.ZenModeControllerImpl;
@@ -59,6 +51,8 @@ public class VolumeUI extends SystemUI {
private static final String TAG = "VolumeUI";
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();
@@ -67,12 +61,10 @@ public class VolumeUI extends SystemUI {
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() {
@@ -83,10 +75,10 @@ public class VolumeUI extends SystemUI {
(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);
+ 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,
@@ -94,30 +86,30 @@ public class VolumeUI extends SystemUI {
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 (!mEnabled) return;
pw.print("mVolumeControllerService="); pw.println(mVolumeControllerService.getComponent());
- if (mPanel != null) {
- mPanel.dump(fd, pw, args);
- }
+ getVolumeComponent().dump(fd, pw, args);
}
- private void setVolumeController(boolean register) {
+ private void setDefaultVolumeController(boolean register) {
if (register) {
- if (LOGD) Log.d(TAG, "Registering default volume controller");
- mAudioManager.setVolumeController(mVolumeController);
- mAudioManager.setVolumePolicy(VolumePolicy.DEFAULT);
- mMediaSessionManager.setRemoteVolumeController(mRemoteVolumeController);
DndTile.setVisible(mContext, false);
+ if (LOGD) Log.d(TAG, "Registering default volume controller");
+ getVolumeComponent().register();
} else {
if (LOGD) Log.d(TAG, "Unregistering default volume controller");
mAudioManager.setVolumeController(null);
@@ -125,33 +117,6 @@ public class VolumeUI extends SystemUI {
}
}
- 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();
- }
- }
-
- @Override
- public void onVisible(boolean visible) {
- if (mAudioManager != null && mVolumeController != null) {
- mAudioManager.notifyVolumeControllerVisible(mVolumeController, visible);
- }
- }
- });
- }
-
private String getAppLabel(ComponentName component) {
final String pkg = component.getPackageName();
try {
@@ -179,83 +144,11 @@ public class VolumeUI extends SystemUI {
d.show();
}
- 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);
- }
-
- @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();
- }
-
- @Override
- public ZenModeController getZenController() {
- return mPanel.getZenController();
- }
-
- @Override
- public void dispatchDemoCommand(String command, Bundle args) {
- mPanel.dispatchDemoCommand(command, args);
- }
-
- @Override
- public void dismissNow() {
- mPanel.postDismiss(0);
- }
- }
-
- private final class RemoteVolumeController extends IRemoteVolumeController.Stub {
-
- @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.
- }
- }
-
private final class ServiceMonitorCallbacks implements ServiceMonitor.Callbacks {
@Override
public void onNoService() {
if (LOGD) Log.d(TAG, "onNoService");
- setVolumeController(true);
+ setDefaultVolumeController(true);
mRestorationNotification.hide();
if (!mVolumeControllerService.isPackageAvailable()) {
mVolumeControllerService.setComponent(null);
@@ -267,8 +160,8 @@ public class VolumeUI extends SystemUI {
if (LOGD) Log.d(TAG, "onServiceStartAttempt");
// poke the setting to update the uid
mVolumeControllerService.setComponent(mVolumeControllerService.getComponent());
- setVolumeController(false);
- mVolumeController.dismissNow();
+ setDefaultVolumeController(false);
+ getVolumeComponent().dismissNow();
mRestorationNotification.show();
return 0;
}
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 878ab712..cb6c29f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
@@ -23,7 +23,6 @@ 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;
@@ -150,12 +149,13 @@ public class ZenModePanel extends LinearLayout {
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(2).setVisibility(mEmbedded ? GONE : VISIBLE);
+ mZenButtons.getChildAt(3).setVisibility(mEmbedded ? GONE : VISIBLE);
mZenEmbeddedDivider.setVisibility(mEmbedded ? VISIBLE : GONE);
setExpanded(mEmbedded);
updateWidgets();
@@ -166,12 +166,13 @@ public class ZenModePanel extends LinearLayout {
super.onFinishInflate();
mZenButtons = (SegmentedButtons) findViewById(R.id.zen_buttons);
- mZenButtons.addButton(R.string.interruption_level_none, R.drawable.ic_zen_none,
+ mZenButtons.addButton(R.string.interruption_level_none_twoline,
Global.ZEN_MODE_NO_INTERRUPTIONS);
- mZenButtons.addButton(R.string.interruption_level_priority, R.drawable.ic_zen_important,
+ mZenButtons.addButton(R.string.interruption_level_alarms_twoline,
+ Global.ZEN_MODE_ALARMS);
+ mZenButtons.addButton(R.string.interruption_level_priority_twoline,
Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
- mZenButtons.addButton(R.string.interruption_level_all, R.drawable.ic_zen_all,
- Global.ZEN_MODE_OFF);
+ mZenButtons.addButton(R.string.interruption_level_all, Global.ZEN_MODE_OFF);
mZenButtons.setCallback(mZenButtonsCallback);
mZenButtonsContainer = (ViewGroup) findViewById(R.id.zen_buttons_container);
@@ -275,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();
@@ -358,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();
@@ -428,7 +434,7 @@ public class ZenModePanel extends LinearLayout {
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);
@@ -715,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,