Merge "Clip windows to their background by default" into lmp-dev
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java
index 90e80d3..4dd7e07 100644
--- a/core/java/android/widget/RelativeLayout.java
+++ b/core/java/android/widget/RelativeLayout.java
@@ -195,6 +195,11 @@
LEFT_OF, RIGHT_OF, ALIGN_LEFT, ALIGN_RIGHT, START_OF, END_OF, ALIGN_START, ALIGN_END
};
+ /**
+ * Used to indicate left/right/top/bottom should be inferred from constraints
+ */
+ private static final int VALUE_NOT_SET = Integer.MIN_VALUE;
+
private View mBaselineView = null;
private boolean mHasBaselineAlignedChild;
@@ -670,8 +675,8 @@
/**
* Measure a child. The child should have left, top, right and bottom information
- * stored in its LayoutParams. If any of these values is -1 it means that the view
- * can extend up to the corresponding edge.
+ * stored in its LayoutParams. If any of these values is VALUE_NOT_SET it means
+ * that the view can extend up to the corresponding edge.
*
* @param child Child to measure
* @param params LayoutParams associated with child
@@ -744,11 +749,11 @@
int childSpecMode = 0;
int childSpecSize = 0;
- // Negative values in a mySize/myWidth/myWidth value in RelativeLayout
+ // Negative values in a mySize value in RelativeLayout
// measurement is code for, "we got an unspecified mode in the
// RelativeLayout's measure spec."
if (mySize < 0 && !mAllowBrokenMeasureSpecs) {
- if (childStart >= 0 && childEnd >= 0) {
+ if (childStart != VALUE_NOT_SET && childEnd != VALUE_NOT_SET) {
// Constraints fixed both edges, so child has an exact size.
childSpecSize = Math.max(0, childEnd - childStart);
childSpecMode = MeasureSpec.EXACTLY;
@@ -771,17 +776,17 @@
// If the view did not express a layout constraint for an edge, use
// view's margins and our padding
- if (tempStart < 0) {
+ if (tempStart == VALUE_NOT_SET) {
tempStart = startPadding + startMargin;
}
- if (tempEnd < 0) {
+ if (tempEnd == VALUE_NOT_SET) {
tempEnd = mySize - endPadding - endMargin;
}
// Figure out maximum size available to this view
int maxAvailable = tempEnd - tempStart;
- if (childStart >= 0 && childEnd >= 0) {
+ if (childStart != VALUE_NOT_SET && childEnd != VALUE_NOT_SET) {
// Constraints fixed both edges, so child must be an exact size
childSpecMode = MeasureSpec.EXACTLY;
childSpecSize = maxAvailable;
@@ -828,13 +833,13 @@
final int layoutDirection = getLayoutDirection();
int[] rules = params.getRules(layoutDirection);
- if (params.mLeft < 0 && params.mRight >= 0) {
+ if (params.mLeft == VALUE_NOT_SET && params.mRight != VALUE_NOT_SET) {
// Right is fixed, but left varies
params.mLeft = params.mRight - child.getMeasuredWidth();
- } else if (params.mLeft >= 0 && params.mRight < 0) {
+ } else if (params.mLeft != VALUE_NOT_SET && params.mRight == VALUE_NOT_SET) {
// Left is fixed, but right varies
params.mRight = params.mLeft + child.getMeasuredWidth();
- } else if (params.mLeft < 0 && params.mRight < 0) {
+ } else if (params.mLeft == VALUE_NOT_SET && params.mRight == VALUE_NOT_SET) {
// Both left and right vary
if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) {
if (!wrapContent) {
@@ -864,13 +869,13 @@
int[] rules = params.getRules();
- if (params.mTop < 0 && params.mBottom >= 0) {
+ if (params.mTop == VALUE_NOT_SET && params.mBottom != VALUE_NOT_SET) {
// Bottom is fixed, but top varies
params.mTop = params.mBottom - child.getMeasuredHeight();
- } else if (params.mTop >= 0 && params.mBottom < 0) {
+ } else if (params.mTop != VALUE_NOT_SET && params.mBottom == VALUE_NOT_SET) {
// Top is fixed, but bottom varies
params.mBottom = params.mTop + child.getMeasuredHeight();
- } else if (params.mTop < 0 && params.mBottom < 0) {
+ } else if (params.mTop == VALUE_NOT_SET && params.mBottom == VALUE_NOT_SET) {
// Both top and bottom vary
if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_VERTICAL] != 0) {
if (!wrapContent) {
@@ -891,12 +896,14 @@
private void applyHorizontalSizeRules(LayoutParams childParams, int myWidth, int[] rules) {
RelativeLayout.LayoutParams anchorParams;
- // -1 indicated a "soft requirement" in that direction. For example:
- // left=10, right=-1 means the view must start at 10, but can go as far as it wants to the right
- // left =-1, right=10 means the view must end at 10, but can go as far as it wants to the left
+ // VALUE_NOT_SET indicates a "soft requirement" in that direction. For example:
+ // left=10, right=VALUE_NOT_SET means the view must start at 10, but can go as far as it
+ // wants to the right
+ // left=VALUE_NOT_SET, right=10 means the view must end at 10, but can go as far as it
+ // wants to the left
// left=10, right=20 means the left and right ends are both fixed
- childParams.mLeft = -1;
- childParams.mRight = -1;
+ childParams.mLeft = VALUE_NOT_SET;
+ childParams.mRight = VALUE_NOT_SET;
anchorParams = getRelatedViewParams(rules, LEFT_OF);
if (anchorParams != null) {
@@ -947,8 +954,8 @@
int[] rules = childParams.getRules();
RelativeLayout.LayoutParams anchorParams;
- childParams.mTop = -1;
- childParams.mBottom = -1;
+ childParams.mTop = VALUE_NOT_SET;
+ childParams.mBottom = VALUE_NOT_SET;
anchorParams = getRelatedViewParams(rules, ABOVE);
if (anchorParams != null) {
@@ -1222,9 +1229,6 @@
private int mLeft, mTop, mRight, mBottom;
- private int mStart = DEFAULT_MARGIN_RELATIVE;
- private int mEnd = DEFAULT_MARGIN_RELATIVE;
-
private boolean mRulesChanged = false;
private boolean mIsRtlCompatibilityMode = false;
@@ -1601,14 +1605,6 @@
@Override
public void resolveLayoutDirection(int layoutDirection) {
final boolean isLayoutRtl = isLayoutRtl();
- if (isLayoutRtl) {
- if (mStart != DEFAULT_MARGIN_RELATIVE) mRight = mStart;
- if (mEnd != DEFAULT_MARGIN_RELATIVE) mLeft = mEnd;
- } else {
- if (mStart != DEFAULT_MARGIN_RELATIVE) mLeft = mStart;
- if (mEnd != DEFAULT_MARGIN_RELATIVE) mRight = mEnd;
- }
-
if (hasRelativeRules() && layoutDirection != getLayoutDirection()) {
resolveRules(layoutDirection);
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index fac0eb2..30831cd 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -222,6 +222,7 @@
* @attr ref android.R.styleable#TextView_imeActionId
* @attr ref android.R.styleable#TextView_editorExtras
* @attr ref android.R.styleable#TextView_elegantTextHeight
+ * @attr ref android.R.styleable#TextView_letterSpacing
*/
@RemoteView
public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
@@ -657,6 +658,7 @@
int shadowcolor = 0;
float dx = 0, dy = 0, r = 0;
boolean elegant = false;
+ float letterSpacing = 0;
final Resources.Theme theme = context.getTheme();
@@ -737,6 +739,10 @@
case com.android.internal.R.styleable.TextAppearance_elegantTextHeight:
elegant = appearance.getBoolean(attr, false);
break;
+
+ case com.android.internal.R.styleable.TextAppearance_letterSpacing:
+ letterSpacing = appearance.getFloat(attr, 0);
+ break;
}
}
@@ -1078,6 +1084,10 @@
case com.android.internal.R.styleable.TextView_elegantTextHeight:
elegant = a.getBoolean(attr, false);
break;
+
+ case com.android.internal.R.styleable.TextView_letterSpacing:
+ letterSpacing = a.getFloat(attr, 0);
+ break;
}
}
a.recycle();
@@ -1259,6 +1269,7 @@
}
setRawTextSize(textSize);
setElegantTextHeight(elegant);
+ setLetterSpacing(letterSpacing);
if (allCaps) {
setTransformationMethod(new AllCapsTransformationMethod(getContext()));
@@ -2487,6 +2498,11 @@
com.android.internal.R.styleable.TextAppearance_elegantTextHeight, false));
}
+ if (appearance.hasValue(com.android.internal.R.styleable.TextAppearance_letterSpacing)) {
+ setLetterSpacing(appearance.getFloat(
+ com.android.internal.R.styleable.TextAppearance_letterSpacing, 0));
+ }
+
appearance.recycle();
}
@@ -2667,6 +2683,41 @@
}
/**
+ * @return the extent by which text is currently being letter-spaced.
+ * This will normally be 0.
+ *
+ * @see #setLetterSpacing(float)
+ * @hide
+ */
+ public float getLetterSpacing() {
+ return mTextPaint.getLetterSpacing();
+ }
+
+ /**
+ * Sets text letter-spacing. The value is in 'EM' units. Typical values
+ * for slight expansion will be around 0.05. Negative values tighten text.
+ *
+ * @see #getLetterSpacing()
+ * @see Paint#setFlags
+ *
+ * @attr ref android.R.styleable#TextView_letterSpacing
+ * @hide
+ */
+ @android.view.RemotableViewMethod
+ public void setLetterSpacing(float letterSpacing) {
+ if (letterSpacing != mTextPaint.getLetterSpacing()) {
+ mTextPaint.setLetterSpacing(letterSpacing);
+
+ if (mLayout != null) {
+ nullLayouts();
+ requestLayout();
+ invalidate();
+ }
+ }
+ }
+
+
+ /**
* Sets the text color for all the states (normal, selected,
* focused) to be this color.
*
diff --git a/core/jni/android/graphics/MinikinUtils.cpp b/core/jni/android/graphics/MinikinUtils.cpp
index c66437a..802f2abe 100644
--- a/core/jni/android/graphics/MinikinUtils.cpp
+++ b/core/jni/android/graphics/MinikinUtils.cpp
@@ -29,6 +29,8 @@
namespace android {
// Do an sprintf starting at offset n, abort on overflow
+static int snprintfcat(char* buf, int off, int size, const char* format, ...)
+ __attribute__((__format__(__printf__, 4, 5)));
static int snprintfcat(char* buf, int off, int size, const char* format, ...) {
va_list args;
va_start(args, format);
@@ -43,17 +45,18 @@
TypefaceImpl* resolvedFace = TypefaceImpl_resolveDefault(typeface);
layout->setFontCollection(resolvedFace->fFontCollection);
FontStyle style = resolvedFace->fStyle;
- char css[256];
+ char css[512];
int off = snprintfcat(css, 0, sizeof(css),
"font-size: %d; font-scale-x: %f; font-skew-x: %f; -paint-flags: %d;"
- " font-weight: %d; font-style: %s; -minikin-bidi: %d;",
+ " font-weight: %d; font-style: %s; -minikin-bidi: %d; letter-spacing: %f;",
(int)paint->getTextSize(),
paint->getTextScaleX(),
paint->getTextSkewX(),
MinikinFontSkia::packPaintFlags(paint),
style.getWeight() * 100,
style.getItalic() ? "italic" : "normal",
- bidiFlags);
+ bidiFlags,
+ paint->getLetterSpacing());
SkString langString = paint->getPaintOptionsAndroid().getLanguage().getTag();
off = snprintfcat(css, off, sizeof(css), " lang: %s;", langString.c_str());
SkPaintOptionsAndroid::FontVariant var = paint->getPaintOptionsAndroid().getFontVariant();
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index c06f0d2..e2b3684 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -423,6 +423,16 @@
GraphicsJNI::getNativePaint(env, paint)->setTextSkewX(skewX);
}
+ static jfloat getLetterSpacing(JNIEnv* env, jobject clazz, jlong paintHandle) {
+ Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+ return paint->getLetterSpacing();
+ }
+
+ static void setLetterSpacing(JNIEnv* env, jobject clazz, jlong paintHandle, jfloat letterSpacing) {
+ Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+ paint->setLetterSpacing(letterSpacing);
+ }
+
static SkScalar getMetricsInternal(JNIEnv* env, jobject jpaint, Paint::FontMetrics *metrics) {
const int kElegantTop = 2500;
const int kElegantBottom = -1000;
@@ -988,6 +998,8 @@
{"setTextScaleX","(F)V", (void*) PaintGlue::setTextScaleX},
{"getTextSkewX","()F", (void*) PaintGlue::getTextSkewX},
{"setTextSkewX","(F)V", (void*) PaintGlue::setTextSkewX},
+ {"native_getLetterSpacing","(J)F", (void*) PaintGlue::getLetterSpacing},
+ {"native_setLetterSpacing","(JF)V", (void*) PaintGlue::setLetterSpacing},
{"ascent","()F", (void*) PaintGlue::ascent},
{"descent","()F", (void*) PaintGlue::descent},
{"getFontMetrics", "(Landroid/graphics/Paint$FontMetrics;)F", (void*)PaintGlue::getFontMetrics},
diff --git a/core/jni/android/graphics/Paint.h b/core/jni/android/graphics/Paint.h
index 239b217..7235cc4 100644
--- a/core/jni/android/graphics/Paint.h
+++ b/core/jni/android/graphics/Paint.h
@@ -34,7 +34,16 @@
return !(a == b);
}
+ void setLetterSpacing(float letterSpacing) {
+ mLetterSpacing = letterSpacing;
+ }
+
+ float getLetterSpacing() const {
+ return mLetterSpacing;
+ }
+
private:
+ float mLetterSpacing;
};
} // namespace android
diff --git a/core/jni/android/graphics/PaintImpl.cpp b/core/jni/android/graphics/PaintImpl.cpp
index 6baae76..ff2bbc5 100644
--- a/core/jni/android/graphics/PaintImpl.cpp
+++ b/core/jni/android/graphics/PaintImpl.cpp
@@ -22,10 +22,12 @@
namespace android {
-Paint::Paint() : SkPaint() {
+Paint::Paint() : SkPaint(),
+ mLetterSpacing(0) {
}
-Paint::Paint(const Paint& paint) : SkPaint(paint) {
+Paint::Paint(const Paint& paint) : SkPaint(paint),
+ mLetterSpacing(0) {
}
Paint::~Paint() {
@@ -33,11 +35,13 @@
Paint& Paint::operator=(const Paint& other) {
SkPaint::operator=(other);
+ mLetterSpacing = other.mLetterSpacing;
return *this;
}
bool operator==(const Paint& a, const Paint& b) {
- return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b);
+ return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b)
+ && a.mLetterSpacing == b.mLetterSpacing;
}
}
diff --git a/core/res/res/drawable/btn_radio_material_anim.xml b/core/res/res/drawable/btn_radio_material_anim.xml
index 0be590e..121e544 100644
--- a/core/res/res/drawable/btn_radio_material_anim.xml
+++ b/core/res/res/drawable/btn_radio_material_anim.xml
@@ -16,42 +16,52 @@
<animated-selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false" android:state_checked="true">
- <bitmap android:src="@drawable/btn_radio_to_on_mtrl_015" android:tint="?attr/colorControlActivated" android:alpha="?attr/disabledAlpha" />
+ <bitmap
+ android:src="@drawable/btn_radio_to_on_mtrl_015"
+ android:tint="?attr/colorControlActivated"
+ android:alpha="?attr/disabledAlpha" />
</item>
<item android:state_enabled="false">
- <bitmap android:src="@drawable/btn_radio_to_on_mtrl_000" android:tint="?attr/colorControlNormal" android:alpha="?attr/disabledAlpha" />
+ <bitmap
+ android:src="@drawable/btn_radio_to_on_mtrl_000"
+ android:tint="?attr/colorControlNormal"
+ android:alpha="?attr/disabledAlpha" />
</item>
<item android:state_checked="true" android:id="@+id/on">
- <bitmap android:src="@drawable/btn_radio_to_on_mtrl_015" android:tint="?attr/colorControlActivated" />
+ <bitmap
+ android:src="@drawable/btn_radio_to_on_mtrl_015"
+ android:tint="?attr/colorControlActivated" />
</item>
<item android:id="@+id/off">
- <bitmap android:src="@drawable/btn_radio_to_on_mtrl_000" android:tint="?attr/colorControlNormal" />
+ <bitmap
+ android:src="@drawable/btn_radio_to_on_mtrl_000"
+ android:tint="?attr/colorControlNormal" />
</item>
<transition android:fromId="@+id/off" android:toId="@+id/on">
<animation-list>
<item android:duration="15">
- <bitmap android:src="@drawable/btn_radio_to_on_mtrl_000" android:tint="?attr/colorControlActivated" />
+ <bitmap android:src="@drawable/btn_radio_to_on_mtrl_000" android:tint="?attr/colorControlNormal" />
</item>
<item android:duration="15">
- <bitmap android:src="@drawable/btn_radio_to_on_mtrl_001" android:tint="?attr/colorControlActivated" />
+ <bitmap android:src="@drawable/btn_radio_to_on_mtrl_001" android:tint="?attr/colorControlNormal" />
</item>
<item android:duration="15">
- <bitmap android:src="@drawable/btn_radio_to_on_mtrl_002" android:tint="?attr/colorControlActivated" />
+ <bitmap android:src="@drawable/btn_radio_to_on_mtrl_002" android:tint="?attr/colorControlNormal" />
</item>
<item android:duration="15">
- <bitmap android:src="@drawable/btn_radio_to_on_mtrl_003" android:tint="?attr/colorControlActivated" />
+ <bitmap android:src="@drawable/btn_radio_to_on_mtrl_003" android:tint="?attr/colorControlNormal" />
</item>
<item android:duration="15">
- <bitmap android:src="@drawable/btn_radio_to_on_mtrl_004" android:tint="?attr/colorControlActivated" />
+ <bitmap android:src="@drawable/btn_radio_to_on_mtrl_004" android:tint="?attr/colorControlNormal" />
</item>
<item android:duration="15">
- <bitmap android:src="@drawable/btn_radio_to_on_mtrl_005" android:tint="?attr/colorControlActivated" />
+ <bitmap android:src="@drawable/btn_radio_to_on_mtrl_005" android:tint="?attr/colorControlNormal" />
</item>
<item android:duration="15">
- <bitmap android:src="@drawable/btn_radio_to_on_mtrl_006" android:tint="?attr/colorControlActivated" />
+ <bitmap android:src="@drawable/btn_radio_to_on_mtrl_006" android:tint="?attr/colorControlNormal" />
</item>
<item android:duration="15">
- <bitmap android:src="@drawable/btn_radio_to_on_mtrl_007" android:tint="?attr/colorControlActivated" />
+ <bitmap android:src="@drawable/btn_radio_to_on_mtrl_007" android:tint="?attr/colorControlNormal" />
</item>
<item android:duration="15">
<bitmap android:src="@drawable/btn_radio_to_on_mtrl_008" android:tint="?attr/colorControlActivated" />
@@ -106,28 +116,28 @@
<bitmap android:src="@drawable/btn_radio_to_off_mtrl_007" android:tint="?attr/colorControlActivated" />
</item>
<item android:duration="15">
- <bitmap android:src="@drawable/btn_radio_to_off_mtrl_008" android:tint="?attr/colorControlActivated" />
+ <bitmap android:src="@drawable/btn_radio_to_off_mtrl_008" android:tint="?attr/colorControlNormal" />
</item>
<item android:duration="15">
- <bitmap android:src="@drawable/btn_radio_to_off_mtrl_009" android:tint="?attr/colorControlActivated" />
+ <bitmap android:src="@drawable/btn_radio_to_off_mtrl_009" android:tint="?attr/colorControlNormal" />
</item>
<item android:duration="15">
- <bitmap android:src="@drawable/btn_radio_to_off_mtrl_010" android:tint="?attr/colorControlActivated" />
+ <bitmap android:src="@drawable/btn_radio_to_off_mtrl_010" android:tint="?attr/colorControlNormal" />
</item>
<item android:duration="15">
- <bitmap android:src="@drawable/btn_radio_to_off_mtrl_011" android:tint="?attr/colorControlActivated" />
+ <bitmap android:src="@drawable/btn_radio_to_off_mtrl_011" android:tint="?attr/colorControlNormal" />
</item>
<item android:duration="15">
- <bitmap android:src="@drawable/btn_radio_to_off_mtrl_012" android:tint="?attr/colorControlActivated" />
+ <bitmap android:src="@drawable/btn_radio_to_off_mtrl_012" android:tint="?attr/colorControlNormal" />
</item>
<item android:duration="15">
- <bitmap android:src="@drawable/btn_radio_to_off_mtrl_013" android:tint="?attr/colorControlActivated" />
+ <bitmap android:src="@drawable/btn_radio_to_off_mtrl_013" android:tint="?attr/colorControlNormal" />
</item>
<item android:duration="15">
- <bitmap android:src="@drawable/btn_radio_to_off_mtrl_014" android:tint="?attr/colorControlActivated" />
+ <bitmap android:src="@drawable/btn_radio_to_off_mtrl_014" android:tint="?attr/colorControlNormal" />
</item>
<item android:duration="15">
- <bitmap android:src="@drawable/btn_radio_to_off_mtrl_015" android:tint="?attr/colorControlActivated" />
+ <bitmap android:src="@drawable/btn_radio_to_off_mtrl_015" android:tint="?attr/colorControlNormal" />
</item>
</animation-list>
</transition>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 2782b52..0e87893 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3755,6 +3755,8 @@
<attr name="shadowRadius" format="float" />
<!-- Elegant text height, especially for less compacted complex script text. -->
<attr name="elegantTextHeight" format="boolean" />
+ <!-- @hide Text letter-spacing. -->
+ <attr name="letterSpacing" format="float" />
</declare-styleable>
<declare-styleable name="TextClock">
<!-- Specifies the formatting pattern used to show the time and/or date
@@ -4048,6 +4050,8 @@
<attr name="textAllCaps" />
<!-- Elegant text height, especially for less compacted complex script text. -->
<attr name="elegantTextHeight" />
+ <!-- @hide Text letter-spacing. -->
+ <attr name="letterSpacing" />
</declare-styleable>
<declare-styleable name="TextViewAppearance">
<!-- Base text color, typeface, size, and style. -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index a6e85e9..b1902dd 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2268,6 +2268,7 @@
<public type="attr" name="checkMarkTintMode" />
<public type="attr" name="popupTheme" />
<public type="attr" name="toolbarStyle" />
+ <public type="attr" name="letterSpacing" />
<public-padding type="dimen" name="l_resource_pad" end="0x01050010" />
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 3f73a03..58a1bf2 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -1260,6 +1260,29 @@
public native void setTextSkewX(float skewX);
/**
+ * Return the paint's letter-spacing for text. The default value
+ * is 0.
+ *
+ * @return the paint's letter-spacing for drawing text.
+ * @hide
+ */
+ public float getLetterSpacing() {
+ return native_getLetterSpacing(mNativePaint);
+ }
+
+ /**
+ * Set the paint's letter-spacing for text. The default value
+ * is 0. The value is in 'EM' units. Typical values for slight
+ * expansion will be around 0.05. Negative values tighten text.
+ *
+ * @param letterSpacing set the paint's letter-spacing for drawing text.
+ * @hide
+ */
+ public void setLetterSpacing(float letterSpacing) {
+ native_setLetterSpacing(mNativePaint, letterSpacing);
+ }
+
+ /**
* Return the distance above (negative) the baseline (ascent) based on the
* current typeface and text size.
*
@@ -2232,4 +2255,8 @@
private static native void native_setShadowLayer(long native_object,
float radius, float dx, float dy, int color);
private static native boolean native_hasShadowLayer(long native_object);
+
+ private static native float native_getLetterSpacing(long native_object);
+ private static native void native_setLetterSpacing(long native_object,
+ float letterSpacing);
}
diff --git a/graphics/java/android/graphics/drawable/Ripple.java b/graphics/java/android/graphics/drawable/Ripple.java
index 2d49365..6f21f2e 100644
--- a/graphics/java/android/graphics/drawable/Ripple.java
+++ b/graphics/java/android/graphics/drawable/Ripple.java
@@ -28,7 +28,6 @@
import android.util.MathUtils;
import android.view.HardwareCanvas;
import android.view.RenderNodeAnimator;
-import android.view.animation.DecelerateInterpolator;
import android.view.animation.LinearInterpolator;
import java.util.ArrayList;
@@ -44,16 +43,14 @@
private static final float WAVE_TOUCH_DOWN_ACCELERATION = 1024.0f * GLOBAL_SPEED;
private static final float WAVE_TOUCH_UP_ACCELERATION = 3400.0f * GLOBAL_SPEED;
private static final float WAVE_OPACITY_DECAY_VELOCITY = 3.0f / GLOBAL_SPEED;
- private static final float WAVE_OUTER_OPACITY_VELOCITY_MAX = 4.5f * GLOBAL_SPEED;
- private static final float WAVE_OUTER_OPACITY_VELOCITY_MIN = 1.5f * GLOBAL_SPEED;
- private static final float WAVE_OUTER_SIZE_INFLUENCE_MAX = 200f;
- private static final float WAVE_OUTER_SIZE_INFLUENCE_MIN = 40f;
private static final long RIPPLE_ENTER_DELAY = 80;
// Hardware animators.
- private final ArrayList<RenderNodeAnimator> mRunningAnimations = new ArrayList<>();
- private final ArrayList<RenderNodeAnimator> mPendingAnimations = new ArrayList<>();
+ private final ArrayList<RenderNodeAnimator> mRunningAnimations =
+ new ArrayList<RenderNodeAnimator>();
+ private final ArrayList<RenderNodeAnimator> mPendingAnimations =
+ new ArrayList<RenderNodeAnimator>();
private final RippleDrawable mOwner;
@@ -79,20 +76,17 @@
private CanvasProperty<Float> mPropRadius;
private CanvasProperty<Float> mPropX;
private CanvasProperty<Float> mPropY;
- private CanvasProperty<Paint> mPropOuterPaint;
- private CanvasProperty<Float> mPropOuterRadius;
- private CanvasProperty<Float> mPropOuterX;
- private CanvasProperty<Float> mPropOuterY;
// Software animators.
private ObjectAnimator mAnimRadius;
private ObjectAnimator mAnimOpacity;
- private ObjectAnimator mAnimOuterOpacity;
private ObjectAnimator mAnimX;
private ObjectAnimator mAnimY;
+ // Temporary paint used for creating canvas properties.
+ private Paint mTempPaint;
+
// Software rendering properties.
- private float mOuterOpacity = 0;
private float mOpacity = 1;
private float mOuterX;
private float mOuterY;
@@ -177,38 +171,35 @@
return mOpacity;
}
- public void setOuterOpacity(float a) {
- mOuterOpacity = a;
- invalidateSelf();
- }
-
- public float getOuterOpacity() {
- return mOuterOpacity;
- }
-
+ @SuppressWarnings("unused")
public void setRadiusGravity(float r) {
mTweenRadius = r;
invalidateSelf();
}
+ @SuppressWarnings("unused")
public float getRadiusGravity() {
return mTweenRadius;
}
+ @SuppressWarnings("unused")
public void setXGravity(float x) {
mTweenX = x;
invalidateSelf();
}
+ @SuppressWarnings("unused")
public float getXGravity() {
return mTweenX;
}
+ @SuppressWarnings("unused")
public void setYGravity(float y) {
mTweenY = y;
invalidateSelf();
}
+ @SuppressWarnings("unused")
public float getYGravity() {
return mTweenY;
}
@@ -238,7 +229,7 @@
// If we have any pending hardware animations, cancel any running
// animations and start those now.
final ArrayList<RenderNodeAnimator> pendingAnimations = mPendingAnimations;
- final int N = pendingAnimations == null ? 0 : pendingAnimations.size();
+ final int N = pendingAnimations.size();
if (N > 0) {
cancelHardwareAnimations();
@@ -251,7 +242,6 @@
pendingAnimations.clear();
}
- c.drawCircle(mPropOuterX, mPropOuterY, mPropOuterRadius, mPropOuterPaint);
c.drawCircle(mPropX, mPropY, mPropRadius, mPropPaint);
return true;
@@ -262,15 +252,6 @@
// Cache the paint alpha so we can restore it later.
final int paintAlpha = p.getAlpha();
-
- final int outerAlpha = (int) (paintAlpha * mOuterOpacity + 0.5f);
- if (outerAlpha > 0 && mOuterRadius > 0) {
- p.setAlpha(outerAlpha);
- p.setStyle(Style.FILL);
- c.drawCircle(mOuterX, mOuterY, mOuterRadius, p);
- hasContent = true;
- }
-
final int alpha = (int) (paintAlpha * mOpacity + 0.5f);
final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
if (alpha > 0 && radius > 0) {
@@ -316,7 +297,6 @@
public void enter() {
final int radiusDuration = (int)
(1000 * Math.sqrt(mOuterRadius / WAVE_TOUCH_DOWN_ACCELERATION * mDensity) + 0.5);
- final int outerDuration = (int) (1000 * 1.0f / WAVE_OUTER_OPACITY_VELOCITY_MIN);
final ObjectAnimator radius = ObjectAnimator.ofFloat(this, "radiusGravity", 1);
radius.setAutoCancel(true);
@@ -336,13 +316,7 @@
cY.setInterpolator(LINEAR_INTERPOLATOR);
cY.setStartDelay(RIPPLE_ENTER_DELAY);
- final ObjectAnimator outer = ObjectAnimator.ofFloat(this, "outerOpacity", 0, 1);
- outer.setAutoCancel(true);
- outer.setDuration(outerDuration);
- outer.setInterpolator(LINEAR_INTERPOLATOR);
-
mAnimRadius = radius;
- mAnimOuterOpacity = outer;
mAnimX = cX;
mAnimY = cY;
@@ -350,7 +324,6 @@
// that anything interesting is happening until the user lifts their
// finger.
radius.start();
- outer.start();
cX.start();
cY.start();
}
@@ -372,51 +345,23 @@
+ WAVE_TOUCH_DOWN_ACCELERATION) * mDensity) + 0.5);
final int opacityDuration = (int) (1000 * mOpacity / WAVE_OPACITY_DECAY_VELOCITY + 0.5f);
- // Scale the outer max opacity and opacity velocity based
- // on the size of the outer radius
-
- float outerSizeInfluence = MathUtils.constrain(
- (mOuterRadius - WAVE_OUTER_SIZE_INFLUENCE_MIN * mDensity)
- / (WAVE_OUTER_SIZE_INFLUENCE_MAX * mDensity), 0, 1);
- float outerOpacityVelocity = MathUtils.lerp(WAVE_OUTER_OPACITY_VELOCITY_MIN,
- WAVE_OUTER_OPACITY_VELOCITY_MAX, outerSizeInfluence);
-
- // Determine at what time the inner and outer opacity intersect.
- // inner(t) = mOpacity - t * WAVE_OPACITY_DECAY_VELOCITY / 1000
- // outer(t) = mOuterOpacity + t * WAVE_OUTER_OPACITY_VELOCITY / 1000
-
- final int outerInflection = Math.max(0, (int) (1000 * (mOpacity - mOuterOpacity)
- / (WAVE_OPACITY_DECAY_VELOCITY + outerOpacityVelocity) + 0.5f));
- final int inflectionOpacity = (int) (255 * (mOuterOpacity + outerInflection
- * outerOpacityVelocity * outerSizeInfluence / 1000) + 0.5f);
-
if (mCanUseHardware) {
- exitHardware(radiusDuration, opacityDuration, outerInflection, inflectionOpacity);
+ exitHardware(radiusDuration, opacityDuration);
} else {
- exitSoftware(radiusDuration, opacityDuration, outerInflection, inflectionOpacity);
+ exitSoftware(radiusDuration, opacityDuration);
}
}
- private void exitHardware(int radiusDuration, int opacityDuration, int outerInflection,
- int inflectionOpacity) {
+ private void exitHardware(int radiusDuration, int opacityDuration) {
mPendingAnimations.clear();
final float startX = MathUtils.lerp(
mClampedStartingX - mBounds.exactCenterX(), mOuterX, mTweenX);
final float startY = MathUtils.lerp(
mClampedStartingY - mBounds.exactCenterY(), mOuterY, mTweenY);
- final Paint outerPaint = new Paint();
- outerPaint.setAntiAlias(true);
- outerPaint.setColor(mColor);
- outerPaint.setAlpha((int) (255 * mOuterOpacity + 0.5f));
- outerPaint.setStyle(Style.FILL);
- mPropOuterPaint = CanvasProperty.createPaint(outerPaint);
- mPropOuterRadius = CanvasProperty.createFloat(mOuterRadius);
- mPropOuterX = CanvasProperty.createFloat(mOuterX);
- mPropOuterY = CanvasProperty.createFloat(mOuterY);
final float startRadius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
- final Paint paint = new Paint();
+ final Paint paint = getTempPaint();
paint.setAntiAlias(true);
paint.setColor(mColor);
paint.setAlpha((int) (255 * mOpacity + 0.5f));
@@ -442,41 +387,10 @@
RenderNodeAnimator.PAINT_ALPHA, 0);
opacityAnim.setDuration(opacityDuration);
opacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
-
- final RenderNodeAnimator outerOpacityAnim;
- if (outerInflection > 0) {
- // Outer opacity continues to increase for a bit.
- outerOpacityAnim = new RenderNodeAnimator(
- mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, inflectionOpacity);
- outerOpacityAnim.setDuration(outerInflection);
- outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
-
- // Chain the outer opacity exit animation.
- final int outerDuration = opacityDuration - outerInflection;
- if (outerDuration > 0) {
- final RenderNodeAnimator outerFadeOutAnim = new RenderNodeAnimator(
- mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, 0);
- outerFadeOutAnim.setDuration(outerDuration);
- outerFadeOutAnim.setInterpolator(LINEAR_INTERPOLATOR);
- outerFadeOutAnim.setStartDelay(outerInflection);
- outerFadeOutAnim.setStartValue(inflectionOpacity);
- outerFadeOutAnim.addListener(mAnimationListener);
-
- mPendingAnimations.add(outerFadeOutAnim);
- } else {
- outerOpacityAnim.addListener(mAnimationListener);
- }
- } else {
- outerOpacityAnim = new RenderNodeAnimator(
- mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, 0);
- outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
- outerOpacityAnim.setDuration(opacityDuration);
- outerOpacityAnim.addListener(mAnimationListener);
- }
+ opacityAnim.addListener(mAnimationListener);
mPendingAnimations.add(radiusAnim);
mPendingAnimations.add(opacityAnim);
- mPendingAnimations.add(outerOpacityAnim);
mPendingAnimations.add(xAnim);
mPendingAnimations.add(yAnim);
@@ -485,8 +399,14 @@
invalidateSelf();
}
- private void exitSoftware(int radiusDuration, int opacityDuration, int outerInflection,
- int inflectionOpacity) {
+ private Paint getTempPaint() {
+ if (mTempPaint == null) {
+ mTempPaint = new Paint();
+ }
+ return mTempPaint;
+ }
+
+ private void exitSoftware(int radiusDuration, int opacityDuration) {
final ObjectAnimator radiusAnim = ObjectAnimator.ofFloat(this, "radiusGravity", 1);
radiusAnim.setAutoCancel(true);
radiusAnim.setDuration(radiusDuration);
@@ -506,58 +426,15 @@
opacityAnim.setAutoCancel(true);
opacityAnim.setDuration(opacityDuration);
opacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
-
- final ObjectAnimator outerOpacityAnim;
- if (outerInflection > 0) {
- // Outer opacity continues to increase for a bit.
- outerOpacityAnim = ObjectAnimator.ofFloat(this,
- "outerOpacity", inflectionOpacity / 255.0f);
- outerOpacityAnim.setAutoCancel(true);
- outerOpacityAnim.setDuration(outerInflection);
- outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
-
- // Chain the outer opacity exit animation.
- final int outerDuration = opacityDuration - outerInflection;
- if (outerDuration > 0) {
- outerOpacityAnim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- final ObjectAnimator outerFadeOutAnim = ObjectAnimator.ofFloat(Ripple.this,
- "outerOpacity", 0);
- outerFadeOutAnim.setAutoCancel(true);
- outerFadeOutAnim.setDuration(outerDuration);
- outerFadeOutAnim.setInterpolator(LINEAR_INTERPOLATOR);
- outerFadeOutAnim.addListener(mAnimationListener);
-
- mAnimOuterOpacity = outerFadeOutAnim;
-
- outerFadeOutAnim.start();
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- animation.removeListener(this);
- }
- });
- } else {
- outerOpacityAnim.addListener(mAnimationListener);
- }
- } else {
- outerOpacityAnim = ObjectAnimator.ofFloat(this, "outerOpacity", 0);
- outerOpacityAnim.setAutoCancel(true);
- outerOpacityAnim.setDuration(opacityDuration);
- outerOpacityAnim.addListener(mAnimationListener);
- }
+ opacityAnim.addListener(mAnimationListener);
mAnimRadius = radiusAnim;
mAnimOpacity = opacityAnim;
- mAnimOuterOpacity = outerOpacityAnim;
- mAnimX = opacityAnim;
- mAnimY = opacityAnim;
+ mAnimX = xAnim;
+ mAnimY = yAnim;
radiusAnim.start();
opacityAnim.start();
- outerOpacityAnim.start();
xAnim.start();
yAnim.start();
}
@@ -579,10 +456,6 @@
mAnimOpacity.cancel();
}
- if (mAnimOuterOpacity != null) {
- mAnimOuterOpacity.cancel();
- }
-
if (mAnimX != null) {
mAnimX.cancel();
}
@@ -597,7 +470,7 @@
*/
private void cancelHardwareAnimations() {
final ArrayList<RenderNodeAnimator> runningAnimations = mRunningAnimations;
- final int N = runningAnimations == null ? 0 : runningAnimations.size();
+ final int N = runningAnimations.size();
for (int i = 0; i < N; i++) {
runningAnimations.get(i).cancel();
}
diff --git a/graphics/java/android/graphics/drawable/RippleBackground.java b/graphics/java/android/graphics/drawable/RippleBackground.java
new file mode 100644
index 0000000..d404ccd
--- /dev/null
+++ b/graphics/java/android/graphics/drawable/RippleBackground.java
@@ -0,0 +1,535 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.drawable;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.graphics.Canvas;
+import android.graphics.CanvasProperty;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.graphics.Rect;
+import android.util.MathUtils;
+import android.view.HardwareCanvas;
+import android.view.RenderNodeAnimator;
+import android.view.animation.LinearInterpolator;
+
+import java.util.ArrayList;
+
+/**
+ * Draws a Material ripple.
+ */
+class RippleBackground {
+ private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
+ private static final TimeInterpolator DECEL_INTERPOLATOR = new LogInterpolator();
+
+ private static final float GLOBAL_SPEED = 1.0f;
+ private static final float WAVE_TOUCH_DOWN_ACCELERATION = 1024.0f * GLOBAL_SPEED;
+ private static final float WAVE_OPACITY_DECAY_VELOCITY = 3.0f / GLOBAL_SPEED;
+ private static final float WAVE_OUTER_OPACITY_VELOCITY_MAX = 4.5f * GLOBAL_SPEED;
+ private static final float WAVE_OUTER_OPACITY_VELOCITY_MIN = 1.5f * GLOBAL_SPEED;
+ private static final float WAVE_OUTER_SIZE_INFLUENCE_MAX = 200f;
+ private static final float WAVE_OUTER_SIZE_INFLUENCE_MIN = 40f;
+
+ private static final long RIPPLE_ENTER_DELAY = 80;
+
+ // Hardware animators.
+ private final ArrayList<RenderNodeAnimator> mRunningAnimations =
+ new ArrayList<RenderNodeAnimator>();
+ private final ArrayList<RenderNodeAnimator> mPendingAnimations =
+ new ArrayList<RenderNodeAnimator>();
+
+ private final RippleDrawable mOwner;
+
+ /** Bounds used for computing max radius. */
+ private final Rect mBounds;
+
+ /** Full-opacity color for drawing this ripple. */
+ private int mColor;
+
+ /** Maximum ripple radius. */
+ private float mOuterRadius;
+
+ /** Screen density used to adjust pixel-based velocities. */
+ private float mDensity;
+
+ private float mStartingX;
+ private float mStartingY;
+ private float mClampedStartingX;
+ private float mClampedStartingY;
+
+ // Hardware rendering properties.
+ private CanvasProperty<Paint> mPropOuterPaint;
+ private CanvasProperty<Float> mPropOuterRadius;
+ private CanvasProperty<Float> mPropOuterX;
+ private CanvasProperty<Float> mPropOuterY;
+
+ // Software animators.
+ private ObjectAnimator mAnimOuterOpacity;
+ private ObjectAnimator mAnimX;
+ private ObjectAnimator mAnimY;
+
+ // Temporary paint used for creating canvas properties.
+ private Paint mTempPaint;
+
+ // Software rendering properties.
+ private float mOuterOpacity = 0;
+ private float mOuterX;
+ private float mOuterY;
+
+ // Values used to tween between the start and end positions.
+ private float mTweenX = 0;
+ private float mTweenY = 0;
+
+ /** Whether we should be drawing hardware animations. */
+ private boolean mHardwareAnimating;
+
+ /** Whether we can use hardware acceleration for the exit animation. */
+ private boolean mCanUseHardware;
+
+ /** Whether we have an explicit maximum radius. */
+ private boolean mHasMaxRadius;
+
+ /**
+ * Creates a new ripple.
+ */
+ public RippleBackground(RippleDrawable owner, Rect bounds, float startingX, float startingY) {
+ mOwner = owner;
+ mBounds = bounds;
+
+ mStartingX = startingX;
+ mStartingY = startingY;
+ }
+
+ public void setup(int maxRadius, int color, float density) {
+ mColor = color | 0xFF000000;
+
+ if (maxRadius != RippleDrawable.RADIUS_AUTO) {
+ mHasMaxRadius = true;
+ mOuterRadius = maxRadius;
+ } else {
+ final float halfWidth = mBounds.width() / 2.0f;
+ final float halfHeight = mBounds.height() / 2.0f;
+ mOuterRadius = (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight);
+ }
+
+ mOuterX = 0;
+ mOuterY = 0;
+ mDensity = density;
+
+ clampStartingPosition();
+ }
+
+ private void clampStartingPosition() {
+ final float cX = mBounds.exactCenterX();
+ final float cY = mBounds.exactCenterY();
+ final float dX = mStartingX - cX;
+ final float dY = mStartingY - cY;
+ final float r = mOuterRadius;
+ if (dX * dX + dY * dY > r * r) {
+ // Point is outside the circle, clamp to the circumference.
+ final double angle = Math.atan2(dY, dX);
+ mClampedStartingX = cX + (float) (Math.cos(angle) * r);
+ mClampedStartingY = cY + (float) (Math.sin(angle) * r);
+ } else {
+ mClampedStartingX = mStartingX;
+ mClampedStartingY = mStartingY;
+ }
+ }
+
+ public void onHotspotBoundsChanged() {
+ if (!mHasMaxRadius) {
+ final float halfWidth = mBounds.width() / 2.0f;
+ final float halfHeight = mBounds.height() / 2.0f;
+ mOuterRadius = (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight);
+
+ clampStartingPosition();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public void setOuterOpacity(float a) {
+ mOuterOpacity = a;
+ invalidateSelf();
+ }
+
+ @SuppressWarnings("unused")
+ public float getOuterOpacity() {
+ return mOuterOpacity;
+ }
+
+ @SuppressWarnings("unused")
+ public void setXGravity(float x) {
+ mTweenX = x;
+ invalidateSelf();
+ }
+
+ @SuppressWarnings("unused")
+ public float getXGravity() {
+ return mTweenX;
+ }
+
+ @SuppressWarnings("unused")
+ public void setYGravity(float y) {
+ mTweenY = y;
+ invalidateSelf();
+ }
+
+ @SuppressWarnings("unused")
+ public float getYGravity() {
+ return mTweenY;
+ }
+
+ /**
+ * Draws the ripple centered at (0,0) using the specified paint.
+ */
+ public boolean draw(Canvas c, Paint p) {
+ final boolean canUseHardware = c.isHardwareAccelerated();
+ if (mCanUseHardware != canUseHardware && mCanUseHardware) {
+ // We've switched from hardware to non-hardware mode. Panic.
+ cancelHardwareAnimations();
+ }
+ mCanUseHardware = canUseHardware;
+
+ final boolean hasContent;
+ if (canUseHardware && mHardwareAnimating) {
+ hasContent = drawHardware((HardwareCanvas) c);
+ } else {
+ hasContent = drawSoftware(c, p);
+ }
+
+ return hasContent;
+ }
+
+ private boolean drawHardware(HardwareCanvas c) {
+ // If we have any pending hardware animations, cancel any running
+ // animations and start those now.
+ final ArrayList<RenderNodeAnimator> pendingAnimations = mPendingAnimations;
+ final int N = pendingAnimations.size();
+ if (N > 0) {
+ cancelHardwareAnimations();
+
+ for (int i = 0; i < N; i++) {
+ pendingAnimations.get(i).setTarget(c);
+ pendingAnimations.get(i).start();
+ }
+
+ mRunningAnimations.addAll(pendingAnimations);
+ pendingAnimations.clear();
+ }
+
+ c.drawCircle(mPropOuterX, mPropOuterY, mPropOuterRadius, mPropOuterPaint);
+
+ return true;
+ }
+
+ private boolean drawSoftware(Canvas c, Paint p) {
+ boolean hasContent = false;
+
+ // Cache the paint alpha so we can restore it later.
+ final int paintAlpha = p.getAlpha();
+
+ final int outerAlpha = (int) (paintAlpha * mOuterOpacity + 0.5f);
+ if (outerAlpha > 0 && mOuterRadius > 0) {
+ p.setAlpha(outerAlpha);
+ p.setStyle(Style.FILL);
+ c.drawCircle(mOuterX, mOuterY, mOuterRadius, p);
+ hasContent = true;
+ }
+
+ p.setAlpha(paintAlpha);
+
+ return hasContent;
+ }
+
+ /**
+ * Returns the maximum bounds of the ripple relative to the ripple center.
+ */
+ public void getBounds(Rect bounds) {
+ final int outerX = (int) mOuterX;
+ final int outerY = (int) mOuterY;
+ final int r = (int) mOuterRadius;
+ bounds.set(outerX - r, outerY - r, outerX + r, outerY + r);
+ }
+
+ /**
+ * Specifies the starting position relative to the drawable bounds. No-op if
+ * the ripple has already entered.
+ */
+ public void move(float x, float y) {
+ mStartingX = x;
+ mStartingY = y;
+
+ clampStartingPosition();
+ }
+
+ /**
+ * Starts the enter animation.
+ */
+ public void enter() {
+ final int radiusDuration = (int)
+ (1000 * Math.sqrt(mOuterRadius / WAVE_TOUCH_DOWN_ACCELERATION * mDensity) + 0.5);
+ final int outerDuration = (int) (1000 * 1.0f / WAVE_OUTER_OPACITY_VELOCITY_MIN);
+
+ final ObjectAnimator cX = ObjectAnimator.ofFloat(this, "xGravity", 1);
+ cX.setAutoCancel(true);
+ cX.setDuration(radiusDuration);
+ cX.setInterpolator(LINEAR_INTERPOLATOR);
+ cX.setStartDelay(RIPPLE_ENTER_DELAY);
+
+ final ObjectAnimator cY = ObjectAnimator.ofFloat(this, "yGravity", 1);
+ cY.setAutoCancel(true);
+ cY.setDuration(radiusDuration);
+ cY.setInterpolator(LINEAR_INTERPOLATOR);
+ cY.setStartDelay(RIPPLE_ENTER_DELAY);
+
+ final ObjectAnimator outer = ObjectAnimator.ofFloat(this, "outerOpacity", 0, 1);
+ outer.setAutoCancel(true);
+ outer.setDuration(outerDuration);
+ outer.setInterpolator(LINEAR_INTERPOLATOR);
+
+ mAnimOuterOpacity = outer;
+ mAnimX = cX;
+ mAnimY = cY;
+
+ // Enter animations always run on the UI thread, since it's unlikely
+ // that anything interesting is happening until the user lifts their
+ // finger.
+ outer.start();
+ cX.start();
+ cY.start();
+ }
+
+ /**
+ * Starts the exit animation.
+ */
+ public void exit() {
+ cancelSoftwareAnimations();
+
+ // Scale the outer max opacity and opacity velocity based
+ // on the size of the outer radius.
+ final int opacityDuration = (int) (1000 / WAVE_OPACITY_DECAY_VELOCITY + 0.5f);
+ final float outerSizeInfluence = MathUtils.constrain(
+ (mOuterRadius - WAVE_OUTER_SIZE_INFLUENCE_MIN * mDensity)
+ / (WAVE_OUTER_SIZE_INFLUENCE_MAX * mDensity), 0, 1);
+ final float outerOpacityVelocity = MathUtils.lerp(WAVE_OUTER_OPACITY_VELOCITY_MIN,
+ WAVE_OUTER_OPACITY_VELOCITY_MAX, outerSizeInfluence);
+
+ // Determine at what time the inner and outer opacity intersect.
+ // inner(t) = mOpacity - t * WAVE_OPACITY_DECAY_VELOCITY / 1000
+ // outer(t) = mOuterOpacity + t * WAVE_OUTER_OPACITY_VELOCITY / 1000
+ final int outerInflection = Math.max(0, (int) (1000 * (1 - mOuterOpacity)
+ / (WAVE_OPACITY_DECAY_VELOCITY + outerOpacityVelocity) + 0.5f));
+ final int inflectionOpacity = (int) (255 * (mOuterOpacity + outerInflection
+ * outerOpacityVelocity * outerSizeInfluence / 1000) + 0.5f);
+
+ if (mCanUseHardware) {
+ exitHardware(opacityDuration, outerInflection, inflectionOpacity);
+ } else {
+ exitSoftware(opacityDuration, outerInflection, inflectionOpacity);
+ }
+ }
+
+ private void exitHardware(int opacityDuration, int outerInflection, int inflectionOpacity) {
+ mPendingAnimations.clear();
+
+ // TODO: Adjust background by starting position.
+ final float startX = MathUtils.lerp(
+ mClampedStartingX - mBounds.exactCenterX(), mOuterX, mTweenX);
+ final float startY = MathUtils.lerp(
+ mClampedStartingY - mBounds.exactCenterY(), mOuterY, mTweenY);
+
+ final Paint outerPaint = getTempPaint();
+ outerPaint.setAntiAlias(true);
+ outerPaint.setColor(mColor);
+ outerPaint.setAlpha((int) (255 * mOuterOpacity + 0.5f));
+ outerPaint.setStyle(Style.FILL);
+ mPropOuterPaint = CanvasProperty.createPaint(outerPaint);
+ mPropOuterRadius = CanvasProperty.createFloat(mOuterRadius);
+ mPropOuterX = CanvasProperty.createFloat(mOuterX);
+ mPropOuterY = CanvasProperty.createFloat(mOuterY);
+
+ final RenderNodeAnimator outerOpacityAnim;
+ if (outerInflection > 0) {
+ // Outer opacity continues to increase for a bit.
+ outerOpacityAnim = new RenderNodeAnimator(
+ mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, inflectionOpacity);
+ outerOpacityAnim.setDuration(outerInflection);
+ outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
+
+ // Chain the outer opacity exit animation.
+ final int outerDuration = opacityDuration - outerInflection;
+ if (outerDuration > 0) {
+ final RenderNodeAnimator outerFadeOutAnim = new RenderNodeAnimator(
+ mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, 0);
+ outerFadeOutAnim.setDuration(outerDuration);
+ outerFadeOutAnim.setInterpolator(LINEAR_INTERPOLATOR);
+ outerFadeOutAnim.setStartDelay(outerInflection);
+ outerFadeOutAnim.setStartValue(inflectionOpacity);
+ outerFadeOutAnim.addListener(mAnimationListener);
+
+ mPendingAnimations.add(outerFadeOutAnim);
+ } else {
+ outerOpacityAnim.addListener(mAnimationListener);
+ }
+ } else {
+ outerOpacityAnim = new RenderNodeAnimator(
+ mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, 0);
+ outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
+ outerOpacityAnim.setDuration(opacityDuration);
+ outerOpacityAnim.addListener(mAnimationListener);
+ }
+
+ mPendingAnimations.add(outerOpacityAnim);
+
+ mHardwareAnimating = true;
+
+ invalidateSelf();
+ }
+
+ private Paint getTempPaint() {
+ if (mTempPaint == null) {
+ mTempPaint = new Paint();
+ }
+ return mTempPaint;
+ }
+
+ private void exitSoftware(int opacityDuration, int outerInflection, int inflectionOpacity) {
+ final ObjectAnimator xAnim = ObjectAnimator.ofFloat(this, "xGravity", 1);
+ xAnim.setAutoCancel(true);
+ xAnim.setDuration(opacityDuration);
+ xAnim.setInterpolator(DECEL_INTERPOLATOR);
+
+ final ObjectAnimator yAnim = ObjectAnimator.ofFloat(this, "yGravity", 1);
+ yAnim.setAutoCancel(true);
+ yAnim.setDuration(opacityDuration);
+ yAnim.setInterpolator(DECEL_INTERPOLATOR);
+
+ final ObjectAnimator outerOpacityAnim;
+ if (outerInflection > 0) {
+ // Outer opacity continues to increase for a bit.
+ outerOpacityAnim = ObjectAnimator.ofFloat(this,
+ "outerOpacity", inflectionOpacity / 255.0f);
+ outerOpacityAnim.setAutoCancel(true);
+ outerOpacityAnim.setDuration(outerInflection);
+ outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
+
+ // Chain the outer opacity exit animation.
+ final int outerDuration = opacityDuration - outerInflection;
+ if (outerDuration > 0) {
+ outerOpacityAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ final ObjectAnimator outerFadeOutAnim = ObjectAnimator.ofFloat(
+ RippleBackground.this, "outerOpacity", 0);
+ outerFadeOutAnim.setAutoCancel(true);
+ outerFadeOutAnim.setDuration(outerDuration);
+ outerFadeOutAnim.setInterpolator(LINEAR_INTERPOLATOR);
+ outerFadeOutAnim.addListener(mAnimationListener);
+
+ mAnimOuterOpacity = outerFadeOutAnim;
+
+ outerFadeOutAnim.start();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ animation.removeListener(this);
+ }
+ });
+ } else {
+ outerOpacityAnim.addListener(mAnimationListener);
+ }
+ } else {
+ outerOpacityAnim = ObjectAnimator.ofFloat(this, "outerOpacity", 0);
+ outerOpacityAnim.setAutoCancel(true);
+ outerOpacityAnim.setDuration(opacityDuration);
+ outerOpacityAnim.addListener(mAnimationListener);
+ }
+
+ mAnimOuterOpacity = outerOpacityAnim;
+ mAnimX = xAnim;
+ mAnimY = yAnim;
+
+ outerOpacityAnim.start();
+ xAnim.start();
+ yAnim.start();
+ }
+
+ /**
+ * Cancel all animations.
+ */
+ public void cancel() {
+ cancelSoftwareAnimations();
+ cancelHardwareAnimations();
+ }
+
+ private void cancelSoftwareAnimations() {
+ if (mAnimOuterOpacity != null) {
+ mAnimOuterOpacity.cancel();
+ }
+
+ if (mAnimX != null) {
+ mAnimX.cancel();
+ }
+
+ if (mAnimY != null) {
+ mAnimY.cancel();
+ }
+ }
+
+ /**
+ * Cancels any running hardware animations.
+ */
+ private void cancelHardwareAnimations() {
+ final ArrayList<RenderNodeAnimator> runningAnimations = mRunningAnimations;
+ final int N = runningAnimations.size();
+ for (int i = 0; i < N; i++) {
+ runningAnimations.get(i).cancel();
+ }
+
+ runningAnimations.clear();
+ }
+
+ private void removeSelf() {
+ // The owner will invalidate itself.
+ mOwner.removeBackground(this);
+ }
+
+ private void invalidateSelf() {
+ mOwner.invalidateSelf();
+ }
+
+ private final AnimatorListenerAdapter mAnimationListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ removeSelf();
+ }
+ };
+
+ /**
+ * Interpolator with a smooth log deceleration
+ */
+ private static final class LogInterpolator implements TimeInterpolator {
+ @Override
+ public float getInterpolation(float input) {
+ return 1 - (float) Math.pow(400, -input * 1.4);
+ }
+ }
+}
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 0e719ee..0c9c558 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -120,8 +120,22 @@
/** The masking layer, e.g. the layer with id R.id.mask. */
private Drawable mMask;
- /** The current hotspot. May be actively animating or pending entry. */
- private Ripple mHotspot;
+ /** The current background. May be actively animating or pending entry. */
+ private RippleBackground mBackground;
+
+ /** Whether we expect to draw a background when visible. */
+ private boolean mBackgroundActive;
+
+ /** The current ripple. May be actively animating or pending entry. */
+ private Ripple mRipple;
+
+ /** Whether we expect to draw a ripple when visible. */
+ private boolean mRippleActive;
+
+ // Hotspot coordinates that are awaiting activation.
+ private float mPendingX;
+ private float mPendingY;
+ private boolean mHasPending;
/**
* Lazily-created array of actively animating ripples. Inactive ripples are
@@ -142,9 +156,6 @@
/** Whether bounds are being overridden. */
private boolean mOverrideBounds;
- /** Whether the hotspot is currently active (e.g. focused or pressed). */
- private boolean mActive;
-
/**
* Constructor used for drawable inflation.
*/
@@ -205,20 +216,26 @@
protected boolean onStateChange(int[] stateSet) {
super.onStateChange(stateSet);
- // TODO: This would make more sense in a StateListDrawable.
- boolean active = false;
boolean enabled = false;
+ boolean pressed = false;
+ boolean focused = false;
+
final int N = stateSet.length;
for (int i = 0; i < N; i++) {
if (stateSet[i] == R.attr.state_enabled) {
enabled = true;
}
if (stateSet[i] == R.attr.state_focused
- || stateSet[i] == R.attr.state_pressed) {
- active = true;
+ || stateSet[i] == R.attr.state_selected) {
+ focused = true;
+ }
+ if (stateSet[i] == R.attr.state_pressed) {
+ pressed = true;
}
}
- setActive(active && enabled);
+
+ setRippleActive(enabled && pressed);
+ setBackgroundActive(focused || (enabled && pressed));
// Update the paint color. Only applicable when animated in software.
if (mRipplePaint != null && mState.mColor != null) {
@@ -235,14 +252,24 @@
return false;
}
- private void setActive(boolean active) {
- if (mActive != active) {
- mActive = active;
-
+ private void setRippleActive(boolean active) {
+ if (mRippleActive != active) {
+ mRippleActive = active;
if (active) {
- activateHotspot();
+ activateRipple();
} else {
- removeHotspot();
+ removeRipple();
+ }
+ }
+ }
+
+ private void setBackgroundActive(boolean active) {
+ if (mBackgroundActive != active) {
+ mBackgroundActive = active;
+ if (active) {
+ activateBackground();
+ } else {
+ removeBackground();
}
}
}
@@ -261,11 +288,23 @@
@Override
public boolean setVisible(boolean visible, boolean restart) {
+ final boolean changed = super.setVisible(visible, restart);
+
if (!visible) {
clearHotspots();
+ } else if (changed) {
+ // If we just became visible, ensure the background and ripple
+ // visibilities are consistent with their internal states.
+ if (mRippleActive) {
+ activateRipple();
+ }
+
+ if (mBackgroundActive) {
+ activateBackground();
+ }
}
- return super.setVisible(visible, restart);
+ return changed;
}
/**
@@ -399,54 +438,101 @@
@Override
public void setHotspot(float x, float y) {
- if (mHotspot == null) {
- mHotspot = new Ripple(this, mHotspotBounds, x, y);
+ if (mRipple == null || mBackground == null) {
+ mPendingX = x;
+ mPendingY = y;
+ mHasPending = true;
+ }
- if (mActive) {
- activateHotspot();
- }
- } else {
- mHotspot.move(x, y);
+ if (mRipple != null) {
+ mRipple.move(x, y);
+ }
+
+ if (mBackground != null) {
+ mBackground.move(x, y);
}
}
/**
* Creates an active hotspot at the specified location.
*/
- private void activateHotspot() {
+ private void activateBackground() {
+ if (mBackground == null) {
+ final float x;
+ final float y;
+ if (mHasPending) {
+ mHasPending = false;
+ x = mPendingX;
+ y = mPendingY;
+ } else {
+ x = mHotspotBounds.exactCenterX();
+ y = mHotspotBounds.exactCenterY();
+ }
+ mBackground = new RippleBackground(this, mHotspotBounds, x, y);
+ }
+
+ final int color = mState.mColor.getColorForState(getState(), Color.TRANSPARENT);
+ mBackground.setup(mState.mMaxRadius, color, mDensity);
+ mBackground.enter();
+ }
+
+ private void removeBackground() {
+ if (mBackground != null) {
+ // Don't null out the background, we need it to draw!
+ mBackground.exit();
+ }
+ }
+
+ /**
+ * Creates an active hotspot at the specified location.
+ */
+ private void activateRipple() {
if (mAnimatingRipplesCount >= MAX_RIPPLES) {
// This should never happen unless the user is tapping like a maniac
// or there is a bug that's preventing ripples from being removed.
return;
}
- if (mHotspot == null) {
- final float x = mHotspotBounds.exactCenterX();
- final float y = mHotspotBounds.exactCenterY();
- mHotspot = new Ripple(this, mHotspotBounds, x, y);
+ if (mRipple == null) {
+ final float x;
+ final float y;
+ if (mHasPending) {
+ mHasPending = false;
+ x = mPendingX;
+ y = mPendingY;
+ } else {
+ x = mHotspotBounds.exactCenterX();
+ y = mHotspotBounds.exactCenterY();
+ }
+ mRipple = new Ripple(this, mHotspotBounds, x, y);
}
final int color = mState.mColor.getColorForState(getState(), Color.TRANSPARENT);
- mHotspot.setup(mState.mMaxRadius, color, mDensity);
- mHotspot.enter();
+ mRipple.setup(mState.mMaxRadius, color, mDensity);
+ mRipple.enter();
if (mAnimatingRipples == null) {
mAnimatingRipples = new Ripple[MAX_RIPPLES];
}
- mAnimatingRipples[mAnimatingRipplesCount++] = mHotspot;
+ mAnimatingRipples[mAnimatingRipplesCount++] = mRipple;
}
- private void removeHotspot() {
- if (mHotspot != null) {
- mHotspot.exit();
- mHotspot = null;
+ private void removeRipple() {
+ if (mRipple != null) {
+ mRipple.exit();
+ mRipple = null;
}
}
private void clearHotspots() {
- if (mHotspot != null) {
- mHotspot.cancel();
- mHotspot = null;
+ if (mRipple != null) {
+ mRipple.cancel();
+ mRipple = null;
+ }
+
+ if (mBackground != null) {
+ mBackground.cancel();
+ mBackground = null;
}
final int count = mAnimatingRipplesCount;
@@ -486,6 +572,10 @@
for (int i = 0; i < count; i++) {
ripples[i].onHotspotBoundsChanged();
}
+
+ if (mBackground != null) {
+ mBackground.onHotspotBoundsChanged();
+ }
}
/**
@@ -524,18 +614,28 @@
// Next, try to draw the ripples (into a layer if necessary). If we need
// to mask against the underlying content, set the xfermode to SRC_ATOP.
final PorterDuffXfermode xfermode = (hasMask || !drawNonMaskContent) ? SRC_OVER : SRC_ATOP;
- final int rippleLayer = drawRippleLayer(canvas, bounds, xfermode);
+
+ // If we have a background and a non-opaque mask, draw the masking layer.
+ final int backgroundLayer = drawBackgroundLayer(canvas, bounds, xfermode);
+ if (backgroundLayer >= 0) {
+ if (drawMask) {
+ drawMaskingLayer(canvas, bounds, DST_IN);
+ }
+ canvas.restoreToCount(backgroundLayer);
+ }
// If we have ripples and a non-opaque mask, draw the masking layer.
- if (rippleLayer >= 0 && drawMask) {
- drawMaskingLayer(canvas, bounds, DST_IN);
+ final int rippleLayer = drawRippleLayer(canvas, bounds, xfermode);
+ if (rippleLayer >= 0) {
+ if (drawMask) {
+ drawMaskingLayer(canvas, bounds, DST_IN);
+ }
+ canvas.restoreToCount(rippleLayer);
}
// Composite the layers if needed.
if (contentLayer >= 0) {
canvas.restoreToCount(contentLayer);
- } else if (rippleLayer >= 0) {
- canvas.restoreToCount(rippleLayer);
}
}
@@ -550,15 +650,20 @@
final int count = mAnimatingRipplesCount;
final int index = getRippleIndex(ripple);
if (index >= 0) {
- for (int i = index + 1; i < count; i++) {
- ripples[i - 1] = ripples[i];
- }
+ System.arraycopy(ripples, index + 1, ripples, index + 1 - 1, count - (index + 1));
ripples[count - 1] = null;
mAnimatingRipplesCount--;
invalidateSelf();
}
}
+ void removeBackground(RippleBackground background) {
+ if (mBackground == background) {
+ mBackground = null;
+ invalidateSelf();
+ }
+ }
+
private int getRippleIndex(Ripple ripple) {
final Ripple[] ripples = mAnimatingRipples;
final int count = mAnimatingRipplesCount;
@@ -577,7 +682,7 @@
// We don't need a layer if we don't expect to draw any ripples, we have
// an explicit mask, or if the non-mask content is all opaque.
boolean needsLayer = false;
- if (mAnimatingRipplesCount > 0 && mMask == null) {
+ if ((mAnimatingRipplesCount > 0 || mBackground != null) && mMask == null) {
for (int i = 0; i < count; i++) {
if (array[i].mId != R.id.mask
&& array[i].mDrawable.getOpacity() != PixelFormat.OPAQUE) {
@@ -601,12 +706,62 @@
return restoreToCount;
}
- private int drawRippleLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) {
- final int count = mAnimatingRipplesCount;
- if (count == 0) {
- return -1;
+ private int drawBackgroundLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) {
+ // Separate the ripple color and alpha channel. The alpha will be
+ // applied when we merge the ripples down to the canvas.
+ final int rippleARGB;
+ if (mState.mColor != null) {
+ rippleARGB = mState.mColor.getColorForState(getState(), Color.TRANSPARENT);
+ } else {
+ rippleARGB = Color.TRANSPARENT;
}
+ if (mRipplePaint == null) {
+ mRipplePaint = new Paint();
+ mRipplePaint.setAntiAlias(true);
+ }
+
+ final int rippleAlpha = Color.alpha(rippleARGB);
+ final Paint ripplePaint = mRipplePaint;
+ ripplePaint.setColor(rippleARGB);
+ ripplePaint.setAlpha(0xFF);
+
+ boolean drewRipples = false;
+ int restoreToCount = -1;
+ int restoreTranslate = -1;
+
+ // Draw background.
+ final RippleBackground background = mBackground;
+ if (background != null) {
+ // If we're masking the ripple layer, make sure we have a layer
+ // first. This will merge SRC_OVER (directly) onto the canvas.
+ final Paint maskingPaint = getMaskingPaint(mode);
+ maskingPaint.setAlpha(rippleAlpha);
+ restoreToCount = canvas.saveLayer(bounds.left, bounds.top,
+ bounds.right, bounds.bottom, maskingPaint);
+
+ restoreTranslate = canvas.save();
+ // Translate the canvas to the current hotspot bounds.
+ canvas.translate(mHotspotBounds.exactCenterX(), mHotspotBounds.exactCenterY());
+
+ drewRipples = background.draw(canvas, ripplePaint);
+ }
+
+ // Always restore the translation.
+ if (restoreTranslate >= 0) {
+ canvas.restoreToCount(restoreTranslate);
+ }
+
+ // If we created a layer with no content, merge it immediately.
+ if (restoreToCount >= 0 && !drewRipples) {
+ canvas.restoreToCount(restoreToCount);
+ restoreToCount = -1;
+ }
+
+ return restoreToCount;
+ }
+
+ private int drawRippleLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) {
// Separate the ripple color and alpha channel. The alpha will be
// applied when we merge the ripples down to the canvas.
final int rippleARGB;
@@ -631,6 +786,7 @@
int restoreTranslate = -1;
// Draw ripples and update the animating ripples array.
+ final int count = mAnimatingRipplesCount;
final Ripple[] ripples = mAnimatingRipples;
for (int i = 0; i < count; i++) {
final Ripple ripple = ripples[i];
@@ -705,6 +861,13 @@
drawingBounds.union(rippleBounds);
}
+ final RippleBackground background = mBackground;
+ if (background != null) {
+ background.getBounds(rippleBounds);
+ rippleBounds.offset(cX, cY);
+ drawingBounds.union(rippleBounds);
+ }
+
dirtyBounds.union(drawingBounds);
dirtyBounds.union(super.getDirtyBounds());
return dirtyBounds;
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 8993af8..050c268 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -44,6 +44,7 @@
import android.content.res.XmlResourceParser;
import android.database.ContentObserver;
import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiPlaybackClient;
import android.hardware.hdmi.HdmiTvClient;
import android.hardware.usb.UsbManager;
import android.media.MediaPlayer.OnCompletionListener;
@@ -144,7 +145,23 @@
private final Context mContext;
private final ContentResolver mContentResolver;
private final AppOpsManager mAppOps;
- private final boolean mVoiceCapable;
+
+ // the platform has no specific capabilities
+ private static final int PLATFORM_DEFAULT = 0;
+ // the platform is voice call capable (a phone)
+ private static final int PLATFORM_VOICE = 1;
+ // the platform is a television or a set-top box
+ private static final int PLATFORM_TELEVISION = 2;
+ // the platform type affects volume and silent mode behavior
+ private final int mPlatformType;
+
+ private boolean isPlatformVoice() {
+ return mPlatformType == PLATFORM_VOICE;
+ }
+
+ private boolean isPlatformTelevision() {
+ return mPlatformType == PLATFORM_TELEVISION;
+ }
/** The controller for the volume UI. */
private final VolumeController mVolumeController = new VolumeController();
@@ -243,9 +260,10 @@
* NOTE: do not create loops in aliases!
* Some streams alias to different streams according to device category (phone or tablet) or
* use case (in call vs off call...). See updateStreamVolumeAlias() for more details.
- * mStreamVolumeAlias contains the default aliases for a voice capable device (phone) and
- * STREAM_VOLUME_ALIAS_NON_VOICE for a non voice capable device (tablet).*/
- private final int[] STREAM_VOLUME_ALIAS = new int[] {
+ * mStreamVolumeAlias contains STREAM_VOLUME_ALIAS_VOICE aliases for a voice capable device
+ * (phone), STREAM_VOLUME_ALIAS_TELEVISION for a television or set-top box and
+ * STREAM_VOLUME_ALIAS_DEFAULT for other devices (e.g. tablets).*/
+ private final int[] STREAM_VOLUME_ALIAS_VOICE = new int[] {
AudioSystem.STREAM_VOICE_CALL, // STREAM_VOICE_CALL
AudioSystem.STREAM_RING, // STREAM_SYSTEM
AudioSystem.STREAM_RING, // STREAM_RING
@@ -257,7 +275,19 @@
AudioSystem.STREAM_RING, // STREAM_DTMF
AudioSystem.STREAM_MUSIC // STREAM_TTS
};
- private final int[] STREAM_VOLUME_ALIAS_NON_VOICE = new int[] {
+ private final int[] STREAM_VOLUME_ALIAS_TELEVISION = new int[] {
+ AudioSystem.STREAM_MUSIC, // STREAM_VOICE_CALL
+ AudioSystem.STREAM_MUSIC, // STREAM_SYSTEM
+ AudioSystem.STREAM_MUSIC, // STREAM_RING
+ AudioSystem.STREAM_MUSIC, // STREAM_MUSIC
+ AudioSystem.STREAM_MUSIC, // STREAM_ALARM
+ AudioSystem.STREAM_MUSIC, // STREAM_NOTIFICATION
+ AudioSystem.STREAM_MUSIC, // STREAM_BLUETOOTH_SCO
+ AudioSystem.STREAM_MUSIC, // STREAM_SYSTEM_ENFORCED
+ AudioSystem.STREAM_MUSIC, // STREAM_DTMF
+ AudioSystem.STREAM_MUSIC // STREAM_TTS
+ };
+ private final int[] STREAM_VOLUME_ALIAS_DEFAULT = new int[] {
AudioSystem.STREAM_VOICE_CALL, // STREAM_VOICE_CALL
AudioSystem.STREAM_MUSIC, // STREAM_SYSTEM
AudioSystem.STREAM_RING, // STREAM_RING
@@ -455,9 +485,12 @@
public final static int STREAM_REMOTE_MUSIC = -200;
// Devices for which the volume is fixed and VolumePanel slider should be disabled
- final int mFixedVolumeDevices = AudioSystem.DEVICE_OUT_HDMI |
+ int mFixedVolumeDevices = AudioSystem.DEVICE_OUT_HDMI |
AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET |
- AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET;
+ AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET |
+ AudioSystem.DEVICE_OUT_HDMI_ARC |
+ AudioSystem.DEVICE_OUT_SPDIF |
+ AudioSystem.DEVICE_OUT_AUX_LINE;
// TODO merge orientation and rotation
private final boolean mMonitorOrientation;
@@ -491,8 +524,16 @@
mContext = context;
mContentResolver = context.getContentResolver();
mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
- mVoiceCapable = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_voice_capable);
+
+ if (mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_voice_capable)) {
+ mPlatformType = PLATFORM_VOICE;
+ } else if (context.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_TELEVISION)) {
+ mPlatformType = PLATFORM_TELEVISION;
+ } else {
+ mPlatformType = PLATFORM_DEFAULT;
+ }
PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
mAudioEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleAudioEvent");
@@ -622,10 +663,15 @@
BluetoothProfile.A2DP);
}
- HdmiControlManager hdmiManager =
+ mHdmiManager =
(HdmiControlManager) mContext.getSystemService(Context.HDMI_CONTROL_SERVICE);
- // Null if device is not Tv.
- mHdmiTvClient = hdmiManager.getTvClient();
+ if (mHdmiManager != null) {
+ synchronized (mHdmiManager) {
+ mHdmiTvClient = mHdmiManager.getTvClient();
+ mHdmiPlaybackClient = mHdmiManager.getPlaybackClient();
+ mHdmiCecSink = false;
+ }
+ }
sendMsg(mAudioHandler,
MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED,
@@ -670,6 +716,14 @@
}
}
+ private void checkAllFixedVolumeDevices()
+ {
+ int numStreamTypes = AudioSystem.getNumStreamTypes();
+ for (int streamType = 0; streamType < numStreamTypes; streamType++) {
+ mStreamStates[streamType].checkFixedVolumeDevices();
+ }
+ }
+
private void createStreamStates() {
int numStreamTypes = AudioSystem.getNumStreamTypes();
VolumeStreamState[] streams = mStreamStates = new VolumeStreamState[numStreamTypes];
@@ -678,6 +732,7 @@
streams[i] = new VolumeStreamState(System.VOLUME_SETTINGS[mStreamVolumeAlias[i]], i);
}
+ checkAllFixedVolumeDevices();
checkAllAliasStreamVolumes();
}
@@ -702,19 +757,32 @@
private void updateStreamVolumeAlias(boolean updateVolumes) {
int dtmfStreamAlias;
- if (mVoiceCapable) {
- mStreamVolumeAlias = STREAM_VOLUME_ALIAS;
+
+ switch (mPlatformType) {
+ case PLATFORM_VOICE:
+ mStreamVolumeAlias = STREAM_VOLUME_ALIAS_VOICE;
dtmfStreamAlias = AudioSystem.STREAM_RING;
- } else {
- mStreamVolumeAlias = STREAM_VOLUME_ALIAS_NON_VOICE;
+ break;
+ case PLATFORM_TELEVISION:
+ mStreamVolumeAlias = STREAM_VOLUME_ALIAS_TELEVISION;
+ dtmfStreamAlias = AudioSystem.STREAM_MUSIC;
+ break;
+ default:
+ mStreamVolumeAlias = STREAM_VOLUME_ALIAS_DEFAULT;
dtmfStreamAlias = AudioSystem.STREAM_MUSIC;
}
- if (isInCommunication()) {
- dtmfStreamAlias = AudioSystem.STREAM_VOICE_CALL;
- mRingerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_DTMF);
+
+ if (isPlatformTelevision()) {
+ mRingerModeAffectedStreams = 0;
} else {
- mRingerModeAffectedStreams |= (1 << AudioSystem.STREAM_DTMF);
+ if (isInCommunication()) {
+ dtmfStreamAlias = AudioSystem.STREAM_VOICE_CALL;
+ mRingerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_DTMF);
+ } else {
+ mRingerModeAffectedStreams |= (1 << AudioSystem.STREAM_DTMF);
+ }
}
+
mStreamVolumeAlias[AudioSystem.STREAM_DTMF] = dtmfStreamAlias;
if (updateVolumes) {
mStreamStates[AudioSystem.STREAM_DTMF].setAllIndexes(mStreamStates[dtmfStreamAlias]);
@@ -768,7 +836,7 @@
if (ringerMode != ringerModeFromSettings) {
Settings.Global.putInt(cr, Settings.Global.MODE_RINGER, ringerMode);
}
- if (mUseFixedVolume) {
+ if (mUseFixedVolume || isPlatformTelevision()) {
ringerMode = AudioManager.RINGER_MODE_NORMAL;
}
synchronized(mSettingsLock) {
@@ -998,15 +1066,30 @@
// Check if volume update should be send to Hdmi system audio.
int newIndex = mStreamStates[streamType].getIndex(device);
- if (mHdmiTvClient != null &&
- streamTypeAlias == AudioSystem.STREAM_MUSIC &&
- (flags & AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME) == 0 &&
- oldIndex != newIndex) {
- int maxIndex = getStreamMaxVolume(streamType);
- synchronized (mHdmiTvClient) {
- if (mHdmiSystemAudioSupported) {
- mHdmiTvClient.setSystemAudioVolume(
- (oldIndex + 5) / 10, (newIndex + 5) / 10, maxIndex);
+ if (mHdmiManager != null) {
+ synchronized (mHdmiManager) {
+ if (mHdmiTvClient != null &&
+ streamTypeAlias == AudioSystem.STREAM_MUSIC &&
+ (flags & AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME) == 0 &&
+ oldIndex != newIndex) {
+ int maxIndex = getStreamMaxVolume(streamType);
+ synchronized (mHdmiTvClient) {
+ if (mHdmiSystemAudioSupported) {
+ mHdmiTvClient.setSystemAudioVolume(
+ (oldIndex + 5) / 10, (newIndex + 5) / 10, maxIndex);
+ }
+ }
+ }
+ // mHdmiCecSink true => mHdmiPlaybackClient != null
+ if (mHdmiCecSink &&
+ streamTypeAlias == AudioSystem.STREAM_MUSIC &&
+ oldIndex != newIndex) {
+ synchronized (mHdmiPlaybackClient) {
+ int keyCode = (direction == -1) ? KeyEvent.KEYCODE_VOLUME_DOWN :
+ KeyEvent.KEYCODE_VOLUME_UP;
+ mHdmiPlaybackClient.sendKeyEvent(keyCode, true);
+ mHdmiPlaybackClient.sendKeyEvent(keyCode, false);
+ }
}
}
}
@@ -1110,15 +1193,19 @@
}
}
- if (mHdmiTvClient != null &&
- streamTypeAlias == AudioSystem.STREAM_MUSIC &&
- (flags & AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME) == 0 &&
- oldIndex != index) {
- int maxIndex = getStreamMaxVolume(streamType);
- synchronized (mHdmiTvClient) {
- if (mHdmiSystemAudioSupported) {
- mHdmiTvClient.setSystemAudioVolume(
- (oldIndex + 5) / 10, (index + 5) / 10, maxIndex);
+ if (mHdmiManager != null) {
+ synchronized (mHdmiManager) {
+ if (mHdmiTvClient != null &&
+ streamTypeAlias == AudioSystem.STREAM_MUSIC &&
+ (flags & AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME) == 0 &&
+ oldIndex != index) {
+ int maxIndex = getStreamMaxVolume(streamType);
+ synchronized (mHdmiTvClient) {
+ if (mHdmiSystemAudioSupported) {
+ mHdmiTvClient.setSystemAudioVolume(
+ (oldIndex + 5) / 10, (index + 5) / 10, maxIndex);
+ }
+ }
}
}
}
@@ -1257,7 +1344,7 @@
// UI update and Broadcast Intent
private void sendVolumeUpdate(int streamType, int oldIndex, int index, int flags) {
- if (!mVoiceCapable && (streamType == AudioSystem.STREAM_RING)) {
+ if (!isPlatformVoice() && (streamType == AudioSystem.STREAM_RING)) {
streamType = AudioSystem.STREAM_NOTIFICATION;
}
@@ -1346,10 +1433,14 @@
}
if (isStreamAffectedByMute(streamType)) {
- if (streamType == AudioSystem.STREAM_MUSIC && mHdmiTvClient != null) {
- synchronized (mHdmiTvClient) {
- if (mHdmiSystemAudioSupported) {
- mHdmiTvClient.setSystemAudioMute(state);
+ if (mHdmiManager != null) {
+ synchronized (mHdmiManager) {
+ if (streamType == AudioSystem.STREAM_MUSIC && mHdmiTvClient != null) {
+ synchronized (mHdmiTvClient) {
+ if (mHdmiSystemAudioSupported) {
+ mHdmiTvClient.setSystemAudioMute(state);
+ }
+ }
}
}
}
@@ -1472,11 +1563,15 @@
/** @see AudioManager#getMasterStreamType() */
public int getMasterStreamType() {
- if (mVoiceCapable) {
- return AudioSystem.STREAM_RING;
- } else {
- return AudioSystem.STREAM_NOTIFICATION;
+ switch (mPlatformType) {
+ case PLATFORM_VOICE:
+ return AudioSystem.STREAM_RING;
+ case PLATFORM_TELEVISION:
+ return AudioSystem.STREAM_MUSIC;
+ default:
+ break;
}
+ return AudioSystem.STREAM_NOTIFICATION;
}
/** @see AudioManager#setMicrophoneMute(boolean) */
@@ -1504,7 +1599,7 @@
/** @see AudioManager#setRingerMode(int) */
public void setRingerMode(int ringerMode) {
- if (mUseFixedVolume) {
+ if (mUseFixedVolume || isPlatformTelevision()) {
return;
}
@@ -1534,7 +1629,7 @@
ringerMode == AudioManager.RINGER_MODE_NORMAL) {
// ring and notifications volume should never be 0 when not silenced
// on voice capable devices
- if (mVoiceCapable &&
+ if (isPlatformVoice() &&
mStreamVolumeAlias[streamType] == AudioSystem.STREAM_RING) {
synchronized (mStreamStates[streamType]) {
Set set = mStreamStates[streamType].mIndex.entrySet();
@@ -2041,6 +2136,7 @@
// muted by ringer mode have the correct volume
setRingerModeInt(getRingerMode(), false);
+ checkAllFixedVolumeDevices();
checkAllAliasStreamVolumes();
synchronized (mSafeMediaVolumeState) {
@@ -2760,11 +2856,18 @@
(1 << AudioSystem.STREAM_NOTIFICATION)|
(1 << AudioSystem.STREAM_SYSTEM);
- if (mVoiceCapable) {
- ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_MUSIC);
- } else {
- ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_MUSIC);
+ switch (mPlatformType) {
+ case PLATFORM_VOICE:
+ ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_MUSIC);
+ break;
+ case PLATFORM_TELEVISION:
+ ringerModeAffectedStreams = 0;
+ break;
+ default:
+ ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_MUSIC);
+ break;
}
+
synchronized (mCameraSoundForced) {
if (mCameraSoundForced) {
ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
@@ -2833,7 +2936,8 @@
}
private int getActiveStreamType(int suggestedStreamType) {
- if (mVoiceCapable) {
+ switch (mPlatformType) {
+ case PLATFORM_VOICE:
if (isInCommunication()) {
if (AudioSystem.getForceUse(AudioSystem.FOR_COMMUNICATION)
== AudioSystem.FORCE_BT_SCO) {
@@ -2863,12 +2967,26 @@
if (DEBUG_VOL)
Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC stream active");
return AudioSystem.STREAM_MUSIC;
- } else {
- if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Returning suggested type "
- + suggestedStreamType);
- return suggestedStreamType;
}
- } else {
+ break;
+ case PLATFORM_TELEVISION:
+ if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
+ if (isAfMusicActiveRecently(DEFAULT_STREAM_TYPE_OVERRIDE_DELAY_MS)) {
+ if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: forcing STREAM_MUSIC");
+ return AudioSystem.STREAM_MUSIC;
+ } else if (mMediaFocusControl.checkUpdateRemoteStateIfActive(
+ AudioSystem.STREAM_MUSIC)) {
+ if (DEBUG_VOL)
+ Log.v(TAG, "getActiveStreamType: Forcing STREAM_REMOTE_MUSIC");
+ return STREAM_REMOTE_MUSIC;
+ } else {
+ if (DEBUG_VOL) Log.v(TAG,
+ "getActiveStreamType: using STREAM_MUSIC as default");
+ return AudioSystem.STREAM_MUSIC;
+ }
+ }
+ break;
+ default:
if (isInCommunication()) {
if (AudioSystem.getForceUse(AudioSystem.FOR_COMMUNICATION)
== AudioSystem.FORCE_BT_SCO) {
@@ -2899,12 +3017,12 @@
"getActiveStreamType: using STREAM_NOTIFICATION as default");
return AudioSystem.STREAM_NOTIFICATION;
}
- } else {
- if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Returning suggested type "
- + suggestedStreamType);
- return suggestedStreamType;
}
+ break;
}
+ if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Returning suggested type "
+ + suggestedStreamType);
+ return suggestedStreamType;
}
private void broadcastRingerMode(int ringerMode) {
@@ -3098,13 +3216,7 @@
continue;
}
- // ignore settings for fixed volume devices: volume should always be at max or 0
- if ((mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_MUSIC) &&
- ((device & mFixedVolumeDevices) != 0)) {
- mIndex.put(device, (index != 0) ? mIndexMax : 0);
- } else {
- mIndex.put(device, getValidIndex(10 * index));
- }
+ mIndex.put(device, getValidIndex(10 * index));
}
}
}
@@ -3263,6 +3375,25 @@
return mStreamType;
}
+ public void checkFixedVolumeDevices() {
+ synchronized (VolumeStreamState.class) {
+ // ignore settings for fixed volume devices: volume should always be at max or 0
+ if (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_MUSIC) {
+ Set set = mIndex.entrySet();
+ Iterator i = set.iterator();
+ while (i.hasNext()) {
+ Map.Entry entry = (Map.Entry)i.next();
+ int device = ((Integer)entry.getKey()).intValue();
+ int index = ((Integer)entry.getValue()).intValue();
+ if (((device & mFixedVolumeDevices) != 0) && index != 0) {
+ entry.setValue(mIndexMax);
+ }
+ applyDeviceVolume(device);
+ }
+ }
+ }
+ }
+
private int getValidIndex(int index) {
if (index < 0) {
return 0;
@@ -3469,6 +3600,9 @@
if (mUseFixedVolume) {
return;
}
+ if (isPlatformTelevision() && (streamState.mStreamType != AudioSystem.STREAM_MUSIC)) {
+ return;
+ }
System.putIntForUser(mContentResolver,
streamState.getSettingNameForDevice(device),
(streamState.getIndex(device) + 5)/ 10,
@@ -3825,11 +3959,13 @@
mDockAudioMediaEnabled ?
AudioSystem.FORCE_ANALOG_DOCK : AudioSystem.FORCE_NONE);
}
-
- if (mHdmiTvClient != null) {
- setHdmiSystemAudioSupported(mHdmiSystemAudioSupported);
+ if (mHdmiManager != null) {
+ synchronized (mHdmiManager) {
+ if (mHdmiTvClient != null) {
+ setHdmiSystemAudioSupported(mHdmiSystemAudioSupported);
+ }
+ }
}
-
// indicate the end of reconfiguration phase to audio HAL
AudioSystem.setParameters("restarting=false");
break;
@@ -4275,6 +4411,27 @@
null,
MUSIC_ACTIVE_POLL_PERIOD_MS);
}
+ // Television devices without CEC service apply software volume on HDMI output
+ if (isPlatformTelevision() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) {
+ mFixedVolumeDevices |= AudioSystem.DEVICE_OUT_HDMI;
+ checkAllFixedVolumeDevices();
+ if (mHdmiManager != null) {
+ synchronized (mHdmiManager) {
+ if (mHdmiPlaybackClient != null) {
+ mHdmiCecSink = false;
+ mHdmiPlaybackClient.queryDisplayStatus(mHdmiDisplayStatusCallback);
+ }
+ }
+ }
+ }
+ } else {
+ if (isPlatformTelevision() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) {
+ if (mHdmiManager != null) {
+ synchronized (mHdmiManager) {
+ mHdmiCecSink = false;
+ }
+ }
+ }
}
if (!isUsb && (device != AudioSystem.DEVICE_IN_WIRED_HEADSET)) {
sendDeviceConnectionIntent(device, state, name);
@@ -4577,18 +4734,20 @@
if (cameraSoundForced != mCameraSoundForced) {
mCameraSoundForced = cameraSoundForced;
- VolumeStreamState s = mStreamStates[AudioSystem.STREAM_SYSTEM_ENFORCED];
- if (cameraSoundForced) {
- s.setAllIndexesToMax();
- mRingerModeAffectedStreams &=
- ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
- } else {
- s.setAllIndexes(mStreamStates[AudioSystem.STREAM_SYSTEM]);
- mRingerModeAffectedStreams |=
- (1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
+ if (!isPlatformTelevision()) {
+ VolumeStreamState s = mStreamStates[AudioSystem.STREAM_SYSTEM_ENFORCED];
+ if (cameraSoundForced) {
+ s.setAllIndexesToMax();
+ mRingerModeAffectedStreams &=
+ ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
+ } else {
+ s.setAllIndexes(mStreamStates[AudioSystem.STREAM_SYSTEM]);
+ mRingerModeAffectedStreams |=
+ (1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
+ }
+ // take new state into account for streams muted by ringer mode
+ setRingerModeInt(getRingerMode(), false);
}
- // take new state into account for streams muted by ringer mode
- setRingerModeInt(getRingerMode(), false);
sendMsg(mAudioHandler,
MSG_SET_FORCE_USE,
@@ -4804,27 +4963,57 @@
// to HdmiControlService so that audio recevier can handle volume change.
//==========================================================================================
+ private class MyDisplayStatusCallback implements HdmiPlaybackClient.DisplayStatusCallback {
+ public void onComplete(int status) {
+ if (mHdmiManager != null) {
+ synchronized (mHdmiManager) {
+ mHdmiCecSink = (status != HdmiControlManager.POWER_STATUS_UNKNOWN);
+ // Television devices without CEC service apply software volume on HDMI output
+ if (isPlatformTelevision() && !mHdmiCecSink) {
+ mFixedVolumeDevices &= ~AudioSystem.DEVICE_OUT_HDMI;
+ }
+ checkAllFixedVolumeDevices();
+ }
+ }
+ }
+ };
+
// If HDMI-CEC system audio is supported
private boolean mHdmiSystemAudioSupported = false;
// Set only when device is tv.
private HdmiTvClient mHdmiTvClient;
+ // true if the device has system feature PackageManager.FEATURE_TELEVISION.
+ // cached HdmiControlManager interface
+ private HdmiControlManager mHdmiManager;
+ // Set only when device is a set-top box.
+ private HdmiPlaybackClient mHdmiPlaybackClient;
+ // true if we are a set-top box, an HDMI sink is connected and it supports CEC.
+ private boolean mHdmiCecSink;
+
+ private MyDisplayStatusCallback mHdmiDisplayStatusCallback = new MyDisplayStatusCallback();
@Override
public int setHdmiSystemAudioSupported(boolean on) {
- if (mHdmiTvClient == null) {
- Log.w(TAG, "Only Hdmi-Cec enabled TV device supports system audio mode.");
- return AudioSystem.DEVICE_NONE;
- }
+ int device = AudioSystem.DEVICE_NONE;
+ if (mHdmiManager != null) {
+ synchronized (mHdmiManager) {
+ if (mHdmiTvClient == null) {
+ Log.w(TAG, "Only Hdmi-Cec enabled TV device supports system audio mode.");
+ return device;
+ }
- synchronized (mHdmiTvClient) {
- if (mHdmiSystemAudioSupported == on) {
- return AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC);
+ synchronized (mHdmiTvClient) {
+ if (mHdmiSystemAudioSupported != on) {
+ mHdmiSystemAudioSupported = on;
+ AudioSystem.setForceUse(AudioSystem.FOR_HDMI_SYSTEM_AUDIO,
+ on ? AudioSystem.FORCE_HDMI_SYSTEM_AUDIO_ENFORCED :
+ AudioSystem.FORCE_NONE);
+ }
+ device = AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC);
+ }
}
- mHdmiSystemAudioSupported = on;
- AudioSystem.setForceUse(AudioSystem.FOR_HDMI_SYSTEM_AUDIO,
- on ? AudioSystem.FORCE_HDMI_SYSTEM_AUDIO_ENFORCED : AudioSystem.FORCE_NONE);
}
- return AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC);
+ return device;
}
//==========================================================================================
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index f41c6dd..d5111f4 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -744,12 +744,12 @@
* session.
*/
void release() {
+ removeOverlayView(true);
onRelease();
if (mSurface != null) {
mSurface.release();
mSurface = null;
}
- removeOverlayView(true);
}
/**
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c0ac023..fed68f9 100755
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -46,8 +46,8 @@
import android.service.voice.IVoiceInteractionSession;
import android.util.ArrayMap;
import android.util.ArraySet;
-
import android.util.SparseIntArray;
+
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IAppOpsService;
@@ -222,7 +222,11 @@
public final class ActivityManagerService extends ActivityManagerNative
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
+
private static final String USER_DATA_DIR = "/data/user/";
+ // File that stores last updated system version and called preboot receivers
+ static final String CALLED_PRE_BOOTS_FILENAME = "called_pre_boots.dat";
+
static final String TAG = "ActivityManager";
static final String TAG_MU = "ActivityManagerServiceMU";
static final boolean DEBUG = false;
@@ -354,6 +358,8 @@
static final int ALLOW_NON_FULL_IN_PROFILE = 1;
static final int ALLOW_FULL_ONLY = 2;
+ static final int LAST_PREBOOT_DELIVERED_FILE_VERSION = 10000;
+
/** All system services */
SystemServiceManager mSystemServiceManager;
@@ -9947,12 +9953,10 @@
private static File getCalledPreBootReceiversFile() {
File dataDir = Environment.getDataDirectory();
File systemDir = new File(dataDir, "system");
- File fname = new File(systemDir, "called_pre_boots.dat");
+ File fname = new File(systemDir, CALLED_PRE_BOOTS_FILENAME);
return fname;
}
- static final int LAST_DONE_VERSION = 10000;
-
private static ArrayList<ComponentName> readLastDonePreBootReceivers() {
ArrayList<ComponentName> lastDoneReceivers = new ArrayList<ComponentName>();
File file = getCalledPreBootReceiversFile();
@@ -9961,7 +9965,7 @@
fis = new FileInputStream(file);
DataInputStream dis = new DataInputStream(new BufferedInputStream(fis, 2048));
int fvers = dis.readInt();
- if (fvers == LAST_DONE_VERSION) {
+ if (fvers == LAST_PREBOOT_DELIVERED_FILE_VERSION) {
String vers = dis.readUTF();
String codename = dis.readUTF();
String build = dis.readUTF();
@@ -9990,16 +9994,15 @@
}
return lastDoneReceivers;
}
-
+
private static void writeLastDonePreBootReceivers(ArrayList<ComponentName> list) {
File file = getCalledPreBootReceiversFile();
FileOutputStream fos = null;
DataOutputStream dos = null;
try {
- Slog.i(TAG, "Writing new set of last done pre-boot receivers...");
fos = new FileOutputStream(file);
dos = new DataOutputStream(new BufferedOutputStream(fos, 2048));
- dos.writeInt(LAST_DONE_VERSION);
+ dos.writeInt(LAST_PREBOOT_DELIVERED_FILE_VERSION);
dos.writeUTF(android.os.Build.VERSION.RELEASE);
dos.writeUTF(android.os.Build.VERSION.CODENAME);
dos.writeUTF(android.os.Build.VERSION.INCREMENTAL);
@@ -10023,11 +10026,92 @@
}
}
}
-
+
+ private boolean deliverPreBootCompleted(final Runnable onFinishCallback,
+ ArrayList<ComponentName> doneReceivers, int userId) {
+ boolean waitingUpdate = false;
+ Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
+ List<ResolveInfo> ris = null;
+ try {
+ ris = AppGlobals.getPackageManager().queryIntentReceivers(
+ intent, null, 0, userId);
+ } catch (RemoteException e) {
+ }
+ if (ris != null) {
+ for (int i=ris.size()-1; i>=0; i--) {
+ if ((ris.get(i).activityInfo.applicationInfo.flags
+ &ApplicationInfo.FLAG_SYSTEM) == 0) {
+ ris.remove(i);
+ }
+ }
+ intent.addFlags(Intent.FLAG_RECEIVER_BOOT_UPGRADE);
+
+ // For User 0, load the version number. When delivering to a new user, deliver
+ // to all receivers.
+ if (userId == UserHandle.USER_OWNER) {
+ ArrayList<ComponentName> lastDoneReceivers = readLastDonePreBootReceivers();
+ for (int i=0; i<ris.size(); i++) {
+ ActivityInfo ai = ris.get(i).activityInfo;
+ ComponentName comp = new ComponentName(ai.packageName, ai.name);
+ if (lastDoneReceivers.contains(comp)) {
+ // We already did the pre boot receiver for this app with the current
+ // platform version, so don't do it again...
+ ris.remove(i);
+ i--;
+ // ...however, do keep it as one that has been done, so we don't
+ // forget about it when rewriting the file of last done receivers.
+ doneReceivers.add(comp);
+ }
+ }
+ }
+
+ // If primary user, send broadcast to all available users, else just to userId
+ final int[] users = userId == UserHandle.USER_OWNER ? getUsersLocked()
+ : new int[] { userId };
+ for (int i = 0; i < ris.size(); i++) {
+ ActivityInfo ai = ris.get(i).activityInfo;
+ ComponentName comp = new ComponentName(ai.packageName, ai.name);
+ doneReceivers.add(comp);
+ intent.setComponent(comp);
+ for (int j=0; j<users.length; j++) {
+ IIntentReceiver finisher = null;
+ // On last receiver and user, set up a completion callback
+ if (i == ris.size() - 1 && j == users.length - 1 && onFinishCallback != null) {
+ finisher = new IIntentReceiver.Stub() {
+ public void performReceive(Intent intent, int resultCode,
+ String data, Bundle extras, boolean ordered,
+ boolean sticky, int sendingUser) {
+ // The raw IIntentReceiver interface is called
+ // with the AM lock held, so redispatch to
+ // execute our code without the lock.
+ mHandler.post(onFinishCallback);
+ }
+ };
+ }
+ Slog.i(TAG, "Sending system update to " + intent.getComponent()
+ + " for user " + users[j]);
+ broadcastIntentLocked(null, null, intent, null, finisher,
+ 0, null, null, null, AppOpsManager.OP_NONE,
+ true, false, MY_PID, Process.SYSTEM_UID,
+ users[j]);
+ if (finisher != null) {
+ waitingUpdate = true;
+ }
+ }
+ }
+ }
+
+ return waitingUpdate;
+ }
+
public void systemReady(final Runnable goingCallback) {
synchronized(this) {
if (mSystemReady) {
- if (goingCallback != null) goingCallback.run();
+ // If we're done calling all the receivers, run the next "boot phase" passed in
+ // by the SystemServer
+ if (goingCallback != null) {
+ goingCallback.run();
+ }
return;
}
@@ -10048,82 +10132,20 @@
if (mWaitingUpdate) {
return;
}
- Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
- List<ResolveInfo> ris = null;
- try {
- ris = AppGlobals.getPackageManager().queryIntentReceivers(
- intent, null, 0, 0);
- } catch (RemoteException e) {
- }
- if (ris != null) {
- for (int i=ris.size()-1; i>=0; i--) {
- if ((ris.get(i).activityInfo.applicationInfo.flags
- &ApplicationInfo.FLAG_SYSTEM) == 0) {
- ris.remove(i);
+ final ArrayList<ComponentName> doneReceivers = new ArrayList<ComponentName>();
+ mWaitingUpdate = deliverPreBootCompleted(new Runnable() {
+ public void run() {
+ synchronized (ActivityManagerService.this) {
+ mDidUpdate = true;
}
+ writeLastDonePreBootReceivers(doneReceivers);
+ showBootMessage(mContext.getText(
+ R.string.android_upgrading_complete),
+ false);
+ systemReady(goingCallback);
}
- intent.addFlags(Intent.FLAG_RECEIVER_BOOT_UPGRADE);
+ }, doneReceivers, UserHandle.USER_OWNER);
- ArrayList<ComponentName> lastDoneReceivers = readLastDonePreBootReceivers();
-
- final ArrayList<ComponentName> doneReceivers = new ArrayList<ComponentName>();
- for (int i=0; i<ris.size(); i++) {
- ActivityInfo ai = ris.get(i).activityInfo;
- ComponentName comp = new ComponentName(ai.packageName, ai.name);
- if (lastDoneReceivers.contains(comp)) {
- // We already did the pre boot receiver for this app with the current
- // platform version, so don't do it again...
- ris.remove(i);
- i--;
- // ...however, do keep it as one that has been done, so we don't
- // forget about it when rewriting the file of last done receivers.
- doneReceivers.add(comp);
- }
- }
-
- final int[] users = getUsersLocked();
- for (int i=0; i<ris.size(); i++) {
- ActivityInfo ai = ris.get(i).activityInfo;
- ComponentName comp = new ComponentName(ai.packageName, ai.name);
- doneReceivers.add(comp);
- intent.setComponent(comp);
- for (int j=0; j<users.length; j++) {
- IIntentReceiver finisher = null;
- if (i == ris.size()-1 && j == users.length-1) {
- finisher = new IIntentReceiver.Stub() {
- public void performReceive(Intent intent, int resultCode,
- String data, Bundle extras, boolean ordered,
- boolean sticky, int sendingUser) {
- // The raw IIntentReceiver interface is called
- // with the AM lock held, so redispatch to
- // execute our code without the lock.
- mHandler.post(new Runnable() {
- public void run() {
- synchronized (ActivityManagerService.this) {
- mDidUpdate = true;
- }
- writeLastDonePreBootReceivers(doneReceivers);
- showBootMessage(mContext.getText(
- R.string.android_upgrading_complete),
- false);
- systemReady(goingCallback);
- }
- });
- }
- };
- }
- Slog.i(TAG, "Sending system update to " + intent.getComponent()
- + " for user " + users[j]);
- broadcastIntentLocked(null, null, intent, null, finisher,
- 0, null, null, null, AppOpsManager.OP_NONE,
- true, false, MY_PID, Process.SYSTEM_UID,
- users[j]);
- if (finisher != null) {
- mWaitingUpdate = true;
- }
- }
- }
- }
if (mWaitingUpdate) {
return;
}
@@ -17156,6 +17178,7 @@
}
if (needStart) {
+ // Send USER_STARTED broadcast
Intent intent = new Intent(Intent.ACTION_USER_STARTED);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
| Intent.FLAG_RECEIVER_FOREGROUND);
@@ -17167,6 +17190,11 @@
if ((userInfo.flags&UserInfo.FLAG_INITIALIZED) == 0) {
if (userId != UserHandle.USER_OWNER) {
+ // Send PRE_BOOT_COMPLETED broadcasts for this new user
+ final ArrayList<ComponentName> doneReceivers
+ = new ArrayList<ComponentName>();
+ deliverPreBootCompleted(null, doneReceivers, userId);
+
Intent intent = new Intent(Intent.ACTION_USER_INITIALIZE);
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
broadcastIntentLocked(null, null, intent, null,
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 013e9fe..fa8626f 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -94,7 +94,6 @@
private final INetworkManagementService mNMService;
private final INetworkStatsService mStatsService;
- private final ConnectivityManager mConnManager;
private Looper mLooper;
private HashMap<String, TetherInterfaceSM> mIfaces; // all tethered/tetherable ifaces
@@ -135,7 +134,6 @@
mContext = context;
mNMService = nmService;
mStatsService = statsService;
- mConnManager = (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
mLooper = looper;
mPublicSync = new Object();
@@ -175,6 +173,12 @@
mDefaultDnsServers[1] = DNS_DEFAULT_SERVER2;
}
+ // We can't do this once in the Tethering() constructor and cache the value, because the
+ // CONNECTIVITY_SERVICE is registered only after the Tethering() constructor has completed.
+ private ConnectivityManager getConnectivityManager() {
+ return (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+ }
+
void updateConfiguration() {
String[] tetherableUsbRegexs = mContext.getResources().getStringArray(
com.android.internal.R.array.config_tether_usb_regexs);
@@ -366,7 +370,7 @@
// TODO - move all private methods used only by the state machine into the state machine
// to clarify what needs synchronized protection.
private void sendTetherStateChangedBroadcast() {
- if (!mConnManager.isTetheringSupported()) return;
+ if (!getConnectivityManager().isTetheringSupported()) return;
ArrayList<String> availableList = new ArrayList<String>();
ArrayList<String> activeList = new ArrayList<String>();
@@ -1183,8 +1187,8 @@
int result = PhoneConstants.APN_REQUEST_FAILED;
String enableString = enableString(apnType);
if (enableString == null) return false;
- result = mConnManager.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
- enableString);
+ result = getConnectivityManager().startUsingNetworkFeature(
+ ConnectivityManager.TYPE_MOBILE, enableString);
switch (result) {
case PhoneConstants.APN_ALREADY_ACTIVE:
case PhoneConstants.APN_REQUEST_STARTED:
@@ -1205,8 +1209,8 @@
// ignore pending renewal requests
++mCurrentConnectionSequence;
if (mMobileApnReserved != ConnectivityManager.TYPE_NONE) {
- mConnManager.stopUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
- enableString(mMobileApnReserved));
+ getConnectivityManager().stopUsingNetworkFeature(
+ ConnectivityManager.TYPE_MOBILE, enableString(mMobileApnReserved));
mMobileApnReserved = ConnectivityManager.TYPE_NONE;
}
return true;
@@ -1269,7 +1273,8 @@
}
for (Integer netType : mUpstreamIfaceTypes) {
- NetworkInfo info = mConnManager.getNetworkInfo(netType.intValue());
+ NetworkInfo info =
+ getConnectivityManager().getNetworkInfo(netType.intValue());
if ((info != null) && info.isConnected()) {
upType = netType.intValue();
break;
@@ -1307,7 +1312,8 @@
sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS);
}
} else {
- LinkProperties linkProperties = mConnManager.getLinkProperties(upType);
+ LinkProperties linkProperties =
+ getConnectivityManager().getLinkProperties(upType);
if (linkProperties != null) {
// Find the interface with the default IPv4 route. It may be the
// interface described by linkProperties, or one of the interfaces
diff --git a/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java b/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java
index 397ef13..051ed0e 100644
--- a/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java
+++ b/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java
@@ -34,8 +34,10 @@
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* This test is intended to measure the amount of memory applications use when
@@ -57,11 +59,12 @@
private static final String TAG = "MemoryUsageInstrumentation";
private static final String KEY_APPS = "apps";
-
+ private static final String KEY_PROCS = "persistent";
+ private static final String LAUNCHER_KEY = "launcher";
private Map<String, Intent> mNameToIntent;
private Map<String, String> mNameToProcess;
private Map<String, String> mNameToResultKey;
-
+ private Set<String> mPersistentProcesses;
private IActivityManager mAm;
public void testMemory() {
@@ -75,35 +78,61 @@
Bundle results = new Bundle();
for (String app : mNameToResultKey.keySet()) {
- String processName;
- try {
- processName = startApp(app);
- measureMemory(app, processName, results);
- closeApp();
- } catch (NameNotFoundException e) {
- Log.i(TAG, "Application " + app + " not found");
+ if (!mPersistentProcesses.contains(app)) {
+ String processName;
+ try {
+ processName = startApp(app);
+ measureMemory(app, processName, results);
+ closeApp();
+ } catch (NameNotFoundException e) {
+ Log.i(TAG, "Application " + app + " not found");
+ }
+ } else {
+ measureMemory(app, app, results);
}
-
}
instrumentation.sendStatus(0, results);
}
- private void parseArgs(Bundle args) {
- mNameToResultKey = new HashMap<String, String>();
- String appList = args.getString(KEY_APPS);
+ private String getLauncherPackageName() {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.addCategory(Intent.CATEGORY_HOME);
+ ResolveInfo resolveInfo = getInstrumentation().getContext().
+ getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
+ return resolveInfo.activityInfo.packageName;
+ }
- if (appList == null)
- return;
-
- String appNames[] = appList.split("\\|");
- for (String pair : appNames) {
+ private Map<String, String> parseListToMap(String list) {
+ Map<String, String> map = new HashMap<String, String>();
+ String names[] = list.split("\\|");
+ for (String pair : names) {
String[] parts = pair.split("\\^");
if (parts.length != 2) {
Log.e(TAG, "The apps key is incorectly formatted");
fail();
}
+ map.put(parts[0], parts[1]);
+ }
+ return map;
+ }
- mNameToResultKey.put(parts[0], parts[1]);
+ private void parseArgs(Bundle args) {
+ mNameToResultKey = new HashMap<String, String>();
+ mPersistentProcesses = new HashSet<String>();
+ String appList = args.getString(KEY_APPS);
+ String procList = args.getString(KEY_PROCS);
+ String mLauncherPackageName = getLauncherPackageName();
+ mPersistentProcesses.add(mLauncherPackageName);
+ mNameToResultKey.put(mLauncherPackageName, LAUNCHER_KEY);
+ if (appList == null && procList == null)
+ return;
+ if (appList != null) {
+ mNameToResultKey.putAll(parseListToMap(appList));
+ }
+ if (procList != null) {
+ Map<String, String> procMap = parseListToMap(procList);
+ mPersistentProcesses.addAll(procMap.keySet());
+ mNameToResultKey.putAll(procMap);
}
}
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index b482697..94e9a5d 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -357,6 +357,7 @@
* @hide
* Uid of last app modifying the configuration
*/
+ @SystemApi
public int lastUpdateUid;
/**