diff options
Diffstat (limited to 'core/java')
504 files changed, 35177 insertions, 13466 deletions
diff --git a/core/java/android/accounts/AccountAuthenticatorActivity.java b/core/java/android/accounts/AccountAuthenticatorActivity.java index 6a55ddf..f9284e6 100644 --- a/core/java/android/accounts/AccountAuthenticatorActivity.java +++ b/core/java/android/accounts/AccountAuthenticatorActivity.java @@ -17,7 +17,6 @@ package android.accounts; import android.app.Activity; -import android.content.Intent; import android.os.Bundle; /** diff --git a/core/java/android/accounts/AccountManagerFuture.java b/core/java/android/accounts/AccountManagerFuture.java index a1ab00c..af00a08 100644 --- a/core/java/android/accounts/AccountManagerFuture.java +++ b/core/java/android/accounts/AccountManagerFuture.java @@ -15,10 +15,7 @@ */ package android.accounts; -import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeoutException; import java.io.IOException; /** diff --git a/core/java/android/accounts/ChooseAccountActivity.java b/core/java/android/accounts/ChooseAccountActivity.java index bfbae24..242b3ea 100644 --- a/core/java/android/accounts/ChooseAccountActivity.java +++ b/core/java/android/accounts/ChooseAccountActivity.java @@ -100,7 +100,7 @@ public class ChooseAccountActivity extends Activity { try { AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType); Context authContext = createPackageContext(desc.packageName, 0); - icon = authContext.getResources().getDrawable(desc.iconId); + icon = authContext.getDrawable(desc.iconId); } catch (PackageManager.NameNotFoundException e) { // Nothing we can do much here, just log if (Log.isLoggable(TAG, Log.WARN)) { diff --git a/core/java/android/accounts/ChooseAccountTypeActivity.java b/core/java/android/accounts/ChooseAccountTypeActivity.java index acc8549..a3222d8 100644 --- a/core/java/android/accounts/ChooseAccountTypeActivity.java +++ b/core/java/android/accounts/ChooseAccountTypeActivity.java @@ -129,7 +129,7 @@ public class ChooseAccountTypeActivity extends Activity { Drawable icon = null; try { Context authContext = createPackageContext(desc.packageName, 0); - icon = authContext.getResources().getDrawable(desc.iconId); + icon = authContext.getDrawable(desc.iconId); final CharSequence sequence = authContext.getResources().getText(desc.labelId); if (sequence != null) { name = sequence.toString(); diff --git a/core/java/android/accounts/GrantCredentialsPermissionActivity.java b/core/java/android/accounts/GrantCredentialsPermissionActivity.java index 8b01c6a..12b2b9c 100644 --- a/core/java/android/accounts/GrantCredentialsPermissionActivity.java +++ b/core/java/android/accounts/GrantCredentialsPermissionActivity.java @@ -16,7 +16,6 @@ package android.accounts; import android.app.Activity; -import android.content.pm.RegisteredServicesCache; import android.content.res.Resources; import android.os.Bundle; import android.widget.TextView; @@ -30,7 +29,6 @@ import android.text.TextUtils; import com.android.internal.R; import java.io.IOException; -import java.net.Authenticator; /** * @hide diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java index d753e32..20236aa 100644 --- a/core/java/android/animation/AnimatorInflater.java +++ b/core/java/android/animation/AnimatorInflater.java @@ -215,7 +215,7 @@ public class AnimatorInflater { (toType <= TypedValue.TYPE_LAST_COLOR_INT))) { // special case for colors: ignore valueType and get ints getFloats = false; - anim.setEvaluator(new ArgbEvaluator()); + evaluator = ArgbEvaluator.getInstance(); } if (getFloats) { diff --git a/core/java/android/animation/ArgbEvaluator.java b/core/java/android/animation/ArgbEvaluator.java index 717a3d9..ed07195 100644 --- a/core/java/android/animation/ArgbEvaluator.java +++ b/core/java/android/animation/ArgbEvaluator.java @@ -21,6 +21,19 @@ package android.animation; * values that represent ARGB colors. */ public class ArgbEvaluator implements TypeEvaluator { + private static final ArgbEvaluator sInstance = new ArgbEvaluator(); + + /** + * Returns an instance of <code>ArgbEvaluator</code> that may be used in + * {@link ValueAnimator#setEvaluator(TypeEvaluator)}. The same instance may + * be used in multiple <code>Animator</code>s because it holds no state. + * @return An instance of <code>ArgbEvalutor</code>. + * + * @hide + */ + public static ArgbEvaluator getInstance() { + return sInstance; + } /** * This function returns the calculated in-between value for a color diff --git a/core/java/android/animation/FloatArrayEvaluator.java b/core/java/android/animation/FloatArrayEvaluator.java new file mode 100644 index 0000000..9ae1197 --- /dev/null +++ b/core/java/android/animation/FloatArrayEvaluator.java @@ -0,0 +1,77 @@ +/* + * 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.animation; + +/** + * This evaluator can be used to perform type interpolation between <code>float[]</code> values. + * Each index into the array is treated as a separate value to interpolate. For example, + * evaluating <code>{100, 200}</code> and <code>{300, 400}</code> will interpolate the value at + * the first index between 100 and 300 and the value at the second index value between 200 and 400. + */ +public class FloatArrayEvaluator implements TypeEvaluator<float[]> { + + private float[] mArray; + + /** + * Create a FloatArrayEvaluator that does not reuse the animated value. Care must be taken + * when using this option because on every evaluation a new <code>float[]</code> will be + * allocated. + * + * @see #FloatArrayEvaluator(float[]) + */ + public FloatArrayEvaluator() { + } + + /** + * Create a FloatArrayEvaluator that reuses <code>reuseArray</code> for every evaluate() call. + * Caution must be taken to ensure that the value returned from + * {@link android.animation.ValueAnimator#getAnimatedValue()} is not cached, modified, or + * used across threads. The value will be modified on each <code>evaluate()</code> call. + * + * @param reuseArray The array to modify and return from <code>evaluate</code>. + */ + public FloatArrayEvaluator(float[] reuseArray) { + mArray = reuseArray; + } + + /** + * Interpolates the value at each index by the fraction. If + * {@link #FloatArrayEvaluator(float[])} was used to construct this object, + * <code>reuseArray</code> will be returned, otherwise a new <code>float[]</code> + * will be returned. + * + * @param fraction The fraction from the starting to the ending values + * @param startValue The start value. + * @param endValue The end value. + * @return A <code>float[]</code> where each element is an interpolation between + * the same index in startValue and endValue. + */ + @Override + public float[] evaluate(float fraction, float[] startValue, float[] endValue) { + float[] array = mArray; + if (array == null) { + array = new float[startValue.length]; + } + + for (int i = 0; i < array.length; i++) { + float start = startValue[i]; + float end = endValue[i]; + array[i] = start + (fraction * (end - start)); + } + return array; + } +} diff --git a/core/java/android/animation/IntArrayEvaluator.java b/core/java/android/animation/IntArrayEvaluator.java new file mode 100644 index 0000000..d7f10f3 --- /dev/null +++ b/core/java/android/animation/IntArrayEvaluator.java @@ -0,0 +1,75 @@ +/* + * 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.animation; + +/** + * This evaluator can be used to perform type interpolation between <code>int[]</code> values. + * Each index into the array is treated as a separate value to interpolate. For example, + * evaluating <code>{100, 200}</code> and <code>{300, 400}</code> will interpolate the value at + * the first index between 100 and 300 and the value at the second index value between 200 and 400. + */ +public class IntArrayEvaluator implements TypeEvaluator<int[]> { + + private int[] mArray; + + /** + * Create an IntArrayEvaluator that does not reuse the animated value. Care must be taken + * when using this option because on every evaluation a new <code>int[]</code> will be + * allocated. + * + * @see #IntArrayEvaluator(int[]) + */ + public IntArrayEvaluator() { + } + + /** + * Create an IntArrayEvaluator that reuses <code>reuseArray</code> for every evaluate() call. + * Caution must be taken to ensure that the value returned from + * {@link android.animation.ValueAnimator#getAnimatedValue()} is not cached, modified, or + * used across threads. The value will be modified on each <code>evaluate()</code> call. + * + * @param reuseArray The array to modify and return from <code>evaluate</code>. + */ + public IntArrayEvaluator(int[] reuseArray) { + mArray = reuseArray; + } + + /** + * Interpolates the value at each index by the fraction. If {@link #IntArrayEvaluator(int[])} + * was used to construct this object, <code>reuseArray</code> will be returned, otherwise + * a new <code>int[]</code> will be returned. + * + * @param fraction The fraction from the starting to the ending values + * @param startValue The start value. + * @param endValue The end value. + * @return An <code>int[]</code> where each element is an interpolation between + * the same index in startValue and endValue. + */ + @Override + public int[] evaluate(float fraction, int[] startValue, int[] endValue) { + int[] array = mArray; + if (array == null) { + array = new int[startValue.length]; + } + for (int i = 0; i < array.length; i++) { + int start = startValue[i]; + int end = endValue[i]; + array[i] = (int) (start + (fraction * (end - start))); + } + return array; + } +} diff --git a/core/java/android/animation/ObjectAnimator.java b/core/java/android/animation/ObjectAnimator.java index 9c88ccf..c0ce795 100644 --- a/core/java/android/animation/ObjectAnimator.java +++ b/core/java/android/animation/ObjectAnimator.java @@ -16,6 +16,8 @@ package android.animation; +import android.graphics.Path; +import android.graphics.PointF; import android.util.Log; import android.util.Property; @@ -191,7 +193,7 @@ public final class ObjectAnimator extends ValueAnimator { /** * Constructs and returns an ObjectAnimator that animates between int values. A single - * value implies that that value is the one being animated to. Two values imply a starting + * value implies that that value is the one being animated to. Two values imply starting * and ending values. More than two values imply a starting value, values to animate through * along the way, and an ending value (these values will be distributed evenly across * the duration of the animation). @@ -210,8 +212,33 @@ public final class ObjectAnimator extends ValueAnimator { } /** + * Constructs and returns an ObjectAnimator that animates coordinates along a <code>Path</code> + * using two properties. A <code>Path</code></> animation moves in two dimensions, animating + * coordinates <code>(x, y)</code> together to follow the line. In this variation, the + * coordinates are integers that are set to separate properties designated by + * <code>xPropertyName</code> and <code>yPropertyName</code>. + * + * @param target The object whose properties are to be animated. This object should + * have public methods on it called <code>setNameX()</code> and + * <code>setNameY</code>, where <code>nameX</code> and <code>nameY</code> + * are the value of <code>xPropertyName</code> and <code>yPropertyName</code> + * parameters, respectively. + * @param xPropertyName The name of the property for the x coordinate being animated. + * @param yPropertyName The name of the property for the y coordinate being animated. + * @param path The <code>Path</code> to animate values along. + * @return An ObjectAnimator object that is set up to animate along <code>path</code>. + */ + public static ObjectAnimator ofInt(Object target, String xPropertyName, String yPropertyName, + Path path) { + Keyframe[][] keyframes = PropertyValuesHolder.createKeyframes(path, true); + PropertyValuesHolder x = PropertyValuesHolder.ofKeyframe(xPropertyName, keyframes[0]); + PropertyValuesHolder y = PropertyValuesHolder.ofKeyframe(yPropertyName, keyframes[1]); + return ofPropertyValuesHolder(target, x, y); + } + + /** * Constructs and returns an ObjectAnimator that animates between int values. A single - * value implies that that value is the one being animated to. Two values imply a starting + * value implies that that value is the one being animated to. Two values imply starting * and ending values. More than two values imply a starting value, values to animate through * along the way, and an ending value (these values will be distributed evenly across * the duration of the animation). @@ -228,8 +255,135 @@ public final class ObjectAnimator extends ValueAnimator { } /** + * Constructs and returns an ObjectAnimator that animates coordinates along a <code>Path</code> + * using two properties. A <code>Path</code></> animation moves in two dimensions, animating + * coordinates <code>(x, y)</code> together to follow the line. In this variation, the + * coordinates are integers that are set to separate properties, <code>xProperty</code> and + * <code>yProperty</code>. + * + * @param target The object whose properties are to be animated. + * @param xProperty The property for the x coordinate being animated. + * @param yProperty The property for the y coordinate being animated. + * @param path The <code>Path</code> to animate values along. + * @return An ObjectAnimator object that is set up to animate along <code>path</code>. + */ + public static <T> ObjectAnimator ofInt(T target, Property<T, Integer> xProperty, + Property<T, Integer> yProperty, Path path) { + Keyframe[][] keyframes = PropertyValuesHolder.createKeyframes(path, true); + PropertyValuesHolder x = PropertyValuesHolder.ofKeyframe(xProperty, keyframes[0]); + PropertyValuesHolder y = PropertyValuesHolder.ofKeyframe(yProperty, keyframes[1]); + return ofPropertyValuesHolder(target, x, y); + } + + /** + * Constructs and returns an ObjectAnimator that animates over int values for a multiple + * parameters setter. Only public methods that take only int parameters are supported. + * Each <code>int[]</code> contains a complete set of parameters to the setter method. + * At least two <code>int[]</code> values must be provided, a start and end. More than two + * values imply a starting value, values to animate through along the way, and an ending + * value (these values will be distributed evenly across the duration of the animation). + * + * @param target The object whose property is to be animated. This object may + * have a public method on it called <code>setName()</code>, where <code>name</code> is + * the value of the <code>propertyName</code> parameter. <code>propertyName</code> may also + * be the case-sensitive complete name of the public setter method. + * @param propertyName The name of the property being animated or the name of the setter method. + * @param values A set of values that the animation will animate between over time. + * @return An ObjectAnimator object that is set up to animate between the given values. + */ + public static ObjectAnimator ofMultiInt(Object target, String propertyName, int[][] values) { + PropertyValuesHolder pvh = PropertyValuesHolder.ofMultiInt(propertyName, values); + return ofPropertyValuesHolder(target, pvh); + } + + /** + * Constructs and returns an ObjectAnimator that animates the target using a multi-int setter + * along the given <code>Path</code>. A <code>Path</code></> animation moves in two dimensions, + * animating coordinates <code>(x, y)</code> together to follow the line. In this variation, the + * coordinates are integer x and y coordinates used in the first and second parameter of the + * setter, respectively. + * + * @param target The object whose property is to be animated. This object may + * have a public method on it called <code>setName()</code>, where <code>name</code> is + * the value of the <code>propertyName</code> parameter. <code>propertyName</code> may also + * be the case-sensitive complete name of the public setter method. + * @param propertyName The name of the property being animated or the name of the setter method. + * @param path The <code>Path</code> to animate values along. + * @return An ObjectAnimator object that is set up to animate along <code>path</code>. + */ + public static ObjectAnimator ofMultiInt(Object target, String propertyName, Path path) { + PropertyValuesHolder pvh = PropertyValuesHolder.ofMultiInt(propertyName, path); + return ofPropertyValuesHolder(target, pvh); + } + + /** + * Constructs and returns an ObjectAnimator that animates over values for a multiple int + * parameters setter. Only public methods that take only int parameters are supported. + * <p>At least two values must be provided, a start and end. More than two + * values imply a starting value, values to animate through along the way, and an ending + * value (these values will be distributed evenly across the duration of the animation).</p> + * + * @param target The object whose property is to be animated. This object may + * have a public method on it called <code>setName()</code>, where <code>name</code> is + * the value of the <code>propertyName</code> parameter. <code>propertyName</code> may also + * be the case-sensitive complete name of the public setter method. + * @param propertyName The name of the property being animated or the name of the setter method. + * @param converter Converts T objects into int parameters for the multi-value setter. + * @param evaluator A TypeEvaluator that will be called on each animation frame to + * provide the necessary interpolation between the Object values to derive the animated + * value. + * @param values A set of values that the animation will animate between over time. + * @return An ObjectAnimator object that is set up to animate between the given values. + */ + public static <T> ObjectAnimator ofMultiInt(Object target, String propertyName, + TypeConverter<T, int[]> converter, TypeEvaluator<T> evaluator, T... values) { + PropertyValuesHolder pvh = PropertyValuesHolder.ofMultiInt(propertyName, converter, + evaluator, values); + return ObjectAnimator.ofPropertyValuesHolder(target, pvh); + } + + /** + * Constructs and returns an ObjectAnimator that animates between color values. A single + * value implies that that value is the one being animated to. Two values imply starting + * and ending values. More than two values imply a starting value, values to animate through + * along the way, and an ending value (these values will be distributed evenly across + * the duration of the animation). + * + * @param target The object whose property is to be animated. This object should + * have a public method on it called <code>setName()</code>, where <code>name</code> is + * the value of the <code>propertyName</code> parameter. + * @param propertyName The name of the property being animated. + * @param values A set of values that the animation will animate between over time. + * @return An ObjectAnimator object that is set up to animate between the given values. + */ + public static ObjectAnimator ofArgb(Object target, String propertyName, int... values) { + ObjectAnimator animator = ofInt(target, propertyName, values); + animator.setEvaluator(ArgbEvaluator.getInstance()); + return animator; + } + + /** + * Constructs and returns an ObjectAnimator that animates between color values. A single + * value implies that that value is the one being animated to. Two values imply starting + * and ending values. More than two values imply a starting value, values to animate through + * along the way, and an ending value (these values will be distributed evenly across + * the duration of the animation). + * + * @param target The object whose property is to be animated. + * @param property The property being animated. + * @param values A set of values that the animation will animate between over time. + * @return An ObjectAnimator object that is set up to animate between the given values. + */ + public static <T> ObjectAnimator ofArgb(T target, Property<T, Integer> property, + int... values) { + ObjectAnimator animator = ofInt(target, property, values); + animator.setEvaluator(ArgbEvaluator.getInstance()); + return animator; + } + + /** * Constructs and returns an ObjectAnimator that animates between float values. A single - * value implies that that value is the one being animated to. Two values imply a starting + * value implies that that value is the one being animated to. Two values imply starting * and ending values. More than two values imply a starting value, values to animate through * along the way, and an ending value (these values will be distributed evenly across * the duration of the animation). @@ -248,8 +402,33 @@ public final class ObjectAnimator extends ValueAnimator { } /** + * Constructs and returns an ObjectAnimator that animates coordinates along a <code>Path</code> + * using two properties. A <code>Path</code></> animation moves in two dimensions, animating + * coordinates <code>(x, y)</code> together to follow the line. In this variation, the + * coordinates are floats that are set to separate properties designated by + * <code>xPropertyName</code> and <code>yPropertyName</code>. + * + * @param target The object whose properties are to be animated. This object should + * have public methods on it called <code>setNameX()</code> and + * <code>setNameY</code>, where <code>nameX</code> and <code>nameY</code> + * are the value of the <code>xPropertyName</code> and <code>yPropertyName</code> + * parameters, respectively. + * @param xPropertyName The name of the property for the x coordinate being animated. + * @param yPropertyName The name of the property for the y coordinate being animated. + * @param path The <code>Path</code> to animate values along. + * @return An ObjectAnimator object that is set up to animate along <code>path</code>. + */ + public static ObjectAnimator ofFloat(Object target, String xPropertyName, String yPropertyName, + Path path) { + Keyframe[][] keyframes = PropertyValuesHolder.createKeyframes(path, false); + PropertyValuesHolder x = PropertyValuesHolder.ofKeyframe(xPropertyName, keyframes[0]); + PropertyValuesHolder y = PropertyValuesHolder.ofKeyframe(yPropertyName, keyframes[1]); + return ofPropertyValuesHolder(target, x, y); + } + + /** * Constructs and returns an ObjectAnimator that animates between float values. A single - * value implies that that value is the one being animated to. Two values imply a starting + * value implies that that value is the one being animated to. Two values imply starting * and ending values. More than two values imply a starting value, values to animate through * along the way, and an ending value (these values will be distributed evenly across * the duration of the animation). @@ -267,8 +446,94 @@ public final class ObjectAnimator extends ValueAnimator { } /** + * Constructs and returns an ObjectAnimator that animates coordinates along a <code>Path</code> + * using two properties. A <code>Path</code></> animation moves in two dimensions, animating + * coordinates <code>(x, y)</code> together to follow the line. In this variation, the + * coordinates are floats that are set to separate properties, <code>xProperty</code> and + * <code>yProperty</code>. + * + * @param target The object whose properties are to be animated. + * @param xProperty The property for the x coordinate being animated. + * @param yProperty The property for the y coordinate being animated. + * @param path The <code>Path</code> to animate values along. + * @return An ObjectAnimator object that is set up to animate along <code>path</code>. + */ + public static <T> ObjectAnimator ofFloat(T target, Property<T, Float> xProperty, + Property<T, Float> yProperty, Path path) { + return ofFloat(target, xProperty.getName(), yProperty.getName(), path); + } + + /** + * Constructs and returns an ObjectAnimator that animates over float values for a multiple + * parameters setter. Only public methods that take only float parameters are supported. + * Each <code>float[]</code> contains a complete set of parameters to the setter method. + * At least two <code>float[]</code> values must be provided, a start and end. More than two + * values imply a starting value, values to animate through along the way, and an ending + * value (these values will be distributed evenly across the duration of the animation). + * + * @param target The object whose property is to be animated. This object may + * have a public method on it called <code>setName()</code>, where <code>name</code> is + * the value of the <code>propertyName</code> parameter. <code>propertyName</code> may also + * be the case-sensitive complete name of the public setter method. + * @param propertyName The name of the property being animated or the name of the setter method. + * @param values A set of values that the animation will animate between over time. + * @return An ObjectAnimator object that is set up to animate between the given values. + */ + public static ObjectAnimator ofMultiFloat(Object target, String propertyName, + float[][] values) { + PropertyValuesHolder pvh = PropertyValuesHolder.ofMultiFloat(propertyName, values); + return ofPropertyValuesHolder(target, pvh); + } + + /** + * Constructs and returns an ObjectAnimator that animates the target using a multi-float setter + * along the given <code>Path</code>. A <code>Path</code></> animation moves in two dimensions, + * animating coordinates <code>(x, y)</code> together to follow the line. In this variation, the + * coordinates are float x and y coordinates used in the first and second parameter of the + * setter, respectively. + * + * @param target The object whose property is to be animated. This object may + * have a public method on it called <code>setName()</code>, where <code>name</code> is + * the value of the <code>propertyName</code> parameter. <code>propertyName</code> may also + * be the case-sensitive complete name of the public setter method. + * @param propertyName The name of the property being animated or the name of the setter method. + * @param path The <code>Path</code> to animate values along. + * @return An ObjectAnimator object that is set up to animate along <code>path</code>. + */ + public static ObjectAnimator ofMultiFloat(Object target, String propertyName, Path path) { + PropertyValuesHolder pvh = PropertyValuesHolder.ofMultiFloat(propertyName, path); + return ofPropertyValuesHolder(target, pvh); + } + + /** + * Constructs and returns an ObjectAnimator that animates over values for a multiple float + * parameters setter. Only public methods that take only float parameters are supported. + * <p>At least two values must be provided, a start and end. More than two + * values imply a starting value, values to animate through along the way, and an ending + * value (these values will be distributed evenly across the duration of the animation).</p> + * + * @param target The object whose property is to be animated. This object may + * have a public method on it called <code>setName()</code>, where <code>name</code> is + * the value of the <code>propertyName</code> parameter. <code>propertyName</code> may also + * be the case-sensitive complete name of the public setter method. + * @param propertyName The name of the property being animated or the name of the setter method. + * @param converter Converts T objects into float parameters for the multi-value setter. + * @param evaluator A TypeEvaluator that will be called on each animation frame to + * provide the necessary interpolation between the Object values to derive the animated + * value. + * @param values A set of values that the animation will animate between over time. + * @return An ObjectAnimator object that is set up to animate between the given values. + */ + public static <T> ObjectAnimator ofMultiFloat(Object target, String propertyName, + TypeConverter<T, float[]> converter, TypeEvaluator<T> evaluator, T... values) { + PropertyValuesHolder pvh = PropertyValuesHolder.ofMultiFloat(propertyName, converter, + evaluator, values); + return ObjectAnimator.ofPropertyValuesHolder(target, pvh); + } + + /** * Constructs and returns an ObjectAnimator that animates between Object values. A single - * value implies that that value is the one being animated to. Two values imply a starting + * value implies that that value is the one being animated to. Two values imply starting * and ending values. More than two values imply a starting value, values to animate through * along the way, and an ending value (these values will be distributed evenly across * the duration of the animation). @@ -292,8 +557,32 @@ public final class ObjectAnimator extends ValueAnimator { } /** + * Constructs and returns an ObjectAnimator that animates a property along a <code>Path</code>. + * A <code>Path</code></> animation moves in two dimensions, animating coordinates + * <code>(x, y)</code> together to follow the line. This variant animates the coordinates + * in a <code>PointF</code> to follow the <code>Path</code>. If the <code>Property</code> + * associated with <code>propertyName</code> uses a type other than <code>PointF</code>, + * <code>converter</code> can be used to change from <code>PointF</code> to the type + * associated with the <code>Property</code>. + * + * @param target The object whose property is to be animated. This object should + * have a public method on it called <code>setName()</code>, where <code>name</code> is + * the value of the <code>propertyName</code> parameter. + * @param propertyName The name of the property being animated. + * @param converter Converts a PointF to the type associated with the setter. May be + * null if conversion is unnecessary. + * @param path The <code>Path</code> to animate values along. + * @return An ObjectAnimator object that is set up to animate along <code>path</code>. + */ + public static ObjectAnimator ofObject(Object target, String propertyName, + TypeConverter<PointF, ?> converter, Path path) { + PropertyValuesHolder pvh = PropertyValuesHolder.ofObject(propertyName, converter, path); + return ofPropertyValuesHolder(target, pvh); + } + + /** * Constructs and returns an ObjectAnimator that animates between Object values. A single - * value implies that that value is the one being animated to. Two values imply a starting + * value implies that that value is the one being animated to. Two values imply starting * and ending values. More than two values imply a starting value, values to animate through * along the way, and an ending value (these values will be distributed evenly across * the duration of the animation). @@ -315,6 +604,53 @@ public final class ObjectAnimator extends ValueAnimator { } /** + * Constructs and returns an ObjectAnimator that animates between Object values. A single + * value implies that that value is the one being animated to. Two values imply starting + * and ending values. More than two values imply a starting value, values to animate through + * along the way, and an ending value (these values will be distributed evenly across + * the duration of the animation). This variant supplies a <code>TypeConverter</code> to + * convert from the animated values to the type of the property. If only one value is + * supplied, the <code>TypeConverter</code> must implement + * {@link TypeConverter#convertBack(Object)} to retrieve the current value. + * + * @param target The object whose property is to be animated. + * @param property The property being animated. + * @param converter Converts the animated object to the Property type. + * @param evaluator A TypeEvaluator that will be called on each animation frame to + * provide the necessary interpolation between the Object values to derive the animated + * value. + * @param values A set of values that the animation will animate between over time. + * @return An ObjectAnimator object that is set up to animate between the given values. + */ + public static <T, V, P> ObjectAnimator ofObject(T target, Property<T, P> property, + TypeConverter<V, P> converter, TypeEvaluator<V> evaluator, V... values) { + PropertyValuesHolder pvh = PropertyValuesHolder.ofObject(property, converter, evaluator, + values); + return ofPropertyValuesHolder(target, pvh); + } + + /** + * Constructs and returns an ObjectAnimator that animates a property along a <code>Path</code>. + * A <code>Path</code></> animation moves in two dimensions, animating coordinates + * <code>(x, y)</code> together to follow the line. This variant animates the coordinates + * in a <code>PointF</code> to follow the <code>Path</code>. If <code>property</code> + * uses a type other than <code>PointF</code>, <code>converter</code> can be used to change + * from <code>PointF</code> to the type associated with the <code>Property</code>. + * + * @param target The object whose property is to be animated. + * @param property The property being animated. Should not be null. + * @param converter Converts a PointF to the type associated with the setter. May be + * null if conversion is unnecessary. + * @param path The <code>Path</code> to animate values along. + * @return An ObjectAnimator object that is set up to animate along <code>path</code>. + */ + public static <T, V> ObjectAnimator ofObject(T target, Property<T, V> property, + TypeConverter<PointF, V> converter, Path path) { + PropertyValuesHolder pvh = PropertyValuesHolder.ofObject(property, converter, path); + return ofPropertyValuesHolder(target, pvh); + } + + /** * Constructs and returns an ObjectAnimator that animates between the sets of values specified * in <code>PropertyValueHolder</code> objects. This variant should be used when animating * several properties at once with the same ObjectAnimator, since PropertyValuesHolder allows diff --git a/core/java/android/animation/PointFEvaluator.java b/core/java/android/animation/PointFEvaluator.java new file mode 100644 index 0000000..91d501f --- /dev/null +++ b/core/java/android/animation/PointFEvaluator.java @@ -0,0 +1,83 @@ +/* + * 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.animation; + +import android.graphics.PointF; + +/** + * This evaluator can be used to perform type interpolation between <code>PointF</code> values. + */ +public class PointFEvaluator implements TypeEvaluator<PointF> { + + /** + * When null, a new PointF is returned on every evaluate call. When non-null, + * mPoint will be modified and returned on every evaluate. + */ + private PointF mPoint; + + /** + * Construct a PointFEvaluator that returns a new PointF on every evaluate call. + * To avoid creating an object for each evaluate call, + * {@link PointFEvaluator#PointFEvaluator(android.graphics.PointF)} should be used + * whenever possible. + */ + public PointFEvaluator() { + } + + /** + * Constructs a PointFEvaluator that modifies and returns <code>reuse</code> + * in {@link #evaluate(float, android.graphics.PointF, android.graphics.PointF)} calls. + * The value returned from + * {@link #evaluate(float, android.graphics.PointF, android.graphics.PointF)} should + * not be cached because it will change over time as the object is reused on each + * call. + * + * @param reuse A PointF to be modified and returned by evaluate. + */ + public PointFEvaluator(PointF reuse) { + mPoint = reuse; + } + + /** + * This function returns the result of linearly interpolating the start and + * end PointF values, with <code>fraction</code> representing the proportion + * between the start and end values. The calculation is a simple parametric + * calculation on each of the separate components in the PointF objects + * (x, y). + * + * <p>If {@link #PointFEvaluator(android.graphics.PointF)} was used to construct + * this PointFEvaluator, the object returned will be the <code>reuse</code> + * passed into the constructor.</p> + * + * @param fraction The fraction from the starting to the ending values + * @param startValue The start PointF + * @param endValue The end PointF + * @return A linear interpolation between the start and end values, given the + * <code>fraction</code> parameter. + */ + @Override + public PointF evaluate(float fraction, PointF startValue, PointF endValue) { + float x = startValue.x + (fraction * (endValue.x - startValue.x)); + float y = startValue.y + (fraction * (endValue.y - startValue.y)); + + if (mPoint != null) { + mPoint.set(x, y); + return mPoint; + } else { + return new PointF(x, y); + } + } +} diff --git a/core/java/android/animation/PropertyValuesHolder.java b/core/java/android/animation/PropertyValuesHolder.java index 21f6eda..8fce80a 100644 --- a/core/java/android/animation/PropertyValuesHolder.java +++ b/core/java/android/animation/PropertyValuesHolder.java @@ -16,6 +16,9 @@ package android.animation; +import android.graphics.Path; +import android.graphics.PointF; +import android.util.FloatMath; import android.util.FloatProperty; import android.util.IntProperty; import android.util.Log; @@ -124,6 +127,11 @@ public class PropertyValuesHolder implements Cloneable { private Object mAnimatedValue; /** + * Converts from the source Object type to the setter Object type. + */ + private TypeConverter mConverter; + + /** * Internal utility constructor, used by the factory methods to set the property name. * @param propertyName The name of the property for this holder. */ @@ -166,6 +174,104 @@ public class PropertyValuesHolder implements Cloneable { /** * Constructs and returns a PropertyValuesHolder with a given property name and + * set of <code>int[]</code> values. At least two <code>int[]</code> values must be supplied, + * a start and end value. If more values are supplied, the values will be animated from the + * start, through all intermediate values to the end value. When used with ObjectAnimator, + * the elements of the array represent the parameters of the setter function. + * + * @param propertyName The name of the property being animated. Can also be the + * case-sensitive name of the entire setter method. Should not be null. + * @param values The values that the property will animate between. + * @return PropertyValuesHolder The constructed PropertyValuesHolder object. + * @see IntArrayEvaluator#IntArrayEvaluator(int[]) + * @see ObjectAnimator#ofMultiInt(Object, String, TypeConverter, TypeEvaluator, Object[]) + */ + public static PropertyValuesHolder ofMultiInt(String propertyName, int[][] values) { + if (values.length < 2) { + throw new IllegalArgumentException("At least 2 values must be supplied"); + } + int numParameters = 0; + for (int i = 0; i < values.length; i++) { + if (values[i] == null) { + throw new IllegalArgumentException("values must not be null"); + } + int length = values[i].length; + if (i == 0) { + numParameters = length; + } else if (length != numParameters) { + throw new IllegalArgumentException("Values must all have the same length"); + } + } + IntArrayEvaluator evaluator = new IntArrayEvaluator(new int[numParameters]); + return new MultiIntValuesHolder(propertyName, null, evaluator, (Object[]) values); + } + + /** + * Constructs and returns a PropertyValuesHolder with a given property name to use + * as a multi-int setter. The values are animated along the path, with the first + * parameter of the setter set to the x coordinate and the second set to the y coordinate. + * + * @param propertyName The name of the property being animated. Can also be the + * case-sensitive name of the entire setter method. Should not be null. + * The setter must take exactly two <code>int</code> parameters. + * @param path The Path along which the values should be animated. + * @return PropertyValuesHolder The constructed PropertyValuesHolder object. + * @see ObjectAnimator#ofPropertyValuesHolder(Object, PropertyValuesHolder...) + */ + public static PropertyValuesHolder ofMultiInt(String propertyName, Path path) { + Keyframe[] keyframes = createKeyframes(path); + KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(keyframes); + TypeEvaluator<PointF> evaluator = new PointFEvaluator(new PointF()); + PointFToIntArray converter = new PointFToIntArray(); + return new MultiIntValuesHolder(propertyName, converter, evaluator, keyframeSet); + } + + /** + * Constructs and returns a PropertyValuesHolder with a given property and + * set of Object values for use with ObjectAnimator multi-value setters. The Object + * values are converted to <code>int[]</code> using the converter. + * + * @param propertyName The property being animated or complete name of the setter. + * Should not be null. + * @param converter Used to convert the animated value to setter parameters. + * @param evaluator A TypeEvaluator that will be called on each animation frame to + * provide the necessary interpolation between the Object values to derive the animated + * value. + * @param values The values that the property will animate between. + * @return PropertyValuesHolder The constructed PropertyValuesHolder object. + * @see ObjectAnimator#ofMultiInt(Object, String, TypeConverter, TypeEvaluator, Object[]) + * @see ObjectAnimator#ofPropertyValuesHolder(Object, PropertyValuesHolder...) + */ + public static <V> PropertyValuesHolder ofMultiInt(String propertyName, + TypeConverter<V, int[]> converter, TypeEvaluator<V> evaluator, V... values) { + return new MultiIntValuesHolder(propertyName, converter, evaluator, values); + } + + /** + * Constructs and returns a PropertyValuesHolder object with the specified property name or + * setter name for use in a multi-int setter function using ObjectAnimator. The values can be + * of any type, but the type should be consistent so that the supplied + * {@link android.animation.TypeEvaluator} can be used to to evaluate the animated value. The + * <code>converter</code> converts the values to parameters in the setter function. + * + * <p>At least two values must be supplied, a start and an end value.</p> + * + * @param propertyName The name of the property to associate with the set of values. This + * may also be the complete name of a setter function. + * @param converter Converts <code>values</code> into int parameters for the setter. + * Can be null if the Keyframes have int[] values. + * @param evaluator Used to interpolate between values. + * @param values The values at specific fractional times to evaluate between + * @return A PropertyValuesHolder for a multi-int parameter setter. + */ + public static <T> PropertyValuesHolder ofMultiInt(String propertyName, + TypeConverter<T, int[]> converter, TypeEvaluator<T> evaluator, Keyframe... values) { + KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values); + return new MultiIntValuesHolder(propertyName, converter, evaluator, keyframeSet); + } + + /** + * Constructs and returns a PropertyValuesHolder with a given property name and * set of float values. * @param propertyName The name of the property being animated. * @param values The values that the named property will animate between. @@ -188,6 +294,103 @@ public class PropertyValuesHolder implements Cloneable { /** * Constructs and returns a PropertyValuesHolder with a given property name and + * set of <code>float[]</code> values. At least two <code>float[]</code> values must be supplied, + * a start and end value. If more values are supplied, the values will be animated from the + * start, through all intermediate values to the end value. When used with ObjectAnimator, + * the elements of the array represent the parameters of the setter function. + * + * @param propertyName The name of the property being animated. Can also be the + * case-sensitive name of the entire setter method. Should not be null. + * @param values The values that the property will animate between. + * @return PropertyValuesHolder The constructed PropertyValuesHolder object. + * @see FloatArrayEvaluator#FloatArrayEvaluator(float[]) + * @see ObjectAnimator#ofMultiFloat(Object, String, TypeConverter, TypeEvaluator, Object[]) + */ + public static PropertyValuesHolder ofMultiFloat(String propertyName, float[][] values) { + if (values.length < 2) { + throw new IllegalArgumentException("At least 2 values must be supplied"); + } + int numParameters = 0; + for (int i = 0; i < values.length; i++) { + if (values[i] == null) { + throw new IllegalArgumentException("values must not be null"); + } + int length = values[i].length; + if (i == 0) { + numParameters = length; + } else if (length != numParameters) { + throw new IllegalArgumentException("Values must all have the same length"); + } + } + FloatArrayEvaluator evaluator = new FloatArrayEvaluator(new float[numParameters]); + return new MultiFloatValuesHolder(propertyName, null, evaluator, (Object[]) values); + } + + /** + * Constructs and returns a PropertyValuesHolder with a given property name to use + * as a multi-float setter. The values are animated along the path, with the first + * parameter of the setter set to the x coordinate and the second set to the y coordinate. + * + * @param propertyName The name of the property being animated. Can also be the + * case-sensitive name of the entire setter method. Should not be null. + * The setter must take exactly two <code>float</code> parameters. + * @param path The Path along which the values should be animated. + * @return PropertyValuesHolder The constructed PropertyValuesHolder object. + * @see ObjectAnimator#ofPropertyValuesHolder(Object, PropertyValuesHolder...) + */ + public static PropertyValuesHolder ofMultiFloat(String propertyName, Path path) { + Keyframe[] keyframes = createKeyframes(path); + KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(keyframes); + TypeEvaluator<PointF> evaluator = new PointFEvaluator(new PointF()); + PointFToFloatArray converter = new PointFToFloatArray(); + return new MultiFloatValuesHolder(propertyName, converter, evaluator, keyframeSet); + } + + /** + * Constructs and returns a PropertyValuesHolder with a given property and + * set of Object values for use with ObjectAnimator multi-value setters. The Object + * values are converted to <code>float[]</code> using the converter. + * + * @param propertyName The property being animated or complete name of the setter. + * Should not be null. + * @param converter Used to convert the animated value to setter parameters. + * @param evaluator A TypeEvaluator that will be called on each animation frame to + * provide the necessary interpolation between the Object values to derive the animated + * value. + * @param values The values that the property will animate between. + * @return PropertyValuesHolder The constructed PropertyValuesHolder object. + * @see ObjectAnimator#ofMultiFloat(Object, String, TypeConverter, TypeEvaluator, Object[]) + */ + public static <V> PropertyValuesHolder ofMultiFloat(String propertyName, + TypeConverter<V, float[]> converter, TypeEvaluator<V> evaluator, V... values) { + return new MultiFloatValuesHolder(propertyName, converter, evaluator, values); + } + + /** + * Constructs and returns a PropertyValuesHolder object with the specified property name or + * setter name for use in a multi-float setter function using ObjectAnimator. The values can be + * of any type, but the type should be consistent so that the supplied + * {@link android.animation.TypeEvaluator} can be used to to evaluate the animated value. The + * <code>converter</code> converts the values to parameters in the setter function. + * + * <p>At least two values must be supplied, a start and an end value.</p> + * + * @param propertyName The name of the property to associate with the set of values. This + * may also be the complete name of a setter function. + * @param converter Converts <code>values</code> into float parameters for the setter. + * Can be null if the Keyframes have float[] values. + * @param evaluator Used to interpolate between values. + * @param values The values at specific fractional times to evaluate between + * @return A PropertyValuesHolder for a multi-float parameter setter. + */ + public static <T> PropertyValuesHolder ofMultiFloat(String propertyName, + TypeConverter<T, float[]> converter, TypeEvaluator<T> evaluator, Keyframe... values) { + KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values); + return new MultiFloatValuesHolder(propertyName, converter, evaluator, keyframeSet); + } + + /** + * Constructs and returns a PropertyValuesHolder with a given property name and * set of Object values. This variant also takes a TypeEvaluator because the system * cannot automatically interpolate between objects of unknown type. * @@ -207,6 +410,27 @@ public class PropertyValuesHolder implements Cloneable { } /** + * Constructs and returns a PropertyValuesHolder with a given property name and + * a Path along which the values should be animated. This variant supports a + * <code>TypeConverter</code> to convert from <code>PointF</code> to the target + * type. + * + * @param propertyName The name of the property being animated. + * @param converter Converts a PointF to the type associated with the setter. May be + * null if conversion is unnecessary. + * @param path The Path along which the values should be animated. + * @return PropertyValuesHolder The constructed PropertyValuesHolder object. + */ + public static PropertyValuesHolder ofObject(String propertyName, + TypeConverter<PointF, ?> converter, Path path) { + Keyframe[] keyframes = createKeyframes(path); + PropertyValuesHolder pvh = ofKeyframe(propertyName, keyframes); + pvh.setEvaluator(new PointFEvaluator(new PointF())); + pvh.setConverter(converter); + return pvh; + } + + /** * Constructs and returns a PropertyValuesHolder with a given property and * set of Object values. This variant also takes a TypeEvaluator because the system * cannot automatically interpolate between objects of unknown type. @@ -227,6 +451,55 @@ public class PropertyValuesHolder implements Cloneable { } /** + * Constructs and returns a PropertyValuesHolder with a given property and + * set of Object values. This variant also takes a TypeEvaluator because the system + * cannot automatically interpolate between objects of unknown type. This variant also + * takes a <code>TypeConverter</code> to convert from animated values to the type + * of the property. If only one value is supplied, the <code>TypeConverter</code> + * must implement {@link TypeConverter#convertBack(Object)} to retrieve the current + * value. + * + * @param property The property being animated. Should not be null. + * @param converter Converts the animated object to the Property type. + * @param evaluator A TypeEvaluator that will be called on each animation frame to + * provide the necessary interpolation between the Object values to derive the animated + * value. + * @param values The values that the property will animate between. + * @return PropertyValuesHolder The constructed PropertyValuesHolder object. + * @see #setConverter(TypeConverter) + * @see TypeConverter + */ + public static <T, V> PropertyValuesHolder ofObject(Property<?, V> property, + TypeConverter<T, V> converter, TypeEvaluator<T> evaluator, T... values) { + PropertyValuesHolder pvh = new PropertyValuesHolder(property); + pvh.setConverter(converter); + pvh.setObjectValues(values); + pvh.setEvaluator(evaluator); + return pvh; + } + + /** + * Constructs and returns a PropertyValuesHolder with a given property and + * a Path along which the values should be animated. This variant supports a + * <code>TypeConverter</code> to convert from <code>PointF</code> to the target + * type. + * + * @param property The property being animated. Should not be null. + * @param converter Converts a PointF to the type associated with the setter. May be + * null if conversion is unnecessary. + * @param path The Path along which the values should be animated. + * @return PropertyValuesHolder The constructed PropertyValuesHolder object. + */ + public static <V> PropertyValuesHolder ofObject(Property<?, V> property, + TypeConverter<PointF, V> converter, Path path) { + Keyframe[] keyframes = createKeyframes(path); + PropertyValuesHolder pvh = ofKeyframe(property, keyframes); + pvh.setEvaluator(new PointFEvaluator(new PointF())); + pvh.setConverter(converter); + return pvh; + } + + /** * Constructs and returns a PropertyValuesHolder object with the specified property name and set * of values. These values can be of any type, but the type should be consistent so that * an appropriate {@link android.animation.TypeEvaluator} can be found that matches @@ -361,6 +634,14 @@ public class PropertyValuesHolder implements Cloneable { } /** + * Sets the converter to convert from the values type to the setter's parameter type. + * @param converter The converter to use to convert values. + */ + public void setConverter(TypeConverter converter) { + mConverter = converter; + } + + /** * Determine the setter or getter function using the JavaBeans convention of setFoo or * getFoo for a property named 'foo'. This function figures out what the name of the * function should be and uses reflection to find the Method with that name on the @@ -389,22 +670,24 @@ public class PropertyValuesHolder implements Cloneable { } else { args = new Class[1]; Class typeVariants[]; - if (mValueType.equals(Float.class)) { + if (valueType.equals(Float.class)) { typeVariants = FLOAT_VARIANTS; - } else if (mValueType.equals(Integer.class)) { + } else if (valueType.equals(Integer.class)) { typeVariants = INTEGER_VARIANTS; - } else if (mValueType.equals(Double.class)) { + } else if (valueType.equals(Double.class)) { typeVariants = DOUBLE_VARIANTS; } else { typeVariants = new Class[1]; - typeVariants[0] = mValueType; + typeVariants[0] = valueType; } for (Class typeVariant : typeVariants) { args[0] = typeVariant; try { returnVal = targetClass.getMethod(methodName, args); - // change the value type to suit - mValueType = typeVariant; + if (mConverter == null) { + // change the value type to suit + mValueType = typeVariant; + } return returnVal; } catch (NoSuchMethodException e) { // Swallow the error and keep trying other variants @@ -415,7 +698,7 @@ public class PropertyValuesHolder implements Cloneable { if (returnVal == null) { Log.w("PropertyValuesHolder", "Method " + - getMethodName(prefix, mPropertyName) + "() with type " + mValueType + + getMethodName(prefix, mPropertyName) + "() with type " + valueType + " not found on target class " + targetClass); } @@ -465,7 +748,8 @@ public class PropertyValuesHolder implements Cloneable { * @param targetClass The Class on which the requested method should exist. */ void setupSetter(Class targetClass) { - mSetter = setupSetterOrGetter(targetClass, sSetterPropertyMap, "set", mValueType); + Class<?> propertyType = mConverter == null ? mValueType : mConverter.getTargetType(); + mSetter = setupSetterOrGetter(targetClass, sSetterPropertyMap, "set", propertyType); } /** @@ -489,10 +773,13 @@ public class PropertyValuesHolder implements Cloneable { if (mProperty != null) { // check to make sure that mProperty is on the class of target try { - Object testValue = mProperty.get(target); + Object testValue = null; for (Keyframe kf : mKeyframeSet.mKeyframes) { if (!kf.hasValue()) { - kf.setValue(mProperty.get(target)); + if (testValue == null) { + testValue = convertBack(mProperty.get(target)); + } + kf.setValue(testValue); } } return; @@ -516,7 +803,8 @@ public class PropertyValuesHolder implements Cloneable { } } try { - kf.setValue(mGetter.invoke(target)); + Object value = convertBack(mGetter.invoke(target)); + kf.setValue(value); } catch (InvocationTargetException e) { Log.e("PropertyValuesHolder", e.toString()); } catch (IllegalAccessException e) { @@ -526,6 +814,18 @@ public class PropertyValuesHolder implements Cloneable { } } + private Object convertBack(Object value) { + if (mConverter != null) { + value = mConverter.convertBack(value); + if (value == null) { + throw new IllegalArgumentException("Converter " + + mConverter.getClass().getName() + + " must implement convertBack and not return null."); + } + } + return value; + } + /** * Utility function to set the value stored in a particular Keyframe. The value used is * whatever the value is for the property name specified in the keyframe on the target object. @@ -535,7 +835,8 @@ public class PropertyValuesHolder implements Cloneable { */ private void setupValue(Object target, Keyframe kf) { if (mProperty != null) { - kf.setValue(mProperty.get(target)); + Object value = convertBack(mProperty.get(target)); + kf.setValue(value); } try { if (mGetter == null) { @@ -546,7 +847,8 @@ public class PropertyValuesHolder implements Cloneable { return; } } - kf.setValue(mGetter.invoke(target)); + Object value = convertBack(mGetter.invoke(target)); + kf.setValue(value); } catch (InvocationTargetException e) { Log.e("PropertyValuesHolder", e.toString()); } catch (IllegalAccessException e) { @@ -657,7 +959,8 @@ public class PropertyValuesHolder implements Cloneable { * @param fraction The elapsed, interpolated fraction of the animation. */ void calculateValue(float fraction) { - mAnimatedValue = mKeyframeSet.getValue(fraction); + Object value = mKeyframeSet.getValue(fraction); + mAnimatedValue = mConverter == null ? value : mConverter.convert(value); } /** @@ -1015,8 +1318,334 @@ public class PropertyValuesHolder implements Cloneable { } + static class MultiFloatValuesHolder extends PropertyValuesHolder { + private long mJniSetter; + private static final HashMap<Class, HashMap<String, Long>> sJNISetterPropertyMap = + new HashMap<Class, HashMap<String, Long>>(); + + public MultiFloatValuesHolder(String propertyName, TypeConverter converter, + TypeEvaluator evaluator, Object... values) { + super(propertyName); + setConverter(converter); + setObjectValues(values); + setEvaluator(evaluator); + } + + public MultiFloatValuesHolder(String propertyName, TypeConverter converter, + TypeEvaluator evaluator, KeyframeSet keyframeSet) { + super(propertyName); + setConverter(converter); + mKeyframeSet = keyframeSet; + setEvaluator(evaluator); + } + + /** + * Internal function to set the value on the target object, using the setter set up + * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator + * to handle turning the value calculated by ValueAnimator into a value set on the object + * according to the name of the property. + * + * @param target The target object on which the value is set + */ + @Override + void setAnimatedValue(Object target) { + float[] values = (float[]) getAnimatedValue(); + int numParameters = values.length; + if (mJniSetter != 0) { + switch (numParameters) { + case 1: + nCallFloatMethod(target, mJniSetter, values[0]); + break; + case 2: + nCallTwoFloatMethod(target, mJniSetter, values[0], values[1]); + break; + case 4: + nCallFourFloatMethod(target, mJniSetter, values[0], values[1], + values[2], values[3]); + break; + default: { + nCallMultipleFloatMethod(target, mJniSetter, values); + break; + } + } + } + } + + /** + * Internal function (called from ObjectAnimator) to set up the setter and getter + * prior to running the animation. No getter can be used for multiple parameters. + * + * @param target The object on which the setter exists. + */ + @Override + void setupSetterAndGetter(Object target) { + setupSetter(target.getClass()); + } + + @Override + void setupSetter(Class targetClass) { + if (mJniSetter != 0) { + return; + } + try { + mPropertyMapLock.writeLock().lock(); + HashMap<String, Long> propertyMap = sJNISetterPropertyMap.get(targetClass); + if (propertyMap != null) { + Long jniSetterLong = propertyMap.get(mPropertyName); + if (jniSetterLong != null) { + mJniSetter = jniSetterLong; + } + } + if (mJniSetter == 0) { + String methodName = getMethodName("set", mPropertyName); + calculateValue(0f); + float[] values = (float[]) getAnimatedValue(); + int numParams = values.length; + try { + mJniSetter = nGetMultipleFloatMethod(targetClass, methodName, numParams); + } catch (NoSuchMethodError e) { + // try without the 'set' prefix + mJniSetter = nGetMultipleFloatMethod(targetClass, mPropertyName, numParams); + } + if (mJniSetter != 0) { + if (propertyMap == null) { + propertyMap = new HashMap<String, Long>(); + sJNISetterPropertyMap.put(targetClass, propertyMap); + } + propertyMap.put(mPropertyName, mJniSetter); + } + } + } finally { + mPropertyMapLock.writeLock().unlock(); + } + } + } + + static class MultiIntValuesHolder extends PropertyValuesHolder { + private long mJniSetter; + private static final HashMap<Class, HashMap<String, Long>> sJNISetterPropertyMap = + new HashMap<Class, HashMap<String, Long>>(); + + public MultiIntValuesHolder(String propertyName, TypeConverter converter, + TypeEvaluator evaluator, Object... values) { + super(propertyName); + setConverter(converter); + setObjectValues(values); + setEvaluator(evaluator); + } + + public MultiIntValuesHolder(String propertyName, TypeConverter converter, + TypeEvaluator evaluator, KeyframeSet keyframeSet) { + super(propertyName); + setConverter(converter); + mKeyframeSet = keyframeSet; + setEvaluator(evaluator); + } + + /** + * Internal function to set the value on the target object, using the setter set up + * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator + * to handle turning the value calculated by ValueAnimator into a value set on the object + * according to the name of the property. + * + * @param target The target object on which the value is set + */ + @Override + void setAnimatedValue(Object target) { + int[] values = (int[]) getAnimatedValue(); + int numParameters = values.length; + if (mJniSetter != 0) { + switch (numParameters) { + case 1: + nCallIntMethod(target, mJniSetter, values[0]); + break; + case 2: + nCallTwoIntMethod(target, mJniSetter, values[0], values[1]); + break; + case 4: + nCallFourIntMethod(target, mJniSetter, values[0], values[1], + values[2], values[3]); + break; + default: { + nCallMultipleIntMethod(target, mJniSetter, values); + break; + } + } + } + } + + /** + * Internal function (called from ObjectAnimator) to set up the setter and getter + * prior to running the animation. No getter can be used for multiple parameters. + * + * @param target The object on which the setter exists. + */ + @Override + void setupSetterAndGetter(Object target) { + setupSetter(target.getClass()); + } + + @Override + void setupSetter(Class targetClass) { + if (mJniSetter != 0) { + return; + } + try { + mPropertyMapLock.writeLock().lock(); + HashMap<String, Long> propertyMap = sJNISetterPropertyMap.get(targetClass); + if (propertyMap != null) { + Long jniSetterLong = propertyMap.get(mPropertyName); + if (jniSetterLong != null) { + mJniSetter = jniSetterLong; + } + } + if (mJniSetter == 0) { + String methodName = getMethodName("set", mPropertyName); + calculateValue(0f); + int[] values = (int[]) getAnimatedValue(); + int numParams = values.length; + try { + mJniSetter = nGetMultipleIntMethod(targetClass, methodName, numParams); + } catch (NoSuchMethodError e) { + // try without the 'set' prefix + mJniSetter = nGetMultipleIntMethod(targetClass, mPropertyName, numParams); + } + if (mJniSetter != 0) { + if (propertyMap == null) { + propertyMap = new HashMap<String, Long>(); + sJNISetterPropertyMap.put(targetClass, propertyMap); + } + propertyMap.put(mPropertyName, mJniSetter); + } + } + } finally { + mPropertyMapLock.writeLock().unlock(); + } + } + } + + /* Path interpolation relies on approximating the Path as a series of line segments. + The line segments are recursively divided until there is less than 1/2 pixel error + between the lines and the curve. Each point of the line segment is converted + to a Keyframe and a linear interpolation between Keyframes creates a good approximation + of the curve. + + The fraction for each Keyframe is the length along the Path to the point, divided by + the total Path length. Two points may have the same fraction in the case of a move + command causing a disjoint Path. + + The value for each Keyframe is either the point as a PointF or one of the x or y + coordinates as an int or float. In the latter case, two Keyframes are generated for + each point that have the same fraction. */ + + /** + * Returns separate Keyframes arrays for the x and y coordinates along a Path. If + * isInt is true, the Keyframes will be IntKeyframes, otherwise they will be FloatKeyframes. + * The element at index 0 are the x coordinate Keyframes and element at index 1 are the + * y coordinate Keyframes. The returned values can be linearly interpolated and get less + * than 1/2 pixel error. + */ + static Keyframe[][] createKeyframes(Path path, boolean isInt) { + if (path == null || path.isEmpty()) { + throw new IllegalArgumentException("The path must not be null or empty"); + } + float[] pointComponents = path.approximate(0.5f); + + int numPoints = pointComponents.length / 3; + + Keyframe[][] keyframes = new Keyframe[2][]; + keyframes[0] = new Keyframe[numPoints]; + keyframes[1] = new Keyframe[numPoints]; + int componentIndex = 0; + for (int i = 0; i < numPoints; i++) { + float fraction = pointComponents[componentIndex++]; + float x = pointComponents[componentIndex++]; + float y = pointComponents[componentIndex++]; + if (isInt) { + keyframes[0][i] = Keyframe.ofInt(fraction, Math.round(x)); + keyframes[1][i] = Keyframe.ofInt(fraction, Math.round(y)); + } else { + keyframes[0][i] = Keyframe.ofFloat(fraction, x); + keyframes[1][i] = Keyframe.ofFloat(fraction, y); + } + } + return keyframes; + } + + /** + * Returns PointF Keyframes for a Path. The resulting points can be linearly interpolated + * with less than 1/2 pixel in error. + */ + private static Keyframe[] createKeyframes(Path path) { + if (path == null || path.isEmpty()) { + throw new IllegalArgumentException("The path must not be null or empty"); + } + float[] pointComponents = path.approximate(0.5f); + + int numPoints = pointComponents.length / 3; + + Keyframe[] keyframes = new Keyframe[numPoints]; + int componentIndex = 0; + for (int i = 0; i < numPoints; i++) { + float fraction = pointComponents[componentIndex++]; + float x = pointComponents[componentIndex++]; + float y = pointComponents[componentIndex++]; + keyframes[i] = Keyframe.ofObject(fraction, new PointF(x, y)); + } + return keyframes; + } + + /** + * Convert from PointF to float[] for multi-float setters along a Path. + */ + private static class PointFToFloatArray extends TypeConverter<PointF, float[]> { + private float[] mCoordinates = new float[2]; + + public PointFToFloatArray() { + super(PointF.class, float[].class); + } + + @Override + public float[] convert(PointF value) { + mCoordinates[0] = value.x; + mCoordinates[1] = value.y; + return mCoordinates; + } + }; + + /** + * Convert from PointF to int[] for multi-int setters along a Path. + */ + private static class PointFToIntArray extends TypeConverter<PointF, int[]> { + private int[] mCoordinates = new int[2]; + + public PointFToIntArray() { + super(PointF.class, int[].class); + } + + @Override + public int[] convert(PointF value) { + mCoordinates[0] = Math.round(value.x); + mCoordinates[1] = Math.round(value.y); + return mCoordinates; + } + }; + native static private long nGetIntMethod(Class targetClass, String methodName); native static private long nGetFloatMethod(Class targetClass, String methodName); + native static private long nGetMultipleIntMethod(Class targetClass, String methodName, + int numParams); + native static private long nGetMultipleFloatMethod(Class targetClass, String methodName, + int numParams); native static private void nCallIntMethod(Object target, long methodID, int arg); native static private void nCallFloatMethod(Object target, long methodID, float arg); + native static private void nCallTwoIntMethod(Object target, long methodID, int arg1, int arg2); + native static private void nCallFourIntMethod(Object target, long methodID, int arg1, int arg2, + int arg3, int arg4); + native static private void nCallMultipleIntMethod(Object target, long methodID, int[] args); + native static private void nCallTwoFloatMethod(Object target, long methodID, float arg1, + float arg2); + native static private void nCallFourFloatMethod(Object target, long methodID, float arg1, + float arg2, float arg3, float arg4); + native static private void nCallMultipleFloatMethod(Object target, long methodID, float[] args); } diff --git a/core/java/android/animation/RectEvaluator.java b/core/java/android/animation/RectEvaluator.java index 28d496b..23eb766 100644 --- a/core/java/android/animation/RectEvaluator.java +++ b/core/java/android/animation/RectEvaluator.java @@ -23,12 +23,45 @@ import android.graphics.Rect; public class RectEvaluator implements TypeEvaluator<Rect> { /** + * When null, a new Rect is returned on every evaluate call. When non-null, + * mRect will be modified and returned on every evaluate. + */ + private Rect mRect; + + /** + * Construct a RectEvaluator that returns a new Rect on every evaluate call. + * To avoid creating an object for each evaluate call, + * {@link RectEvaluator#RectEvaluator(android.graphics.Rect)} should be used + * whenever possible. + */ + public RectEvaluator() { + } + + /** + * Constructs a RectEvaluator that modifies and returns <code>reuseRect</code> + * in {@link #evaluate(float, android.graphics.Rect, android.graphics.Rect)} calls. + * The value returned from + * {@link #evaluate(float, android.graphics.Rect, android.graphics.Rect)} should + * not be cached because it will change over time as the object is reused on each + * call. + * + * @param reuseRect A Rect to be modified and returned by evaluate. + */ + public RectEvaluator(Rect reuseRect) { + mRect = reuseRect; + } + + /** * This function returns the result of linearly interpolating the start and * end Rect values, with <code>fraction</code> representing the proportion * between the start and end values. The calculation is a simple parametric * calculation on each of the separate components in the Rect objects * (left, top, right, and bottom). * + * <p>If {@link #RectEvaluator(android.graphics.Rect)} was used to construct + * this RectEvaluator, the object returned will be the <code>reuseRect</code> + * passed into the constructor.</p> + * * @param fraction The fraction from the starting to the ending values * @param startValue The start Rect * @param endValue The end Rect @@ -37,9 +70,15 @@ public class RectEvaluator implements TypeEvaluator<Rect> { */ @Override public Rect evaluate(float fraction, Rect startValue, Rect endValue) { - return new Rect(startValue.left + (int)((endValue.left - startValue.left) * fraction), - startValue.top + (int)((endValue.top - startValue.top) * fraction), - startValue.right + (int)((endValue.right - startValue.right) * fraction), - startValue.bottom + (int)((endValue.bottom - startValue.bottom) * fraction)); + int left = startValue.left + (int) ((endValue.left - startValue.left) * fraction); + int top = startValue.top + (int) ((endValue.top - startValue.top) * fraction); + int right = startValue.right + (int) ((endValue.right - startValue.right) * fraction); + int bottom = startValue.bottom + (int) ((endValue.bottom - startValue.bottom) * fraction); + if (mRect == null) { + return new Rect(left, top, right, bottom); + } else { + mRect.set(left, top, right, bottom); + return mRect; + } } } diff --git a/core/java/android/animation/TypeConverter.java b/core/java/android/animation/TypeConverter.java new file mode 100644 index 0000000..03b3eb5 --- /dev/null +++ b/core/java/android/animation/TypeConverter.java @@ -0,0 +1,68 @@ +/* + * 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.animation; + +/** + * Abstract base class used convert type T to another type V. This + * is necessary when the value types of in animation are different + * from the property type. + * @see PropertyValuesHolder#setConverter(TypeConverter) + */ +public abstract class TypeConverter<T, V> { + private Class<T> mFromClass; + private Class<V> mToClass; + + public TypeConverter(Class<T> fromClass, Class<V> toClass) { + mFromClass = fromClass; + mToClass = toClass; + } + + /** + * Returns the target converted type. Used by the animation system to determine + * the proper setter function to call. + * @return The Class to convert the input to. + */ + Class<V> getTargetType() { + return mToClass; + } + + /** + * Returns the source conversion type. + */ + Class<T> getSourceType() { + return mFromClass; + } + + /** + * Converts a value from one type to another. + * @param value The Object to convert. + * @return A value of type V, converted from <code>value</code>. + */ + public abstract V convert(T value); + + /** + * Does a conversion from the target type back to the source type. The subclass + * must implement this when a TypeConverter is used in animations and current + * values will need to be read for an animation. By default, this will return null, + * indicating that back-conversion is not supported. + * @param value The Object to convert. + * @return A value of type T, converted from <code>value</code>. + */ + public T convertBack(V value) { + return null; + } +} diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java index 86da673..7880f39 100644 --- a/core/java/android/animation/ValueAnimator.java +++ b/core/java/android/animation/ValueAnimator.java @@ -280,6 +280,24 @@ public class ValueAnimator extends Animator { } /** + * Constructs and returns a ValueAnimator that animates between color values. A single + * value implies that that value is the one being animated to. However, this is not typically + * useful in a ValueAnimator object because there is no way for the object to determine the + * starting value for the animation (unlike ObjectAnimator, which can derive that value + * from the target object and property being animated). Therefore, there should typically + * be two or more values. + * + * @param values A set of values that the animation will animate between over time. + * @return A ValueAnimator object that is set up to animate between the given values. + */ + public static ValueAnimator ofArgb(int... values) { + ValueAnimator anim = new ValueAnimator(); + anim.setIntValues(values); + anim.setEvaluator(ArgbEvaluator.getInstance()); + return anim; + } + + /** * Constructs and returns a ValueAnimator that animates between float values. A single * value implies that that value is the one being animated to. However, this is not typically * useful in a ValueAnimator object because there is no way for the object to determine the diff --git a/core/java/android/annotation/IntDef.java b/core/java/android/annotation/IntDef.java new file mode 100644 index 0000000..3cae9c5 --- /dev/null +++ b/core/java/android/annotation/IntDef.java @@ -0,0 +1,60 @@ +/* + * 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.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that the annotated element of integer type, represents + * a logical type and that its value should be one of the explicitly + * named constants. If the {@link #flag()} attribute is set to true, + * multiple constants can be combined. + * <p> + * Example: + * <pre>{@code + * @Retention(CLASS) + * @IntDef({NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS}) + * public @interface NavigationMode {} + * public static final int NAVIGATION_MODE_STANDARD = 0; + * public static final int NAVIGATION_MODE_LIST = 1; + * public static final int NAVIGATION_MODE_TABS = 2; + * ... + * public abstract void setNavigationMode(@NavigationMode int mode); + * @NavigationMode + * public abstract int getNavigationMode(); + * }</pre> + * For a flag, set the flag attribute: + * <pre>{@code + * @IntDef( + * flag = true + * value = {NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS}) + * }</pre> + * + * @hide + */ +@Retention(CLASS) +@Target({ANNOTATION_TYPE}) +public @interface IntDef { + /** Defines the allowed constants for this element */ + long[] value() default {}; + + /** Defines whether the constants can be used as a flag, or just as an enum (the default) */ + boolean flag() default false; +} diff --git a/core/java/android/annotation/NonNull.java b/core/java/android/annotation/NonNull.java new file mode 100644 index 0000000..3ca9eea --- /dev/null +++ b/core/java/android/annotation/NonNull.java @@ -0,0 +1,36 @@ +/* + * 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.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that a parameter, field or method return value can never be null. + * <p> + * This is a marker annotation and it has no specific attributes. + * + * @hide + */ +@Retention(SOURCE) +@Target({METHOD, PARAMETER, FIELD}) +public @interface NonNull { +} diff --git a/core/java/android/annotation/Nullable.java b/core/java/android/annotation/Nullable.java new file mode 100644 index 0000000..43f42fa --- /dev/null +++ b/core/java/android/annotation/Nullable.java @@ -0,0 +1,43 @@ +/* + * 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.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that a parameter, field or method return value can be null. + * <p> + * When decorating a method call parameter, this denotes that the parameter can + * legitimately be null and the method will gracefully deal with it. Typically + * used on optional parameters. + * <p> + * When decorating a method, this denotes the method might legitimately return + * null. + * <p> + * This is a marker annotation and it has no specific attributes. + * + * @hide + */ +@Retention(SOURCE) +@Target({METHOD, PARAMETER, FIELD}) +public @interface Nullable { +} diff --git a/core/java/android/annotation/StringDef.java b/core/java/android/annotation/StringDef.java new file mode 100644 index 0000000..5f7f380 --- /dev/null +++ b/core/java/android/annotation/StringDef.java @@ -0,0 +1,51 @@ +/* + * 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.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that the annotated String element, represents a logical + * type and that its value should be one of the explicitly named constants. + * <p> + * Example: + * <pre>{@code + * @Retention(SOURCE) + * @StringDef({ + * POWER_SERVICE, + * WINDOW_SERVICE, + * LAYOUT_INFLATER_SERVICE + * }) + * public @interface ServiceName {} + * public static final String POWER_SERVICE = "power"; + * public static final String WINDOW_SERVICE = "window"; + * public static final String LAYOUT_INFLATER_SERVICE = "layout_inflater"; + * ... + * public abstract Object getSystemService(@ServiceName String name); + * }</pre> + * + * @hide + */ +@Retention(CLASS) +@Target({ANNOTATION_TYPE}) +public @interface StringDef { + /** Defines the allowed constants for this element */ + String[] value() default {}; +} diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java index c4ddf1f..fbe8987 100644 --- a/core/java/android/app/ActionBar.java +++ b/core/java/android/app/ActionBar.java @@ -16,6 +16,9 @@ package android.app; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; @@ -28,6 +31,9 @@ import android.view.ViewGroup.MarginLayoutParams; import android.view.Window; import android.widget.SpinnerAdapter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * A window feature at the top of the activity that may display the activity title, navigation * modes, and other interactive items. @@ -57,6 +63,11 @@ import android.widget.SpinnerAdapter; * </div> */ public abstract class ActionBar { + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS}) + public @interface NavigationMode {} + /** * Standard navigation mode. Consists of either a logo or icon * and title text with an optional subtitle. Clicking any of these elements @@ -78,6 +89,19 @@ public abstract class ActionBar { */ public static final int NAVIGATION_MODE_TABS = 2; + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, + value = { + DISPLAY_USE_LOGO, + DISPLAY_SHOW_HOME, + DISPLAY_HOME_AS_UP, + DISPLAY_SHOW_TITLE, + DISPLAY_SHOW_CUSTOM, + DISPLAY_TITLE_MULTIPLE_LINES + }) + public @interface DisplayOptions {} + /** * Use logo instead of icon if available. This flag will cause appropriate * navigation modes to use a wider logo in place of the standard icon. @@ -341,7 +365,7 @@ public abstract class ActionBar { * @param options A combination of the bits defined by the DISPLAY_ constants * defined in ActionBar. */ - public abstract void setDisplayOptions(int options); + public abstract void setDisplayOptions(@DisplayOptions int options); /** * Set selected display options. Only the options specified by mask will be changed. @@ -356,7 +380,7 @@ public abstract class ActionBar { * defined in ActionBar. * @param mask A bit mask declaring which display options should be changed. */ - public abstract void setDisplayOptions(int options, int mask); + public abstract void setDisplayOptions(@DisplayOptions int options, @DisplayOptions int mask); /** * Set whether to display the activity logo rather than the activity icon. @@ -431,7 +455,7 @@ public abstract class ActionBar { * @see #setStackedBackgroundDrawable(Drawable) * @see #setSplitBackgroundDrawable(Drawable) */ - public abstract void setBackgroundDrawable(Drawable d); + public abstract void setBackgroundDrawable(@Nullable Drawable d); /** * Set the ActionBar's stacked background. This will appear @@ -484,6 +508,7 @@ public abstract class ActionBar { * * @return The current navigation mode. */ + @NavigationMode public abstract int getNavigationMode(); /** @@ -494,7 +519,7 @@ public abstract class ActionBar { * @see #NAVIGATION_MODE_LIST * @see #NAVIGATION_MODE_TABS */ - public abstract void setNavigationMode(int mode); + public abstract void setNavigationMode(@NavigationMode int mode); /** * @return The current set of display options. @@ -1024,7 +1049,7 @@ public abstract class ActionBar { }) public int gravity = Gravity.NO_GRAVITY; - public LayoutParams(Context c, AttributeSet attrs) { + public LayoutParams(@NonNull Context c, AttributeSet attrs) { super(c, attrs); TypedArray a = c.obtainStyledAttributes(attrs, diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 63c9fec..606d803 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -16,11 +16,18 @@ package android.app; +import android.annotation.NonNull; +import android.transition.Scene; +import android.transition.Transition; +import android.transition.TransitionManager; import android.util.ArrayMap; +import android.util.Pair; import android.util.SuperNotCalledException; import com.android.internal.app.ActionBarImpl; import com.android.internal.policy.PolicyManager; +import android.annotation.IntDef; +import android.annotation.Nullable; import android.content.ComponentCallbacks2; import android.content.ComponentName; import android.content.ContentResolver; @@ -84,6 +91,8 @@ import android.widget.AdapterView; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.HashMap; @@ -763,6 +772,7 @@ public class Activity extends ContextThemeWrapper private Thread mUiThread; final Handler mHandler = new Handler(); + private ActivityOptions mTransitionActivityOptions; /** Return the intent that started this activity. */ public Intent getIntent() { @@ -852,6 +862,7 @@ public class Activity extends ContextThemeWrapper * @see #getWindow * @see android.view.Window#getCurrentFocus */ + @Nullable public View getCurrentFocus() { return mWindow != null ? mWindow.getCurrentFocus() : null; } @@ -882,7 +893,7 @@ public class Activity extends ContextThemeWrapper * @see #onRestoreInstanceState * @see #onPostCreate */ - protected void onCreate(Bundle savedInstanceState) { + protected void onCreate(@Nullable Bundle savedInstanceState) { if (DEBUG_LIFECYCLE) Slog.v(TAG, "onCreate " + this + ": " + savedInstanceState); if (mLastNonConfigurationInstances != null) { mAllLoaderManagers = mLastNonConfigurationInstances.loaders; @@ -1010,7 +1021,7 @@ public class Activity extends ContextThemeWrapper * recently supplied in {@link #onSaveInstanceState}. <b><i>Note: Otherwise it is null.</i></b> * @see #onCreate */ - protected void onPostCreate(Bundle savedInstanceState) { + protected void onPostCreate(@Nullable Bundle savedInstanceState) { if (!isChild()) { mTitleReady = true; onTitleChanged(getTitle(), getTitleColor()); @@ -1021,7 +1032,7 @@ public class Activity extends ContextThemeWrapper /** * Called after {@link #onCreate} — or after {@link #onRestart} when * the activity had been stopped, but is now again being displayed to the - * user. It will be followed by {@link #onResume}. + * user. It will be followed by {@link #onResume}. * * <p><em>Derived classes must call through to the super class's * implementation of this method. If they do not, an exception will be @@ -1347,6 +1358,7 @@ public class Activity extends ContextThemeWrapper * @see #onSaveInstanceState * @see #onPause */ + @Nullable public CharSequence onCreateDescription() { return null; } @@ -1551,6 +1563,7 @@ public class Activity extends ContextThemeWrapper * {@link Fragment#setRetainInstance(boolean)} instead; this is also * available on older platforms through the Android compatibility package. */ + @Nullable @Deprecated public Object getLastNonConfigurationInstance() { return mLastNonConfigurationInstances != null @@ -1630,6 +1643,7 @@ public class Activity extends ContextThemeWrapper * @return Returns the object previously returned by * {@link #onRetainNonConfigurationChildInstances()} */ + @Nullable HashMap<String, Object> getLastNonConfigurationChildInstances() { return mLastNonConfigurationInstances != null ? mLastNonConfigurationInstances.children : null; @@ -1642,6 +1656,7 @@ public class Activity extends ContextThemeWrapper * set of child activities, such as ActivityGroup. The same guarantees and restrictions apply * as for {@link #onRetainNonConfigurationInstance()}. The default implementation returns null. */ + @Nullable HashMap<String,Object> onRetainNonConfigurationChildInstances() { return null; } @@ -1889,6 +1904,7 @@ public class Activity extends ContextThemeWrapper * * @return The Activity's ActionBar, or null if it does not have one. */ + @Nullable public ActionBar getActionBar() { initActionBar(); return mActionBar; @@ -1979,13 +1995,58 @@ public class Activity extends ContextThemeWrapper } /** + * Retrieve the {@link TransitionManager} responsible for default transitions in this window. + * Requires {@link Window#FEATURE_CONTENT_TRANSITIONS}. + * + * <p>This method will return non-null after content has been initialized (e.g. by using + * {@link #setContentView}) if {@link Window#FEATURE_CONTENT_TRANSITIONS} has been granted.</p> + * + * @return This window's content TransitionManager or null if none is set. + */ + public TransitionManager getContentTransitionManager() { + return getWindow().getTransitionManager(); + } + + /** + * Set the {@link TransitionManager} to use for default transitions in this window. + * Requires {@link Window#FEATURE_CONTENT_TRANSITIONS}. + * + * @param tm The TransitionManager to use for scene changes. + */ + public void setContentTransitionManager(TransitionManager tm) { + getWindow().setTransitionManager(tm); + } + + /** + * Retrieve the {@link Scene} representing this window's current content. + * Requires {@link Window#FEATURE_CONTENT_TRANSITIONS}. + * + * <p>This method will return null if the current content is not represented by a Scene.</p> + * + * @return Current Scene being shown or null + */ + public Scene getContentScene() { + return getWindow().getContentScene(); + } + + /** * Sets whether this activity is finished when touched outside its window's * bounds. */ public void setFinishOnTouchOutside(boolean finish) { mWindow.setCloseOnTouchOutside(finish); } - + + /** @hide */ + @IntDef({ + DEFAULT_KEYS_DISABLE, + DEFAULT_KEYS_DIALER, + DEFAULT_KEYS_SHORTCUT, + DEFAULT_KEYS_SEARCH_LOCAL, + DEFAULT_KEYS_SEARCH_GLOBAL}) + @Retention(RetentionPolicy.SOURCE) + @interface DefaultKeyMode {} + /** * Use with {@link #setDefaultKeyMode} to turn off default handling of * keys. @@ -2055,7 +2116,7 @@ public class Activity extends ContextThemeWrapper * @see #DEFAULT_KEYS_SEARCH_GLOBAL * @see #onKeyDown */ - public final void setDefaultKeyMode(int mode) { + public final void setDefaultKeyMode(@DefaultKeyMode int mode) { mDefaultKeyMode = mode; // Some modes use a SpannableStringBuilder to track & dispatch input events @@ -2528,6 +2589,7 @@ public class Activity extends ContextThemeWrapper * simply returns null so that all panel sub-windows will have the default * menu behavior. */ + @Nullable public View onCreatePanelView(int featureId) { return null; } @@ -3025,6 +3087,7 @@ public class Activity extends ContextThemeWrapper * {@link FragmentManager} instead; this is also * available on older platforms through the Android compatibility package. */ + @Nullable @Deprecated protected Dialog onCreateDialog(int id, Bundle args) { return onCreateDialog(id); @@ -3112,6 +3175,7 @@ public class Activity extends ContextThemeWrapper * {@link FragmentManager} instead; this is also * available on older platforms through the Android compatibility package. */ + @Nullable @Deprecated public final boolean showDialog(int id, Bundle args) { if (mManagedDialogs == null) { @@ -3233,13 +3297,13 @@ public class Activity extends ContextThemeWrapper * <p>It is typically called from onSearchRequested(), either directly from * Activity.onSearchRequested() or from an overridden version in any given * Activity. If your goal is simply to activate search, it is preferred to call - * onSearchRequested(), which may have been overriden elsewhere in your Activity. If your goal + * onSearchRequested(), which may have been overridden elsewhere in your Activity. If your goal * is to inject specific data such as context data, it is preferred to <i>override</i> * onSearchRequested(), so that any callers to it will benefit from the override. * * @param initialQuery Any non-null non-empty string will be inserted as * pre-entered text in the search query box. - * @param selectInitialQuery If true, the intial query will be preselected, which means that + * @param selectInitialQuery If true, the initial query will be preselected, which means that * any further typing will replace it. This is useful for cases where an entire pre-formed * query is being inserted. If false, the selection point will be placed at the end of the * inserted query. This is useful when the inserted query is text that the user entered, @@ -3257,11 +3321,11 @@ public class Activity extends ContextThemeWrapper * @see android.app.SearchManager * @see #onSearchRequested */ - public void startSearch(String initialQuery, boolean selectInitialQuery, - Bundle appSearchData, boolean globalSearch) { + public void startSearch(@Nullable String initialQuery, boolean selectInitialQuery, + @Nullable Bundle appSearchData, boolean globalSearch) { ensureSearchManager(); mSearchManager.startSearch(initialQuery, selectInitialQuery, getComponentName(), - appSearchData, globalSearch); + appSearchData, globalSearch); } /** @@ -3274,7 +3338,7 @@ public class Activity extends ContextThemeWrapper * searches. This data will be returned with SEARCH intent(s). Null if * no extra data is required. */ - public void triggerSearch(String query, Bundle appSearchData) { + public void triggerSearch(String query, @Nullable Bundle appSearchData) { ensureSearchManager(); mSearchManager.triggerSearch(query, getComponentName(), appSearchData); } @@ -3341,6 +3405,7 @@ public class Activity extends ContextThemeWrapper * Convenience for calling * {@link android.view.Window#getLayoutInflater}. */ + @NonNull public LayoutInflater getLayoutInflater() { return getWindow().getLayoutInflater(); } @@ -3348,6 +3413,7 @@ public class Activity extends ContextThemeWrapper /** * Returns a {@link MenuInflater} with this context. */ + @NonNull public MenuInflater getMenuInflater() { // Make sure that action views can get an appropriate theme. if (mMenuInflater == null) { @@ -3386,16 +3452,21 @@ public class Activity extends ContextThemeWrapper * * @throws android.content.ActivityNotFoundException * - * @see #startActivity + * @see #startActivity */ public void startActivityForResult(Intent intent, int requestCode) { - startActivityForResult(intent, requestCode, null); + Bundle options = null; + if (mWindow.hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) { + final Pair<View, String>[] noSharedElements = null; + options = ActivityOptions.makeSceneTransitionAnimation(noSharedElements).toBundle(); + } + startActivityForResult(intent, requestCode, options); } /** * Launch an activity for which you would like a result when it finished. * When this activity exits, your - * onActivityResult() method will be called with the given requestCode. + * onActivityResult() method will be called with the given requestCode. * Using a negative requestCode is the same as calling * {@link #startActivity} (the activity is not launched as a sub-activity). * @@ -3408,9 +3479,9 @@ public class Activity extends ContextThemeWrapper * * <p>As a special case, if you call startActivityForResult() with a requestCode * >= 0 during the initial onCreate(Bundle savedInstanceState)/onResume() of your - * activity, then your window will not be displayed until a result is - * returned back from the started activity. This is to avoid visible - * flickering when redirecting to another activity. + * activity, then your window will not be displayed until a result is + * returned back from the started activity. This is to avoid visible + * flickering when redirecting to another activity. * * <p>This method throws {@link android.content.ActivityNotFoundException} * if there was no Activity found to run the given Intent. @@ -3424,9 +3495,20 @@ public class Activity extends ContextThemeWrapper * * @throws android.content.ActivityNotFoundException * - * @see #startActivity + * @see #startActivity */ - public void startActivityForResult(Intent intent, int requestCode, Bundle options) { + public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) { + if (options != null) { + ActivityOptions activityOptions = new ActivityOptions(options); + if (activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) { + if (mActionBar != null) { + ArrayMap<String, View> sharedElementMap = new ArrayMap<String, View>(); + mActionBar.captureSharedElements(sharedElementMap); + activityOptions.addSharedElements(sharedElementMap); + } + options = mWindow.startExitTransition(activityOptions); + } + } if (mParent == null) { Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity( @@ -3505,7 +3587,7 @@ public class Activity extends ContextThemeWrapper * @param extraFlags Always set to 0. */ public void startIntentSenderForResult(IntentSender intent, int requestCode, - Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) + @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) throws IntentSender.SendIntentException { startIntentSenderForResult(intent, requestCode, fillInIntent, flagsMask, flagsValues, extraFlags, null); @@ -3537,7 +3619,7 @@ public class Activity extends ContextThemeWrapper * override any that conflict with those given by the IntentSender. */ public void startIntentSenderForResult(IntentSender intent, int requestCode, - Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, + @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) throws IntentSender.SendIntentException { if (mParent == null) { startIntentSenderForResultInner(intent, requestCode, fillInIntent, @@ -3599,7 +3681,7 @@ public class Activity extends ContextThemeWrapper */ @Override public void startActivity(Intent intent) { - startActivity(intent, null); + this.startActivity(intent, null); } /** @@ -3625,7 +3707,7 @@ public class Activity extends ContextThemeWrapper * @see #startActivityForResult */ @Override - public void startActivity(Intent intent, Bundle options) { + public void startActivity(Intent intent, @Nullable Bundle options) { if (options != null) { startActivityForResult(intent, -1, options); } else { @@ -3674,7 +3756,7 @@ public class Activity extends ContextThemeWrapper * @see #startActivityForResult */ @Override - public void startActivities(Intent[] intents, Bundle options) { + public void startActivities(Intent[] intents, @Nullable Bundle options) { mInstrumentation.execStartActivities(this, mMainThread.getApplicationThread(), mToken, this, intents, options); } @@ -3693,7 +3775,7 @@ public class Activity extends ContextThemeWrapper * @param extraFlags Always set to 0. */ public void startIntentSender(IntentSender intent, - Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) + @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) throws IntentSender.SendIntentException { startIntentSender(intent, fillInIntent, flagsMask, flagsValues, extraFlags, null); @@ -3720,7 +3802,7 @@ public class Activity extends ContextThemeWrapper * override any that conflict with those given by the IntentSender. */ public void startIntentSender(IntentSender intent, - Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, + @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) throws IntentSender.SendIntentException { if (options != null) { startIntentSenderForResult(intent, -1, fillInIntent, flagsMask, @@ -3748,7 +3830,7 @@ public class Activity extends ContextThemeWrapper * @see #startActivity * @see #startActivityForResult */ - public boolean startActivityIfNeeded(Intent intent, int requestCode) { + public boolean startActivityIfNeeded(@NonNull Intent intent, int requestCode) { return startActivityIfNeeded(intent, requestCode, null); } @@ -3782,7 +3864,8 @@ public class Activity extends ContextThemeWrapper * @see #startActivity * @see #startActivityForResult */ - public boolean startActivityIfNeeded(Intent intent, int requestCode, Bundle options) { + public boolean startActivityIfNeeded(@NonNull Intent intent, int requestCode, + @Nullable Bundle options) { if (mParent == null) { int result = ActivityManager.START_RETURN_INTENT_TO_CALLER; try { @@ -3831,7 +3914,7 @@ public class Activity extends ContextThemeWrapper * wasn't. In general, if true is returned you will then want to call * finish() on yourself. */ - public boolean startNextMatchingActivity(Intent intent) { + public boolean startNextMatchingActivity(@NonNull Intent intent) { return startNextMatchingActivity(intent, null); } @@ -3854,7 +3937,7 @@ public class Activity extends ContextThemeWrapper * wasn't. In general, if true is returned you will then want to call * finish() on yourself. */ - public boolean startNextMatchingActivity(Intent intent, Bundle options) { + public boolean startNextMatchingActivity(@NonNull Intent intent, @Nullable Bundle options) { if (mParent == null) { try { intent.migrateExtraStreamToClipData(); @@ -3884,7 +3967,7 @@ public class Activity extends ContextThemeWrapper * @see #startActivity * @see #startActivityForResult */ - public void startActivityFromChild(Activity child, Intent intent, + public void startActivityFromChild(@NonNull Activity child, Intent intent, int requestCode) { startActivityFromChild(child, intent, requestCode, null); } @@ -3908,8 +3991,8 @@ public class Activity extends ContextThemeWrapper * @see #startActivity * @see #startActivityForResult */ - public void startActivityFromChild(Activity child, Intent intent, - int requestCode, Bundle options) { + public void startActivityFromChild(@NonNull Activity child, Intent intent, + int requestCode, @Nullable Bundle options) { Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity( this, mMainThread.getApplicationThread(), mToken, child, @@ -3934,7 +4017,7 @@ public class Activity extends ContextThemeWrapper * @see Fragment#startActivity * @see Fragment#startActivityForResult */ - public void startActivityFromFragment(Fragment fragment, Intent intent, + public void startActivityFromFragment(@NonNull Fragment fragment, Intent intent, int requestCode) { startActivityFromFragment(fragment, intent, requestCode, null); } @@ -3959,8 +4042,8 @@ public class Activity extends ContextThemeWrapper * @see Fragment#startActivity * @see Fragment#startActivityForResult */ - public void startActivityFromFragment(Fragment fragment, Intent intent, - int requestCode, Bundle options) { + public void startActivityFromFragment(@NonNull Fragment fragment, Intent intent, + int requestCode, @Nullable Bundle options) { Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity( this, mMainThread.getApplicationThread(), mToken, fragment, @@ -3992,7 +4075,7 @@ public class Activity extends ContextThemeWrapper */ public void startIntentSenderFromChild(Activity child, IntentSender intent, int requestCode, Intent fillInIntent, int flagsMask, int flagsValues, - int extraFlags, Bundle options) + int extraFlags, @Nullable Bundle options) throws IntentSender.SendIntentException { startIntentSenderForResultInner(intent, requestCode, fillInIntent, flagsMask, flagsValues, child, options); @@ -4091,6 +4174,7 @@ public class Activity extends ContextThemeWrapper * @return The package of the activity that will receive your * reply, or null if none. */ + @Nullable public String getCallingPackage() { try { return ActivityManagerNative.getDefault().getCallingPackage(mToken); @@ -4113,6 +4197,7 @@ public class Activity extends ContextThemeWrapper * @return The ComponentName of the activity that will receive your * reply, or null if none. */ + @Nullable public ComponentName getCallingActivity() { try { return ActivityManagerNative.getDefault().getCallingActivity(mToken); @@ -4305,7 +4390,7 @@ public class Activity extends ContextThemeWrapper * @param requestCode Request code that had been used to start the * activity. */ - public void finishActivityFromChild(Activity child, int requestCode) { + public void finishActivityFromChild(@NonNull Activity child, int requestCode) { try { ActivityManagerNative.getDefault() .finishSubActivity(mToken, child.mEmbeddedID, requestCode); @@ -4366,8 +4451,8 @@ public class Activity extends ContextThemeWrapper * * @see PendingIntent */ - public PendingIntent createPendingResult(int requestCode, Intent data, - int flags) { + public PendingIntent createPendingResult(int requestCode, @NonNull Intent data, + @PendingIntent.Flags int flags) { String packageName = getPackageName(); try { data.prepareToLeaveProcess(); @@ -4394,7 +4479,7 @@ public class Activity extends ContextThemeWrapper * @param requestedOrientation An orientation constant as used in * {@link ActivityInfo#screenOrientation ActivityInfo.screenOrientation}. */ - public void setRequestedOrientation(int requestedOrientation) { + public void setRequestedOrientation(@ActivityInfo.ScreenOrientation int requestedOrientation) { if (mParent == null) { try { ActivityManagerNative.getDefault().setRequestedOrientation( @@ -4416,6 +4501,7 @@ public class Activity extends ContextThemeWrapper * @return Returns an orientation constant as used in * {@link ActivityInfo#screenOrientation ActivityInfo.screenOrientation}. */ + @ActivityInfo.ScreenOrientation public int getRequestedOrientation() { if (mParent == null) { try { @@ -4487,6 +4573,7 @@ public class Activity extends ContextThemeWrapper * * @return The local class name. */ + @NonNull public String getLocalClassName() { final String pkg = getPackageName(); final String cls = mComponent.getClassName(); @@ -4532,9 +4619,9 @@ public class Activity extends ContextThemeWrapper mSearchManager = new SearchManager(this, null); } - + @Override - public Object getSystemService(String name) { + public Object getSystemService(@ServiceName @NonNull String name) { if (getBaseContext() == null) { throw new IllegalStateException( "System services not available to Activities before onCreate()"); @@ -4574,6 +4661,17 @@ public class Activity extends ContextThemeWrapper setTitle(getText(titleId)); } + /** + * Change the color of the title associated with this activity. + * <p> + * This method is deprecated starting in API Level 11 and replaced by action + * bar styles. For information on styling the Action Bar, read the <a + * href="{@docRoot} guide/topics/ui/actionbar.html">Action Bar</a> developer + * guide. + * + * @deprecated Use action bar styles instead. + */ + @Deprecated public void setTitleColor(int textColor) { mTitleColor = textColor; onTitleChanged(mTitle, textColor); @@ -4639,7 +4737,8 @@ public class Activity extends ContextThemeWrapper */ public final void setProgressBarIndeterminate(boolean indeterminate) { getWindow().setFeatureInt(Window.FEATURE_PROGRESS, - indeterminate ? Window.PROGRESS_INDETERMINATE_ON : Window.PROGRESS_INDETERMINATE_OFF); + indeterminate ? Window.PROGRESS_INDETERMINATE_ON + : Window.PROGRESS_INDETERMINATE_OFF); } /** @@ -4696,7 +4795,7 @@ public class Activity extends ContextThemeWrapper /** * Gets the suggested audio stream whose volume should be changed by the - * harwdare volume controls. + * hardware volume controls. * * @return The suggested audio stream type whose volume should be changed by * the hardware volume controls. @@ -4732,6 +4831,7 @@ public class Activity extends ContextThemeWrapper * @see android.view.LayoutInflater#createView * @see android.view.Window#getLayoutInflater */ + @Nullable public View onCreateView(String name, Context context, AttributeSet attrs) { return null; } @@ -4990,6 +5090,7 @@ public class Activity extends ContextThemeWrapper * * @see ActionMode */ + @Nullable public ActionMode startActionMode(ActionMode.Callback callback) { return mWindow.getDecorView().startActionMode(callback); } @@ -5005,6 +5106,7 @@ public class Activity extends ContextThemeWrapper * @return The new action mode, or <code>null</code> if the activity does not want to * provide special handling for this action mode. (It will be handled by the system.) */ + @Nullable @Override public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) { initActionBar(); @@ -5148,6 +5250,7 @@ public class Activity extends ContextThemeWrapper * @return a new Intent targeting the defined parent of this activity or null if * there is no valid parent. */ + @Nullable public Intent getParentActivityIntent() { final String parentName = mActivityInfo.parentActivityName; if (TextUtils.isEmpty(parentName)) { @@ -5190,6 +5293,16 @@ public class Activity extends ContextThemeWrapper CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config) { + attach(context, aThread, instr, token, ident, application, intent, info, title, parent, id, + lastNonConfigurationInstances, config, null); + } + + final void attach(Context context, ActivityThread aThread, + Instrumentation instr, IBinder token, int ident, + Application application, Intent intent, ActivityInfo info, + CharSequence title, Activity parent, String id, + NonConfigurationInstances lastNonConfigurationInstances, + Configuration config, Bundle options) { attachBaseContext(context); mFragments.attachActivity(this, mContainer, null); @@ -5204,7 +5317,7 @@ public class Activity extends ContextThemeWrapper mWindow.setUiOptions(info.uiOptions); } mUiThread = Thread.currentThread(); - + mMainThread = aThread; mInstrumentation = instr; mToken = token; @@ -5227,6 +5340,43 @@ public class Activity extends ContextThemeWrapper } mWindowManager = mWindow.getWindowManager(); mCurrentConfig = config; + mTransitionActivityOptions = null; + Window.SceneTransitionListener sceneTransitionListener = null; + if (options != null) { + ActivityOptions activityOptions = new ActivityOptions(options); + if (activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) { + mTransitionActivityOptions = activityOptions; + sceneTransitionListener = new Window.SceneTransitionListener() { + @Override + public void nullPendingTransition() { + overridePendingTransition(0, 0); + } + + @Override + public void convertFromTranslucent() { + Activity.this.convertFromTranslucent(); + } + + @Override + public void convertToTranslucent() { + Activity.this.convertToTranslucent(null); + } + + @Override + public void sharedElementStart(Transition transition) { + Activity.this.onCaptureSharedElementStart(transition); + } + + @Override + public void sharedElementEnd() { + Activity.this.onCaptureSharedElementEnd(); + } + }; + + } + } + + mWindow.setTransitionOptions(mTransitionActivityOptions, sceneTransitionListener); } /** @hide */ @@ -5240,7 +5390,7 @@ public class Activity extends ContextThemeWrapper com.android.internal.R.styleable.Window_windowNoDisplay, false); mFragments.dispatchActivityCreated(); } - + final void performStart() { mFragments.noteStateNotSaved(); mCalled = false; @@ -5397,7 +5547,7 @@ public class Activity extends ContextThemeWrapper } } } - + mStopped = true; } mResumed = false; @@ -5412,7 +5562,27 @@ public class Activity extends ContextThemeWrapper mLoaderManager.doDestroy(); } } - + + /** + * Called when setting up Activity Scene transitions when the start state for shared + * elements has been captured. Override this method to modify the start position of shared + * elements for the entry Transition. + * + * @param transition The <code>Transition</code> being used to change + * bounds of shared elements in the source Activity to + * the bounds defined by the entering Scene. + */ + public void onCaptureSharedElementStart(Transition transition) { + } + + /** + * Called when setting up Activity Scene transitions when the final state for + * shared elements state has been captured. Override this method to modify the destination + * position of shared elements for the entry Transition. + */ + public void onCaptureSharedElementEnd() { + } + /** * @hide */ @@ -5420,7 +5590,7 @@ public class Activity extends ContextThemeWrapper return mResumed; } - void dispatchActivityResult(String who, int requestCode, + void dispatchActivityResult(String who, int requestCode, int resultCode, Intent data) { if (false) Log.v( TAG, "Dispatching result: who=" + who + ", reqCode=" + requestCode @@ -5436,6 +5606,22 @@ public class Activity extends ContextThemeWrapper } } + /** @hide */ + public void startLockTask() { + try { + ActivityManagerNative.getDefault().startLockTaskMode(mToken); + } catch (RemoteException e) { + } + } + + /** @hide */ + public void stopLockTask() { + try { + ActivityManagerNative.getDefault().stopLockTaskMode(); + } catch (RemoteException e) { + } + } + /** * Interface for informing a translucent {@link Activity} once all visible activities below it * have completed drawing. This is necessary only after an {@link Activity} has been made diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index c877cd3..a2183e6 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -155,6 +155,13 @@ public class ActivityManager { public static final int START_SWITCHES_CANCELED = 4; /** + * Result for IActivityManaqer.startActivity: a new activity was attempted to be started + * while in Lock Task Mode. + * @hide + */ + public static final int START_RETURN_LOCK_TASK_MODE_VIOLATION = 5; + + /** * Flag for IActivityManaqer.startActivity: do special start mode where * a new activity is launched only if it is needed. * @hide @@ -933,6 +940,16 @@ public class ActivityManager { } } + /** @hide */ + public boolean isInHomeStack(int taskId) { + try { + return ActivityManagerNative.getDefault().isInHomeStack(taskId); + } catch (RemoteException e) { + // System dead, we will be dead too soon! + return false; + } + } + /** * Flag for {@link #moveTaskToFront(int, int)}: also move the "home" * activity along with the task, so it is positioned immediately behind @@ -2222,4 +2239,35 @@ public class ActivityManager { e.printStackTrace(pw); } } + + /** + * @hide + */ + public void startLockTaskMode(int taskId) { + try { + ActivityManagerNative.getDefault().startLockTaskMode(taskId); + } catch (RemoteException e) { + } + } + + /** + * @hide + */ + public void stopLockTaskMode() { + try { + ActivityManagerNative.getDefault().stopLockTaskMode(); + } catch (RemoteException e) { + } + } + + /** + * @hide + */ + public boolean isInLockTaskMode() { + try { + return ActivityManagerNative.getDefault().isInLockTaskMode(); + } catch (RemoteException e) { + return false; + } + } } diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 7695ecc..373a8a3 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -101,9 +101,9 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM } } - static public void noteWakeupAlarm(PendingIntent ps) { + static public void noteWakeupAlarm(PendingIntent ps, int sourceUid, String sourcePkg) { try { - getDefault().noteWakeupAlarm(ps.getTarget()); + getDefault().noteWakeupAlarm(ps.getTarget(), sourceUid, sourcePkg); } catch (RemoteException ex) { } } @@ -654,6 +654,15 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case IS_IN_HOME_STACK_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + int taskId = data.readInt(); + boolean isInHomeStack = isInHomeStack(taskId); + reply.writeNoException(); + reply.writeInt(isInHomeStack ? 1 : 0); + return true; + } + case SET_FOCUSED_STACK_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); int stackId = data.readInt(); @@ -1258,7 +1267,9 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM data.enforceInterface(IActivityManager.descriptor); IIntentSender is = IIntentSender.Stub.asInterface( data.readStrongBinder()); - noteWakeupAlarm(is); + int sourceUid = data.readInt(); + String sourcePkg = data.readString(); + noteWakeupAlarm(is, sourceUid, sourcePkg); reply.writeNoException(); return true; } @@ -1711,6 +1722,15 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case START_USER_IN_BACKGROUND_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + int userid = data.readInt(); + boolean result = startUserInBackground(userid); + reply.writeNoException(); + reply.writeInt(result ? 1 : 0); + return true; + } + case STOP_USER_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); int userid = data.readInt(); @@ -1841,6 +1861,17 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case GET_TAG_FOR_INTENT_SENDER_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IIntentSender r = IIntentSender.Stub.asInterface( + data.readStrongBinder()); + String prefix = data.readString(); + String tag = getTagForIntentSender(r, prefix); + reply.writeNoException(); + reply.writeString(tag); + return true; + } + case UPDATE_PERSISTENT_CONFIGURATION_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); Configuration config = Configuration.CREATOR.createFromParcel(data); @@ -2066,6 +2097,37 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM reply.writeStrongBinder(homeActivityToken); return true; } + + case START_LOCK_TASK_BY_TASK_ID_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + final int taskId = data.readInt(); + startLockTaskMode(taskId); + reply.writeNoException(); + return true; + } + + case START_LOCK_TASK_BY_TOKEN_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder token = data.readStrongBinder(); + startLockTaskMode(token); + reply.writeNoException(); + return true; + } + + case STOP_LOCK_TASK_MODE_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + stopLockTaskMode(); + reply.writeNoException(); + return true; + } + + case IS_IN_LOCK_TASK_MODE_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + final boolean isInLockTaskMode = isInLockTaskMode(); + reply.writeNoException(); + reply.writeInt(isInLockTaskMode ? 1 : 0); + return true; + } } return super.onTransact(code, data, reply, flags); @@ -2811,6 +2873,19 @@ class ActivityManagerProxy implements IActivityManager return info; } @Override + public boolean isInHomeStack(int taskId) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(taskId); + mRemote.transact(IS_IN_HOME_STACK_TRANSACTION, data, reply, 0); + reply.readException(); + boolean isInHomeStack = reply.readInt() > 0; + data.recycle(); + reply.recycle(); + return isInHomeStack; + } + @Override public void setFocusedStack(int stackId) throws RemoteException { Parcel data = Parcel.obtain(); @@ -3660,10 +3735,13 @@ class ActivityManagerProxy implements IActivityManager mRemote.transact(ENTER_SAFE_MODE_TRANSACTION, data, null, 0); data.recycle(); } - public void noteWakeupAlarm(IIntentSender sender) throws RemoteException { + public void noteWakeupAlarm(IIntentSender sender, int sourceUid, String sourcePkg) + throws RemoteException { Parcel data = Parcel.obtain(); - data.writeStrongBinder(sender.asBinder()); data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(sender.asBinder()); + data.writeInt(sourceUid); + data.writeString(sourcePkg); mRemote.transact(NOTE_WAKEUP_ALARM_TRANSACTION, data, null, 0); data.recycle(); } @@ -4288,6 +4366,19 @@ class ActivityManagerProxy implements IActivityManager return result; } + public boolean startUserInBackground(int userid) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(userid); + mRemote.transact(START_USER_IN_BACKGROUND_TRANSACTION, data, reply, 0); + reply.readException(); + boolean result = reply.readInt() != 0; + reply.recycle(); + data.recycle(); + return result; + } + public int stopUser(int userid, IStopUserCallback callback) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); @@ -4430,6 +4521,21 @@ class ActivityManagerProxy implements IActivityManager return res; } + public String getTagForIntentSender(IIntentSender sender, String prefix) + throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(sender.asBinder()); + data.writeString(prefix); + mRemote.transact(GET_TAG_FOR_INTENT_SENDER_TRANSACTION, data, reply, 0); + reply.readException(); + String res = reply.readString(); + data.recycle(); + reply.recycle(); + return res; + } + public void updatePersistentConfiguration(Configuration values) throws RemoteException { Parcel data = Parcel.obtain(); @@ -4745,5 +4851,53 @@ class ActivityManagerProxy implements IActivityManager return res; } + @Override + public void startLockTaskMode(int taskId) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(taskId); + mRemote.transact(START_LOCK_TASK_BY_TASK_ID_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + + @Override + public void startLockTaskMode(IBinder token) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(token); + mRemote.transact(START_LOCK_TASK_BY_TOKEN_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + + @Override + public void stopLockTaskMode() throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + mRemote.transact(STOP_LOCK_TASK_MODE_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + + @Override + public boolean isInLockTaskMode() throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + mRemote.transact(IS_IN_LOCK_TASK_MODE_TRANSACTION, data, reply, 0); + reply.readException(); + boolean isInLockTaskMode = reply.readInt() == 1; + data.recycle(); + reply.recycle(); + return isInLockTaskMode; + } + private IBinder mRemote; } diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 87b1e24..07247ff 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -22,14 +22,22 @@ import android.os.Bundle; import android.os.Handler; import android.os.IRemoteCallback; import android.os.RemoteException; +import android.transition.Transition; +import android.util.Log; +import android.util.Pair; import android.view.View; +import java.util.ArrayList; +import java.util.Map; + /** * Helper class for building an options Bundle that can be used with * {@link android.content.Context#startActivity(android.content.Intent, android.os.Bundle) * Context.startActivity(Intent, Bundle)} and related methods. */ public class ActivityOptions { + private static final String TAG = "ActivityOptions"; + /** * The package name that created the options. * @hide @@ -90,6 +98,31 @@ public class ActivityOptions { */ public static final String KEY_ANIM_START_LISTENER = "android:animStartListener"; + /** + * For Activity transitions, the calling Activity's TransitionListener used to + * notify the called Activity when the shared element and the exit transitions + * complete. + */ + private static final String KEY_TRANSITION_COMPLETE_LISTENER + = "android:transitionCompleteListener"; + + /** + * For Activity transitions, the called Activity's listener to receive calls + * when transitions complete. + */ + private static final String KEY_TRANSITION_TARGET_LISTENER = "android:transitionTargetListener"; + + /** + * The names of shared elements that are transitioned to the started Activity. + * This is also the name of shared elements that the started Activity accepted. + */ + private static final String KEY_SHARED_ELEMENT_NAMES = "android:shared_element_names"; + + /** + * The shared elements names of the views in the calling Activity. + */ + private static final String KEY_LOCAL_ELEMENT_NAMES = "android:local_element_names"; + /** @hide */ public static final int ANIM_NONE = 0; /** @hide */ @@ -100,6 +133,8 @@ public class ActivityOptions { public static final int ANIM_THUMBNAIL_SCALE_UP = 3; /** @hide */ public static final int ANIM_THUMBNAIL_SCALE_DOWN = 4; + /** @hide */ + public static final int ANIM_SCENE_TRANSITION = 5; private String mPackageName; private int mAnimationType = ANIM_NONE; @@ -111,6 +146,9 @@ public class ActivityOptions { private int mStartWidth; private int mStartHeight; private IRemoteCallback mAnimationStartedListener; + private IRemoteCallback mTransitionCompleteListener; + private ArrayList<String> mSharedElementNames; + private ArrayList<String> mLocalElementNames; /** * Create an ActivityOptions specifying a custom animation to run when @@ -156,11 +194,12 @@ public class ActivityOptions { opts.mAnimationType = ANIM_CUSTOM; opts.mCustomEnterResId = enterResId; opts.mCustomExitResId = exitResId; - opts.setListener(handler, listener); + opts.setOnAnimationStartedListener(handler, listener); return opts; } - private void setListener(Handler handler, OnAnimationStartedListener listener) { + private void setOnAnimationStartedListener(Handler handler, + OnAnimationStartedListener listener) { if (listener != null) { final Handler h = handler; final OnAnimationStartedListener finalListener = listener; @@ -185,6 +224,12 @@ public class ActivityOptions { void onAnimationStarted(); } + /** @hide */ + public interface ActivityTransitionTarget { + void sharedElementTransitionComplete(Bundle transitionArgs); + void exitTransitionComplete(); + } + /** * Create an ActivityOptions specifying an animation where the new * activity is scaled from a small originating area of the screen to @@ -298,7 +343,56 @@ public class ActivityOptions { source.getLocationOnScreen(pts); opts.mStartX = pts[0] + startX; opts.mStartY = pts[1] + startY; - opts.setListener(source.getHandler(), listener); + opts.setOnAnimationStartedListener(source.getHandler(), listener); + return opts; + } + + /** + * Create an ActivityOptions to transition between Activities using cross-Activity scene + * animations. This method carries the position of one shared element to the started Activity. + * + * <p>This requires {@link android.view.Window#FEATURE_CONTENT_TRANSITIONS} to be + * enabled on the calling Activity to cause an exit transition. The same must be in + * the called Activity to get an entering transition.</p> + * @param sharedElement The View to transition to the started Activity. sharedElement must + * have a non-null sharedElementName. + * @param sharedElementName The shared element name as used in the target Activity. This may + * be null if it has the same name as sharedElement. + * @return Returns a new ActivityOptions object that you can use to + * supply these options as the options Bundle when starting an activity. + */ + public static ActivityOptions makeSceneTransitionAnimation(View sharedElement, + String sharedElementName) { + return makeSceneTransitionAnimation( + new Pair<View, String>(sharedElement, sharedElementName)); + } + + /** + * Create an ActivityOptions to transition between Activities using cross-Activity scene + * animations. This method carries the position of multiple shared elements to the started + * Activity. + * + * <p>This requires {@link android.view.Window#FEATURE_CONTENT_TRANSITIONS} to be + * enabled on the calling Activity to cause an exit transition. The same must be in + * the called Activity to get an entering transition.</p> + * @param sharedElements The View to transition to the started Activity along with the + * shared element name as used in the started Activity. The view + * must have a non-null sharedElementName. + * @return Returns a new ActivityOptions object that you can use to + * supply these options as the options Bundle when starting an activity. + */ + public static ActivityOptions makeSceneTransitionAnimation( + Pair<View, String>... sharedElements) { + ActivityOptions opts = new ActivityOptions(); + opts.mAnimationType = ANIM_SCENE_TRANSITION; + opts.mSharedElementNames = new ArrayList<String>(); + opts.mLocalElementNames = new ArrayList<String>(); + + if (sharedElements != null) { + for (Pair<View, String> sharedElement : sharedElements) { + opts.addSharedElement(sharedElement.first, sharedElement.second); + } + } return opts; } @@ -309,23 +403,36 @@ public class ActivityOptions { public ActivityOptions(Bundle opts) { mPackageName = opts.getString(KEY_PACKAGE_NAME); mAnimationType = opts.getInt(KEY_ANIM_TYPE); - if (mAnimationType == ANIM_CUSTOM) { - mCustomEnterResId = opts.getInt(KEY_ANIM_ENTER_RES_ID, 0); - mCustomExitResId = opts.getInt(KEY_ANIM_EXIT_RES_ID, 0); - mAnimationStartedListener = IRemoteCallback.Stub.asInterface( - opts.getIBinder(KEY_ANIM_START_LISTENER)); - } else if (mAnimationType == ANIM_SCALE_UP) { - mStartX = opts.getInt(KEY_ANIM_START_X, 0); - mStartY = opts.getInt(KEY_ANIM_START_Y, 0); - mStartWidth = opts.getInt(KEY_ANIM_START_WIDTH, 0); - mStartHeight = opts.getInt(KEY_ANIM_START_HEIGHT, 0); - } else if (mAnimationType == ANIM_THUMBNAIL_SCALE_UP || - mAnimationType == ANIM_THUMBNAIL_SCALE_DOWN) { - mThumbnail = (Bitmap)opts.getParcelable(KEY_ANIM_THUMBNAIL); - mStartX = opts.getInt(KEY_ANIM_START_X, 0); - mStartY = opts.getInt(KEY_ANIM_START_Y, 0); - mAnimationStartedListener = IRemoteCallback.Stub.asInterface( - opts.getIBinder(KEY_ANIM_START_LISTENER)); + switch (mAnimationType) { + case ANIM_CUSTOM: + mCustomEnterResId = opts.getInt(KEY_ANIM_ENTER_RES_ID, 0); + mCustomExitResId = opts.getInt(KEY_ANIM_EXIT_RES_ID, 0); + mAnimationStartedListener = IRemoteCallback.Stub.asInterface( + opts.getBinder(KEY_ANIM_START_LISTENER)); + break; + + case ANIM_SCALE_UP: + mStartX = opts.getInt(KEY_ANIM_START_X, 0); + mStartY = opts.getInt(KEY_ANIM_START_Y, 0); + mStartWidth = opts.getInt(KEY_ANIM_START_WIDTH, 0); + mStartHeight = opts.getInt(KEY_ANIM_START_HEIGHT, 0); + break; + + case ANIM_THUMBNAIL_SCALE_UP: + case ANIM_THUMBNAIL_SCALE_DOWN: + mThumbnail = (Bitmap)opts.getParcelable(KEY_ANIM_THUMBNAIL); + mStartX = opts.getInt(KEY_ANIM_START_X, 0); + mStartY = opts.getInt(KEY_ANIM_START_Y, 0); + mAnimationStartedListener = IRemoteCallback.Stub.asInterface( + opts.getBinder(KEY_ANIM_START_LISTENER)); + break; + + case ANIM_SCENE_TRANSITION: + mTransitionCompleteListener = IRemoteCallback.Stub.asInterface( + opts.getBinder(KEY_TRANSITION_COMPLETE_LISTENER)); + mSharedElementNames = opts.getStringArrayList(KEY_SHARED_ELEMENT_NAMES); + mLocalElementNames = opts.getStringArrayList(KEY_LOCAL_ELEMENT_NAMES); + break; } } @@ -380,6 +487,54 @@ public class ActivityOptions { } /** @hide */ + public ArrayList<String> getSharedElementNames() { return mSharedElementNames; } + + /** @hide */ + public ArrayList<String> getLocalElementNames() { return mLocalElementNames; } + + /** @hide */ + public void dispatchSceneTransitionStarted(final ActivityTransitionTarget target, + ArrayList<String> sharedElementNames) { + boolean listenerSent = false; + if (mTransitionCompleteListener != null) { + IRemoteCallback callback = new IRemoteCallback.Stub() { + @Override + public void sendResult(Bundle data) throws RemoteException { + if (data == null) { + target.exitTransitionComplete(); + } else { + target.sharedElementTransitionComplete(data); + } + } + }; + Bundle bundle = new Bundle(); + bundle.putBinder(KEY_TRANSITION_TARGET_LISTENER, callback.asBinder()); + bundle.putStringArrayList(KEY_SHARED_ELEMENT_NAMES, sharedElementNames); + try { + mTransitionCompleteListener.sendResult(bundle); + listenerSent = true; + } catch (RemoteException e) { + Log.w(TAG, "Couldn't retrieve transition notifications", e); + } + } + if (!listenerSent) { + target.sharedElementTransitionComplete(null); + target.exitTransitionComplete(); + } + } + + /** @hide */ + public void dispatchSharedElementsReady() { + if (mTransitionCompleteListener != null) { + try { + mTransitionCompleteListener.sendResult(null); + } catch (RemoteException e) { + Log.w(TAG, "Couldn't synchronize shared elements", e); + } + } + } + + /** @hide */ public void abort() { if (mAnimationStartedListener != null) { try { @@ -405,19 +560,22 @@ public class ActivityOptions { if (otherOptions.mPackageName != null) { mPackageName = otherOptions.mPackageName; } + mSharedElementNames = null; + mLocalElementNames = null; switch (otherOptions.mAnimationType) { case ANIM_CUSTOM: mAnimationType = otherOptions.mAnimationType; mCustomEnterResId = otherOptions.mCustomEnterResId; mCustomExitResId = otherOptions.mCustomExitResId; mThumbnail = null; - if (otherOptions.mAnimationStartedListener != null) { + if (mAnimationStartedListener != null) { try { - otherOptions.mAnimationStartedListener.sendResult(null); + mAnimationStartedListener.sendResult(null); } catch (RemoteException e) { } } mAnimationStartedListener = otherOptions.mAnimationStartedListener; + mTransitionCompleteListener = null; break; case ANIM_SCALE_UP: mAnimationType = otherOptions.mAnimationType; @@ -425,13 +583,14 @@ public class ActivityOptions { mStartY = otherOptions.mStartY; mStartWidth = otherOptions.mStartWidth; mStartHeight = otherOptions.mStartHeight; - if (otherOptions.mAnimationStartedListener != null) { + if (mAnimationStartedListener != null) { try { - otherOptions.mAnimationStartedListener.sendResult(null); + mAnimationStartedListener.sendResult(null); } catch (RemoteException e) { } } mAnimationStartedListener = null; + mTransitionCompleteListener = null; break; case ANIM_THUMBNAIL_SCALE_UP: case ANIM_THUMBNAIL_SCALE_DOWN: @@ -439,13 +598,22 @@ public class ActivityOptions { mThumbnail = otherOptions.mThumbnail; mStartX = otherOptions.mStartX; mStartY = otherOptions.mStartY; - if (otherOptions.mAnimationStartedListener != null) { + if (mAnimationStartedListener != null) { try { - otherOptions.mAnimationStartedListener.sendResult(null); + mAnimationStartedListener.sendResult(null); } catch (RemoteException e) { } } mAnimationStartedListener = otherOptions.mAnimationStartedListener; + mTransitionCompleteListener = null; + break; + case ANIM_SCENE_TRANSITION: + mAnimationType = otherOptions.mAnimationType; + mTransitionCompleteListener = otherOptions.mTransitionCompleteListener; + mThumbnail = null; + mAnimationStartedListener = null; + mSharedElementNames = otherOptions.mSharedElementNames; + mLocalElementNames = otherOptions.mLocalElementNames; break; } } @@ -468,7 +636,7 @@ public class ActivityOptions { b.putInt(KEY_ANIM_TYPE, mAnimationType); b.putInt(KEY_ANIM_ENTER_RES_ID, mCustomEnterResId); b.putInt(KEY_ANIM_EXIT_RES_ID, mCustomExitResId); - b.putIBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener + b.putBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener != null ? mAnimationStartedListener.asBinder() : null); break; case ANIM_SCALE_UP: @@ -484,10 +652,151 @@ public class ActivityOptions { b.putParcelable(KEY_ANIM_THUMBNAIL, mThumbnail); b.putInt(KEY_ANIM_START_X, mStartX); b.putInt(KEY_ANIM_START_Y, mStartY); - b.putIBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener + b.putBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener != null ? mAnimationStartedListener.asBinder() : null); break; + case ANIM_SCENE_TRANSITION: + b.putInt(KEY_ANIM_TYPE, mAnimationType); + if (mTransitionCompleteListener != null) { + b.putBinder(KEY_TRANSITION_COMPLETE_LISTENER, + mTransitionCompleteListener.asBinder()); + } + b.putStringArrayList(KEY_SHARED_ELEMENT_NAMES, mSharedElementNames); + b.putStringArrayList(KEY_LOCAL_ELEMENT_NAMES, mLocalElementNames); + break; } return b; } + + /** + * Return the filtered options only meant to be seen by the target activity itself + * @hide + */ + public ActivityOptions forTargetActivity() { + if (mAnimationType == ANIM_SCENE_TRANSITION) { + final ActivityOptions result = new ActivityOptions(); + result.update(this); + return result; + } + + return null; + } + + /** @hide */ + public void addSharedElements(Map<String, View> sharedElements) { + for (Map.Entry<String, View> entry : sharedElements.entrySet()) { + addSharedElement(entry.getValue(), entry.getKey()); + } + } + + /** @hide */ + public void updateSceneTransitionAnimation(Transition exitTransition, + Transition sharedElementTransition, SharedElementSource sharedElementSource) { + mTransitionCompleteListener = new ExitTransitionListener(exitTransition, + sharedElementTransition, sharedElementSource); + } + + private void addSharedElement(View view, String name) { + String sharedElementName = view.getSharedElementName(); + if (name == null) { + name = sharedElementName; + } + mSharedElementNames.add(name); + mLocalElementNames.add(sharedElementName); + } + + /** @hide */ + public interface SharedElementSource { + Bundle getSharedElementExitState(); + void acceptedSharedElements(ArrayList<String> sharedElementNames); + void hideSharedElements(); + } + + private static class ExitTransitionListener extends IRemoteCallback.Stub + implements Transition.TransitionListener { + private boolean mSharedElementNotified; + private Transition mExitTransition; + private Transition mSharedElementTransition; + private IRemoteCallback mTransitionCompleteCallback; + private boolean mExitComplete; + private boolean mSharedElementComplete; + private SharedElementSource mSharedElementSource; + + public ExitTransitionListener(Transition exitTransition, Transition sharedElementTransition, + SharedElementSource sharedElementSource) { + mSharedElementSource = sharedElementSource; + mExitTransition = exitTransition; + mExitTransition.addListener(this); + mSharedElementTransition = sharedElementTransition; + mSharedElementTransition.addListener(this); + } + + @Override + public void sendResult(Bundle data) throws RemoteException { + if (data != null) { + mTransitionCompleteCallback = IRemoteCallback.Stub.asInterface( + data.getBinder(KEY_TRANSITION_TARGET_LISTENER)); + ArrayList<String> sharedElementNames + = data.getStringArrayList(KEY_SHARED_ELEMENT_NAMES); + mSharedElementSource.acceptedSharedElements(sharedElementNames); + notifySharedElement(); + notifyExit(); + } else { + mSharedElementSource.hideSharedElements(); + } + } + + @Override + public void onTransitionStart(Transition transition) { + } + + @Override + public void onTransitionEnd(Transition transition) { + if (transition == mExitTransition) { + mExitComplete = true; + notifyExit(); + mExitTransition.removeListener(this); + } else { + mSharedElementComplete = true; + notifySharedElement(); + mSharedElementTransition.removeListener(this); + } + } + + @Override + public void onTransitionCancel(Transition transition) { + onTransitionEnd(transition); + } + + @Override + public void onTransitionPause(Transition transition) { + } + + @Override + public void onTransitionResume(Transition transition) { + } + + private void notifySharedElement() { + if (!mSharedElementNotified && mSharedElementComplete + && mTransitionCompleteCallback != null) { + mSharedElementNotified = true; + try { + Bundle sharedElementState = mSharedElementSource.getSharedElementExitState(); + mTransitionCompleteCallback.sendResult(sharedElementState); + } catch (RemoteException e) { + Log.w(TAG, "Couldn't notify that the transition ended", e); + } + } + } + + private void notifyExit() { + if (mExitComplete && mTransitionCompleteCallback != null) { + try { + mTransitionCompleteCallback.sendResult(null); + } catch (RemoteException e) { + Log.w(TAG, "Couldn't notify that the transition ended", e); + } + } + } + } } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 7f8dbba..69ada6a 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -56,6 +56,7 @@ import android.os.DropBoxManager; import android.os.Environment; import android.os.Handler; import android.os.IBinder; +import android.os.IRemoteCallback; import android.os.Looper; import android.os.Message; import android.os.MessageQueue; @@ -68,6 +69,8 @@ import android.os.SystemClock; import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; +import android.transition.Scene; +import android.transition.TransitionManager; import android.provider.Settings; import android.util.AndroidRuntimeException; import android.util.ArrayMap; @@ -75,6 +78,7 @@ import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; import android.util.LogPrinter; +import android.util.Pair; import android.util.PrintWriterPrinter; import android.util.Slog; import android.util.SuperNotCalledException; @@ -289,6 +293,7 @@ public final class ActivityThread { boolean isForward; int pendingConfigChanges; boolean onlyLocalRequest; + Bundle activityOptions; View mPendingRemoveWindow; WindowManager mPendingRemoveWindowManager; @@ -581,9 +586,10 @@ public final class ActivityThread { } public final void scheduleResumeActivity(IBinder token, int processState, - boolean isForward) { + boolean isForward, Bundle resumeArgs) { updateProcessState(processState, false); - sendMessage(H.RESUME_ACTIVITY, token, isForward ? 1 : 0); + sendMessage(H.RESUME_ACTIVITY, new Pair<IBinder, Bundle>(token, resumeArgs), + isForward ? 1 : 0); } public final void scheduleSendResult(IBinder token, List<ResultInfo> results) { @@ -599,7 +605,8 @@ public final class ActivityThread { ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo, int procState, Bundle state, List<ResultInfo> pendingResults, List<Intent> pendingNewIntents, boolean notResumed, boolean isForward, - String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler) { + String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler, + Bundle resumeArgs) { updateProcessState(procState, false); @@ -621,6 +628,7 @@ public final class ActivityThread { r.profileFile = profileName; r.profileFd = profileFd; r.autoStopProfiler = autoStopProfiler; + r.activityOptions = resumeArgs; updatePendingConfiguration(curConfig); @@ -1244,7 +1252,7 @@ public final class ActivityThread { switch (msg.what) { case LAUNCH_ACTIVITY: { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart"); - ActivityClientRecord r = (ActivityClientRecord)msg.obj; + final ActivityClientRecord r = (ActivityClientRecord) msg.obj; r.packageInfo = getPackageInfoNoCheck( r.activityInfo.applicationInfo, r.compatInfo); @@ -1290,7 +1298,8 @@ public final class ActivityThread { break; case RESUME_ACTIVITY: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume"); - handleResumeActivity((IBinder)msg.obj, true, + final Pair<IBinder, Bundle> resumeArgs = (Pair<IBinder, Bundle>) msg.obj; + handleResumeActivity(resumeArgs.first, resumeArgs.second, true, msg.arg1 != 0, true); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; @@ -2076,7 +2085,7 @@ public final class ActivityThread { + ", comp=" + name + ", token=" + token); } - return performLaunchActivity(r, null); + return performLaunchActivity(r, null, null); } public final Activity getActivity(IBinder token) { @@ -2129,7 +2138,8 @@ public final class ActivityThread { sendMessage(H.CLEAN_UP_CONTEXT, cci); } - private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { + private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent, + Bundle options) { // System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")"); ActivityInfo aInfo = r.activityInfo; @@ -2187,7 +2197,7 @@ public final class ActivityThread { + r.activityInfo.name + " with config " + config); activity.attach(appContext, this, getInstrumentation(), r.token, r.ident, app, r.intent, r.activityInfo, title, r.parent, - r.embeddedID, r.lastNonConfigurationInstances, config); + r.embeddedID, r.lastNonConfigurationInstances, config, options); if (customIntent != null) { activity.mIntent = customIntent; @@ -2297,12 +2307,13 @@ public final class ActivityThread { if (localLOGV) Slog.v( TAG, "Handling launch of " + r); - Activity a = performLaunchActivity(r, customIntent); + + Activity a = performLaunchActivity(r, customIntent, r.activityOptions); if (a != null) { r.createdConfig = new Configuration(mConfiguration); Bundle oldState = r.state; - handleResumeActivity(r.token, false, r.isForward, + handleResumeActivity(r.token, r.activityOptions, false, r.isForward, !r.activity.mFinished && !r.startsNotResumed); if (!r.activity.mFinished && r.startsNotResumed) { @@ -2861,12 +2872,13 @@ public final class ActivityThread { r.mPendingRemoveWindowManager = null; } - final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, - boolean reallyResume) { + final void handleResumeActivity(IBinder token, Bundle resumeArgs, + boolean clearHide, boolean isForward, boolean reallyResume) { // If we are getting ready to gc after going to the background, well // we are back active so skip it. unscheduleGcIdler(); + // TODO Push resumeArgs into the activity for consideration ActivityClientRecord r = performResumeActivity(token, clearHide); if (r != null) { @@ -2991,11 +3003,17 @@ public final class ActivityThread { int h; if (w < 0) { Resources res = r.activity.getResources(); - mThumbnailHeight = h = - res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_height); - - mThumbnailWidth = w = - res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_width); + if (SystemProperties.getBoolean("persist.recents.use_alternate", false)) { + int wId = com.android.internal.R.dimen.recents_thumbnail_width; + int hId = com.android.internal.R.dimen.recents_thumbnail_height; + mThumbnailWidth = w = res.getDimensionPixelSize(wId); + mThumbnailHeight = h = res.getDimensionPixelSize(hId); + } else { + mThumbnailHeight = h = + res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_height); + mThumbnailWidth = w = + res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_width); + } } else { h = mThumbnailHeight; } @@ -3787,6 +3805,7 @@ public final class ActivityThread { } } r.startsNotResumed = tmp.startsNotResumed; + r.activityOptions = null; handleLaunchActivity(r, currentIntent); } @@ -3964,6 +3983,7 @@ public final class ActivityThread { ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(false, config); // Cleanup hardware accelerated stuff + // TODO: Do we actually want to do this in response to all config changes? WindowManagerGlobal.getInstance().trimLocalMemory(); freeTextLayoutCachesIfNeeded(configDiff); diff --git a/core/java/android/app/AlertDialog.java b/core/java/android/app/AlertDialog.java index 10d5e25..ab148a9 100644 --- a/core/java/android/app/AlertDialog.java +++ b/core/java/android/app/AlertDialog.java @@ -27,7 +27,6 @@ import android.os.Message; import android.util.TypedValue; import android.view.ContextThemeWrapper; import android.view.KeyEvent; -import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; import android.widget.AdapterView; @@ -147,10 +146,10 @@ public class AlertDialog extends Dialog implements DialogInterface { } /** - * Gets one of the buttons used in the dialog. - * <p> - * If a button does not exist in the dialog, null will be returned. - * + * Gets one of the buttons used in the dialog. Returns null if the specified + * button does not exist or the dialog has not yet been fully created (for + * example, via {@link #show()} or {@link #create()}). + * * @param whichButton The identifier of the button that should be returned. * For example, this can be * {@link DialogInterface#BUTTON_POSITIVE}. @@ -159,7 +158,7 @@ public class AlertDialog extends Dialog implements DialogInterface { public Button getButton(int whichButton) { return mAlert.getButton(whichButton); } - + /** * Gets the list view used in the dialog. * @@ -853,6 +852,21 @@ public class AlertDialog extends Dialog implements DialogInterface { } /** + * Set a custom view resource to be the contents of the Dialog. The + * resource will be inflated, adding all top-level views to the screen. + * + * @param layoutResId Resource ID to be inflated. + * @return This Builder object to allow for chaining of calls to set + * methods + */ + public Builder setView(int layoutResId) { + P.mView = null; + P.mViewLayoutResId = layoutResId; + P.mViewSpacingSpecified = false; + return this; + } + + /** * Set a custom view to be the contents of the Dialog. If the supplied view is an instance * of a {@link ListView} the light background will be used. * @@ -862,6 +876,7 @@ public class AlertDialog extends Dialog implements DialogInterface { */ public Builder setView(View view) { P.mView = view; + P.mViewLayoutResId = 0; P.mViewSpacingSpecified = false; return this; } @@ -891,6 +906,7 @@ public class AlertDialog extends Dialog implements DialogInterface { public Builder setView(View view, int viewSpacingLeft, int viewSpacingTop, int viewSpacingRight, int viewSpacingBottom) { P.mView = view; + P.mViewLayoutResId = 0; P.mViewSpacingSpecified = true; P.mViewSpacingLeft = viewSpacingLeft; P.mViewSpacingTop = viewSpacingTop; diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index aece462..e71d47d 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -36,7 +36,7 @@ import android.os.RemoteException; * API for interacting with "application operation" tracking. * * <p>This API is not generally intended for third party application developers; most - * features are only available to system applicatins. Obtain an instance of it through + * features are only available to system applications. Obtain an instance of it through * {@link Context#getSystemService(String) Context.getSystemService} with * {@link Context#APP_OPS_SERVICE Context.APP_OPS_SERVICE}.</p> */ diff --git a/core/java/android/app/ApplicationErrorReport.java b/core/java/android/app/ApplicationErrorReport.java index c117486..8b132e0 100644 --- a/core/java/android/app/ApplicationErrorReport.java +++ b/core/java/android/app/ApplicationErrorReport.java @@ -24,7 +24,6 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.Parcel; import android.os.Parcelable; -import android.os.SystemClock; import android.os.SystemProperties; import android.provider.Settings; import android.util.Printer; diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 4ddabd9..8165fa1 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -57,8 +57,6 @@ import android.view.Display; import java.lang.ref.WeakReference; import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; import java.util.List; /*package*/ @@ -1094,7 +1092,7 @@ final class ApplicationPackageManager extends PackageManager { } @Override - public void installPackageWithVerificationAndEncryption(Uri packageURI, + public void installPackageWithVerificationAndEncryption(Uri packageURI, IPackageInstallObserver observer, int flags, String installerPackageName, VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) { try { diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java index cb453e2..f1c632e 100644 --- a/core/java/android/app/ApplicationThreadNative.java +++ b/core/java/android/app/ApplicationThreadNative.java @@ -113,7 +113,8 @@ public abstract class ApplicationThreadNative extends Binder IBinder b = data.readStrongBinder(); int procState = data.readInt(); boolean isForward = data.readInt() != 0; - scheduleResumeActivity(b, procState, isForward); + Bundle resumeArgs = data.readBundle(); + scheduleResumeActivity(b, procState, isForward, resumeArgs); return true; } @@ -145,8 +146,10 @@ public abstract class ApplicationThreadNative extends Binder ParcelFileDescriptor profileFd = data.readInt() != 0 ? ParcelFileDescriptor.CREATOR.createFromParcel(data) : null; boolean autoStopProfiler = data.readInt() != 0; + Bundle resumeArgs = data.readBundle(); scheduleLaunchActivity(intent, b, ident, info, curConfig, compatInfo, procState, state, - ri, pi, notResumed, isForward, profileName, profileFd, autoStopProfiler); + ri, pi, notResumed, isForward, profileName, profileFd, autoStopProfiler, + resumeArgs); return true; } @@ -705,20 +708,22 @@ class ApplicationThreadProxy implements IApplicationThread { data.recycle(); } - public final void scheduleResumeActivity(IBinder token, int procState, boolean isForward) + public final void scheduleResumeActivity(IBinder token, int procState, boolean isForward, + Bundle resumeArgs) throws RemoteException { Parcel data = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); data.writeStrongBinder(token); data.writeInt(procState); data.writeInt(isForward ? 1 : 0); + data.writeBundle(resumeArgs); mRemote.transact(SCHEDULE_RESUME_ACTIVITY_TRANSACTION, data, null, IBinder.FLAG_ONEWAY); data.recycle(); } public final void scheduleSendResult(IBinder token, List<ResultInfo> results) - throws RemoteException { + throws RemoteException { Parcel data = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); data.writeStrongBinder(token); @@ -731,9 +736,10 @@ class ApplicationThreadProxy implements IApplicationThread { public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident, ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo, int procState, Bundle state, List<ResultInfo> pendingResults, - List<Intent> pendingNewIntents, boolean notResumed, boolean isForward, - String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler) - throws RemoteException { + List<Intent> pendingNewIntents, boolean notResumed, boolean isForward, + String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler, + Bundle resumeArgs) + throws RemoteException { Parcel data = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); intent.writeToParcel(data, 0); @@ -756,6 +762,7 @@ class ApplicationThreadProxy implements IApplicationThread { data.writeInt(0); } data.writeInt(autoStopProfiler ? 1 : 0); + data.writeBundle(resumeArgs); mRemote.transact(SCHEDULE_LAUNCH_ACTIVITY_TRANSACTION, data, null, IBinder.FLAG_ONEWAY); data.recycle(); @@ -886,7 +893,7 @@ class ApplicationThreadProxy implements IApplicationThread { } public final void scheduleServiceArgs(IBinder token, boolean taskRemoved, int startId, - int flags, Intent args) throws RemoteException { + int flags, Intent args) throws RemoteException { Parcel data = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); data.writeStrongBinder(token); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index df50989..0351292 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -17,6 +17,7 @@ package android.app; import android.os.Build; + import com.android.internal.policy.PolicyManager; import com.android.internal.util.Preconditions; @@ -62,6 +63,7 @@ import android.location.ILocationManager; import android.location.LocationManager; import android.media.AudioManager; import android.media.MediaRouter; +import android.media.session.MediaSessionManager; import android.net.ConnectivityManager; import android.net.IConnectivityManager; import android.net.INetworkPolicyManager; @@ -595,6 +597,12 @@ class ContextImpl extends Context { public Object createService(ContextImpl ctx) { return new ConsumerIrManager(ctx); }}); + + registerService(MEDIA_SESSION_SERVICE, new ServiceFetcher() { + public Object createService(ContextImpl ctx) { + return new MediaSessionManager(ctx); + } + }); } static ContextImpl getImpl(Context context) { diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java index a8277b5..fb96d8d 100644 --- a/core/java/android/app/Dialog.java +++ b/core/java/android/app/Dialog.java @@ -17,7 +17,6 @@ package android.app; import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; import com.android.internal.app.ActionBarImpl; import com.android.internal.policy.PolicyManager; @@ -240,6 +239,18 @@ public class Dialog implements DialogInterface, Window.Callback, } /** + * Forces immediate creation of the dialog. + * <p> + * Note that you should not override this method to perform dialog creation. + * Rather, override {@link #onCreate(Bundle)}. + */ + public void create() { + if (!mCreated) { + dispatchOnCreate(null); + } + } + + /** * Start the dialog and display it on screen. The window is placed in the * application layer and opaque. Note that you should not override this * method to do initialization when the dialog is shown, instead implement @@ -457,11 +468,12 @@ public class Dialog implements DialogInterface, Window.Callback, } /** - * Finds a view that was identified by the id attribute from the XML that - * was processed in {@link #onStart}. + * Finds a child view with the given identifier. Returns null if the + * specified child view does not exist or the dialog has not yet been fully + * created (for example, via {@link #show()} or {@link #create()}). * * @param id the identifier of the view to find - * @return The view if found or null otherwise. + * @return The view with the given id or null. */ public View findViewById(int id) { return mWindow.findViewById(id); @@ -480,7 +492,7 @@ public class Dialog implements DialogInterface, Window.Callback, /** * Set the screen content to an explicit view. This view is placed * directly into the screen's view hierarchy. It can itself be a complex - * view hierarhcy. + * view hierarchy. * * @param view The desired content to display. */ diff --git a/core/java/android/app/ExpandableListActivity.java b/core/java/android/app/ExpandableListActivity.java index 9651078..e08f25a 100644 --- a/core/java/android/app/ExpandableListActivity.java +++ b/core/java/android/app/ExpandableListActivity.java @@ -27,7 +27,6 @@ import android.widget.ExpandableListAdapter; import android.widget.ExpandableListView; import android.widget.SimpleCursorTreeAdapter; import android.widget.SimpleExpandableListAdapter; -import android.widget.AdapterView.AdapterContextMenuInfo; import java.util.Map; diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java index af8f177..6c0d379 100644 --- a/core/java/android/app/Fragment.java +++ b/core/java/android/app/Fragment.java @@ -17,6 +17,7 @@ package android.app; import android.animation.Animator; +import android.annotation.Nullable; import android.content.ComponentCallbacks2; import android.content.Context; import android.content.Intent; @@ -575,7 +576,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene * the given fragment class. This is a runtime exception; it is not * normally expected to happen. */ - public static Fragment instantiate(Context context, String fname, Bundle args) { + public static Fragment instantiate(Context context, String fname, @Nullable Bundle args) { try { Class<?> clazz = sClassMap.get(fname); if (clazz == null) { @@ -1213,7 +1214,8 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene * * @return Return the View for the fragment's UI, or null. */ - public View onCreateView(LayoutInflater inflater, ViewGroup container, + @Nullable + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { return null; } @@ -1228,7 +1230,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene * @param savedInstanceState If non-null, this fragment is being re-constructed * from a previous saved state as given here. */ - public void onViewCreated(View view, Bundle savedInstanceState) { + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { } /** @@ -1237,6 +1239,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene * * @return The fragment's root view, or null if it has no layout. */ + @Nullable public View getView() { return mView; } diff --git a/core/java/android/app/FragmentBreadCrumbs.java b/core/java/android/app/FragmentBreadCrumbs.java index b810b89..e4de7af 100644 --- a/core/java/android/app/FragmentBreadCrumbs.java +++ b/core/java/android/app/FragmentBreadCrumbs.java @@ -81,14 +81,19 @@ public class FragmentBreadCrumbs extends ViewGroup } public FragmentBreadCrumbs(Context context, AttributeSet attrs) { - this(context, attrs, android.R.style.Widget_FragmentBreadCrumbs); + this(context, attrs, com.android.internal.R.attr.fragmentBreadCrumbsStyle); } - public FragmentBreadCrumbs(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public FragmentBreadCrumbs(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public FragmentBreadCrumbs( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); - TypedArray a = context.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.FragmentBreadCrumbs, defStyle, 0); + final TypedArray a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.FragmentBreadCrumbs, defStyleAttr, defStyleRes); mGravity = a.getInt(com.android.internal.R.styleable.FragmentBreadCrumbs_gravity, DEFAULT_GRAVITY); diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 02a6343..cb06a42 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -122,6 +122,7 @@ public interface IActivityManager extends IInterface { public void resizeStack(int stackId, Rect bounds) throws RemoteException; public List<StackInfo> getAllStackInfos() throws RemoteException; public StackInfo getStackInfo(int stackId) throws RemoteException; + public boolean isInHomeStack(int taskId) throws RemoteException; public void setFocusedStack(int stackId) throws RemoteException; public int getTaskForActivity(IBinder token, boolean onlyRoot) throws RemoteException; /* oneway */ @@ -243,7 +244,8 @@ public interface IActivityManager extends IInterface { public void enterSafeMode() throws RemoteException; - public void noteWakeupAlarm(IIntentSender sender) throws RemoteException; + public void noteWakeupAlarm(IIntentSender sender, int sourceUid, String sourcePkg) + throws RemoteException; public boolean killPids(int[] pids, String reason, boolean secure) throws RemoteException; public boolean killProcessesBelowForeground(String reason) throws RemoteException; @@ -348,6 +350,7 @@ public interface IActivityManager extends IInterface { // Multi-user APIs public boolean switchUser(int userid) throws RemoteException; + public boolean startUserInBackground(int userid) throws RemoteException; public int stopUser(int userid, IStopUserCallback callback) throws RemoteException; public UserInfo getCurrentUser() throws RemoteException; public boolean isUserRunning(int userid, boolean orStopping) throws RemoteException; @@ -366,6 +369,8 @@ public interface IActivityManager extends IInterface { public Intent getIntentForIntentSender(IIntentSender sender) throws RemoteException; + public String getTagForIntentSender(IIntentSender sender, String prefix) throws RemoteException; + public void updatePersistentConfiguration(Configuration values) throws RemoteException; public long[] getProcessPss(int[] pids) throws RemoteException; @@ -419,6 +424,18 @@ public interface IActivityManager extends IInterface { public IBinder getHomeActivityToken() throws RemoteException; + /** @hide */ + public void startLockTaskMode(int taskId) throws RemoteException; + + /** @hide */ + public void startLockTaskMode(IBinder token) throws RemoteException; + + /** @hide */ + public void stopLockTaskMode() throws RemoteException; + + /** @hide */ + public boolean isInLockTaskMode() throws RemoteException; + /* * Private non-Binder interfaces */ @@ -708,4 +725,14 @@ public interface IActivityManager extends IInterface { int GET_HOME_ACTIVITY_TOKEN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+183; int GET_ACTIVITY_CONTAINER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+184; int DELETE_ACTIVITY_CONTAINER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+185; + + + // Start of L transactions + int GET_TAG_FOR_INTENT_SENDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+210; + int START_USER_IN_BACKGROUND_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+211; + int IS_IN_HOME_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+212; + int START_LOCK_TASK_BY_TASK_ID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+213; + int START_LOCK_TASK_BY_TOKEN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+214; + int STOP_LOCK_TASK_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+215; + int IS_IN_LOCK_TASK_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+216; } diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java index 3aceff9..ac8ac8f 100644 --- a/core/java/android/app/IApplicationThread.java +++ b/core/java/android/app/IApplicationThread.java @@ -50,15 +50,16 @@ public interface IApplicationThread extends IInterface { int configChanges) throws RemoteException; void scheduleWindowVisibility(IBinder token, boolean showWindow) throws RemoteException; void scheduleSleeping(IBinder token, boolean sleeping) throws RemoteException; - void scheduleResumeActivity(IBinder token, int procState, boolean isForward) + void scheduleResumeActivity(IBinder token, int procState, boolean isForward, Bundle resumeArgs) throws RemoteException; void scheduleSendResult(IBinder token, List<ResultInfo> results) throws RemoteException; void scheduleLaunchActivity(Intent intent, IBinder token, int ident, ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo, int procState, Bundle state, List<ResultInfo> pendingResults, - List<Intent> pendingNewIntents, boolean notResumed, boolean isForward, - String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler) - throws RemoteException; + List<Intent> pendingNewIntents, boolean notResumed, boolean isForward, + String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler, + Bundle resumeArgs) + throws RemoteException; void scheduleRelaunchActivity(IBinder token, List<ResultInfo> pendingResults, List<Intent> pendingNewIntents, int configChanges, boolean notResumed, Configuration config) throws RemoteException; diff --git a/core/java/android/app/MediaRouteButton.java b/core/java/android/app/MediaRouteButton.java index a7982f4..fa2813e 100644 --- a/core/java/android/app/MediaRouteButton.java +++ b/core/java/android/app/MediaRouteButton.java @@ -73,13 +73,18 @@ public class MediaRouteButton extends View { } public MediaRouteButton(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); + this(context, attrs, defStyleAttr, 0); + } + + public MediaRouteButton( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); mRouter = (MediaRouter)context.getSystemService(Context.MEDIA_ROUTER_SERVICE); mCallback = new MediaRouterCallback(); - TypedArray a = context.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.MediaRouteButton, defStyleAttr, 0); + final TypedArray a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.MediaRouteButton, defStyleAttr, defStyleRes); setRemoteIndicatorDrawable(a.getDrawable( com.android.internal.R.styleable.MediaRouteButton_externalRouteEnabledDrawable)); mMinWidth = a.getDimensionPixelSize( diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 6be2b7b..12a8ff6 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -18,6 +18,7 @@ package android.app; import com.android.internal.R; +import android.annotation.IntDef; import android.content.Context; import android.content.Intent; import android.content.res.Resources; @@ -37,6 +38,8 @@ import android.view.View; import android.widget.ProgressBar; import android.widget.RemoteViews; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.text.NumberFormat; import java.util.ArrayList; @@ -201,6 +204,15 @@ public class Notification implements Parcelable */ public RemoteViews bigContentView; + + /** + * @hide + * A medium-format version of {@link #contentView}, giving the Notification an + * opportunity to add action buttons to contentView. The system UI may + * choose to show this as a popup notification at its discretion. + */ + public RemoteViews headsUpContentView; + /** * The bitmap that may escape the bounds of the panel and bar. */ @@ -357,6 +369,11 @@ public class Notification implements Parcelable public int flags; + /** @hide */ + @IntDef({PRIORITY_DEFAULT,PRIORITY_LOW,PRIORITY_MIN,PRIORITY_HIGH,PRIORITY_MAX}) + @Retention(RetentionPolicy.SOURCE) + public @interface Priority {} + /** * Default notification {@link #priority}. If your application does not prioritize its own * notifications, use this value for all notifications. @@ -398,8 +415,34 @@ public class Notification implements Parcelable * system will make a determination about how to interpret this priority when presenting * the notification. */ + @Priority public int priority; + + /** + * Sphere of visibility of this notification, which affects how and when the SystemUI reveals + * the notification's presence and contents in untrusted situations (namely, on the secure + * lockscreen). + * + * The default level, {@link #VISIBILITY_PRIVATE}, behaves exactly as notifications have always + * done on Android: The notification's {@link #icon} and {@link #tickerText} (if available) are + * shown in all situations, but the contents are only available if the device is unlocked for + * the appropriate user. + * + * A more permissive policy can be expressed by {@link #VISIBILITY_PUBLIC}; such a notification + * can be read even in an "insecure" context (that is, above a secure lockscreen). + * To modify the public version of this notification—for example, to redact some portions—see + * {@link Builder#setPublicVersion(Notification)}. + * + * Finally, a notification can be made {@link #VISIBILITY_SECRET}, which will suppress its icon + * and ticker until the user has bypassed the lockscreen. + */ + public int visibility; + + public static final int VISIBILITY_PUBLIC = 1; + public static final int VISIBILITY_PRIVATE = 0; + public static final int VISIBILITY_SECRET = -1; + /** * @hide * Notification type: incoming call (voice or video) or similar synchronous communication request. @@ -547,6 +590,7 @@ public class Notification implements Parcelable * notifications, each of which was supplied to {@link InboxStyle#addLine(CharSequence)}. */ public static final String EXTRA_TEXT_LINES = "android.textLines"; + public static final String EXTRA_TEMPLATE = "android.template"; /** * {@link #extras} key: An array of people that this notification relates to, specified @@ -568,6 +612,13 @@ public class Notification implements Parcelable public static final String EXTRA_AS_HEADS_UP = "headsup"; /** + * Extra added from {@link Notification.Builder} to indicate that the remote views were inflated + * from the builder, as opposed to being created directly from the application. + * @hide + */ + public static final String EXTRA_BUILDER_REMOTE_VIEWS = "android.builderRemoteViews"; + + /** * Value for {@link #EXTRA_AS_HEADS_UP}. * @hide */ @@ -668,6 +719,13 @@ public class Notification implements Parcelable public Action[] actions; /** + * Replacement version of this notification whose content will be shown + * in an insecure context such as atop a secure keyguard. See {@link #visibility} + * and {@link #VISIBILITY_PUBLIC}. + */ + public Notification publicVersion; + + /** * Constructs a Notification object with default values. * You might want to consider using {@link Builder} instead. */ @@ -766,6 +824,16 @@ public class Notification implements Parcelable if (parcel.readInt() != 0) { bigContentView = RemoteViews.CREATOR.createFromParcel(parcel); } + + if (parcel.readInt() != 0) { + headsUpContentView = RemoteViews.CREATOR.createFromParcel(parcel); + } + + visibility = parcel.readInt(); + + if (parcel.readInt() != 0) { + publicVersion = Notification.CREATOR.createFromParcel(parcel); + } } @Override @@ -851,6 +919,17 @@ public class Notification implements Parcelable that.bigContentView = this.bigContentView.clone(); } + if (heavy && this.headsUpContentView != null) { + that.headsUpContentView = this.headsUpContentView.clone(); + } + + that.visibility = this.visibility; + + if (this.publicVersion != null) { + that.publicVersion = new Notification(); + this.publicVersion.cloneInto(that.publicVersion, heavy); + } + if (!heavy) { that.lightenPayload(); // will clean out extras } @@ -865,6 +944,7 @@ public class Notification implements Parcelable tickerView = null; contentView = null; bigContentView = null; + headsUpContentView = null; largeIcon = null; if (extras != null) { extras.remove(Notification.EXTRA_LARGE_ICON); @@ -976,6 +1056,22 @@ public class Notification implements Parcelable } else { parcel.writeInt(0); } + + if (headsUpContentView != null) { + parcel.writeInt(1); + headsUpContentView.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(visibility); + + if (publicVersion != null) { + parcel.writeInt(1); + publicVersion.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } } /** @@ -1118,6 +1214,9 @@ public class Notification implements Parcelable if (bigContentView != null) { bigContentView.setUser(user); } + if (headsUpContentView != null) { + headsUpContentView.setUser(user); + } } /** @@ -1179,6 +1278,9 @@ public class Notification implements Parcelable private boolean mUseChronometer; private Style mStyle; private boolean mShowWhen = true; + private int mVisibility = VISIBILITY_PRIVATE; + private Notification mPublicVersion = null; + private boolean mQuantumTheme; /** * Constructs a new Builder with the defaults: @@ -1206,6 +1308,9 @@ public class Notification implements Parcelable mWhen = System.currentTimeMillis(); mAudioStreamType = STREAM_DEFAULT; mPriority = PRIORITY_DEFAULT; + + // TODO: Decide on targetSdk from calling app whether to use quantum theme. + mQuantumTheme = true; } /** @@ -1568,7 +1673,7 @@ public class Notification implements Parcelable * * @see Notification#priority */ - public Builder setPriority(int pri) { + public Builder setPriority(@Priority int pri) { mPriority = pri; return this; } @@ -1672,6 +1777,30 @@ public class Notification implements Parcelable return this; } + /** + * Specify the value of {@link #visibility}. + + * @param visibility One of {@link #VISIBILITY_PRIVATE} (the default), + * {@link #VISIBILITY_SECRET}, or {@link #VISIBILITY_PUBLIC}. + * + * @return The same Builder. + */ + public Builder setVisibility(int visibility) { + mVisibility = visibility; + return this; + } + + /** + * Supply a replacement Notification whose contents should be shown in insecure contexts + * (i.e. atop the secure lockscreen). See {@link #visibility} and {@link #VISIBILITY_PUBLIC}. + * @param n A replacement notification, presumably with some or all info redacted. + * @return The same Builder. + */ + public Builder setPublicVersion(Notification n) { + mPublicVersion = n; + return this; + } + private void setFlag(int mask, boolean value) { if (value) { mFlags |= mask; @@ -1689,7 +1818,7 @@ public class Notification implements Parcelable contentView.setImageViewBitmap(R.id.icon, mLargeIcon); smallIconImageViewId = R.id.right_icon; } - if (mPriority < PRIORITY_LOW) { + if (!mQuantumTheme && mPriority < PRIORITY_LOW) { contentView.setInt(R.id.icon, "setBackgroundResource", R.drawable.notification_template_icon_low_bg); contentView.setInt(R.id.status_bar_latest_event_content, @@ -1803,7 +1932,7 @@ public class Notification implements Parcelable if (mContentView != null) { return mContentView; } else { - return applyStandardTemplate(R.layout.notification_template_base, true); // no more special large_icon flavor + return applyStandardTemplate(getBaseLayoutResource(), true); // no more special large_icon flavor } } @@ -1824,14 +1953,21 @@ public class Notification implements Parcelable private RemoteViews makeBigContentView() { if (mActions.size() == 0) return null; - return applyStandardTemplateWithActions(R.layout.notification_template_big_base); + return applyStandardTemplateWithActions(getBigBaseLayoutResource()); + } + + private RemoteViews makeHeadsUpContentView() { + if (mActions.size() == 0) return null; + + return applyStandardTemplateWithActions(getBigBaseLayoutResource()); } + private RemoteViews generateActionButton(Action action) { final boolean tombstone = (action.actionIntent == null); RemoteViews button = new RemoteViews(mContext.getPackageName(), - tombstone ? R.layout.notification_action_tombstone - : R.layout.notification_action); + tombstone ? getActionTombstoneLayoutResource() + : getActionLayoutResource()); button.setTextViewCompoundDrawablesRelative(R.id.action0, action.icon, 0, 0, 0); button.setTextViewText(R.id.action0, action.title); if (!tombstone) { @@ -1867,6 +2003,7 @@ public class Notification implements Parcelable n.defaults = mDefaults; n.flags = mFlags; n.bigContentView = makeBigContentView(); + n.headsUpContentView = makeHeadsUpContentView(); if (mLedOnMs != 0 || mLedOffMs != 0) { n.flags |= FLAG_SHOW_LIGHTS; } @@ -1884,6 +2021,12 @@ public class Notification implements Parcelable n.actions = new Action[mActions.size()]; mActions.toArray(n.actions); } + n.visibility = mVisibility; + + if (mPublicVersion != null) { + n.publicVersion = new Notification(); + mPublicVersion.cloneInto(n.publicVersion, true); + } return n; } @@ -1905,6 +2048,7 @@ public class Notification implements Parcelable extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, mProgressIndeterminate); extras.putBoolean(EXTRA_SHOW_CHRONOMETER, mUseChronometer); extras.putBoolean(EXTRA_SHOW_WHEN, mShowWhen); + extras.putBoolean(EXTRA_BUILDER_REMOTE_VIEWS, mContentView == null); if (mLargeIcon != null) { extras.putParcelable(EXTRA_LARGE_ICON, mLargeIcon); } @@ -1948,6 +2092,49 @@ public class Notification implements Parcelable build().cloneInto(n, true); return n; } + + + private int getBaseLayoutResource() { + return mQuantumTheme + ? R.layout.notification_template_quantum_base + : R.layout.notification_template_base; + } + + private int getBigBaseLayoutResource() { + return mQuantumTheme + ? R.layout.notification_template_quantum_big_base + : R.layout.notification_template_big_base; + } + + private int getBigPictureLayoutResource() { + return mQuantumTheme + ? R.layout.notification_template_quantum_big_picture + : R.layout.notification_template_big_picture; + } + + private int getBigTextLayoutResource() { + return mQuantumTheme + ? R.layout.notification_template_quantum_big_text + : R.layout.notification_template_big_text; + } + + private int getInboxLayoutResource() { + return mQuantumTheme + ? R.layout.notification_template_quantum_inbox + : R.layout.notification_template_inbox; + } + + private int getActionLayoutResource() { + return mQuantumTheme + ? R.layout.notification_quantum_action + : R.layout.notification_action; + } + + private int getActionTombstoneLayoutResource() { + return mQuantumTheme + ? R.layout.notification_quantum_action_tombstone + : R.layout.notification_action_tombstone; + } } /** @@ -2033,6 +2220,7 @@ public class Notification implements Parcelable if (mBigContentTitle != null) { extras.putCharSequence(EXTRA_TITLE_BIG, mBigContentTitle); } + extras.putString(EXTRA_TEMPLATE, this.getClass().getName()); } /** @@ -2116,7 +2304,7 @@ public class Notification implements Parcelable } private RemoteViews makeBigContentView() { - RemoteViews contentView = getStandardView(R.layout.notification_template_big_picture); + RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource()); contentView.setImageViewBitmap(R.id.big_picture, mPicture); @@ -2215,7 +2403,7 @@ public class Notification implements Parcelable final boolean hadThreeLines = (mBuilder.mContentText != null && mBuilder.mSubText != null); mBuilder.mContentText = null; - RemoteViews contentView = getStandardView(R.layout.notification_template_big_text); + RemoteViews contentView = getStandardView(mBuilder.getBigTextLayoutResource()); if (hadThreeLines) { // vertical centering @@ -2309,7 +2497,7 @@ public class Notification implements Parcelable private RemoteViews makeBigContentView() { // Remove the content text so line3 disappears unless you have a summary mBuilder.mContentText = null; - RemoteViews contentView = getStandardView(R.layout.notification_template_inbox); + RemoteViews contentView = getStandardView(mBuilder.getInboxLayoutResource()); contentView.setViewVisibility(R.id.text2, View.GONE); diff --git a/core/java/android/app/OnActivityPausedListener.java b/core/java/android/app/OnActivityPausedListener.java index 379f133..5003973 100644 --- a/core/java/android/app/OnActivityPausedListener.java +++ b/core/java/android/app/OnActivityPausedListener.java @@ -5,7 +5,7 @@ * 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 + * 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, diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index 45467b8..8efc14f 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -16,6 +16,9 @@ package android.app; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.content.Intent; import android.content.IIntentReceiver; @@ -30,6 +33,9 @@ import android.os.Parcelable; import android.os.UserHandle; import android.util.AndroidException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * A description of an Intent and target action to perform with it. Instances * of this class are created with {@link #getActivity}, {@link #getActivities}, @@ -86,6 +92,26 @@ import android.util.AndroidException; public final class PendingIntent implements Parcelable { private final IIntentSender mTarget; + /** @hide */ + @IntDef(flag = true, + value = { + FLAG_ONE_SHOT, + FLAG_NO_CREATE, + FLAG_CANCEL_CURRENT, + FLAG_UPDATE_CURRENT, + + Intent.FILL_IN_ACTION, + Intent.FILL_IN_DATA, + Intent.FILL_IN_CATEGORIES, + Intent.FILL_IN_COMPONENT, + Intent.FILL_IN_PACKAGE, + Intent.FILL_IN_SOURCE_BOUNDS, + Intent.FILL_IN_SELECTOR, + Intent.FILL_IN_CLIP_DATA + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Flags {} + /** * Flag indicating that this PendingIntent can be used only once. * For use with {@link #getActivity}, {@link #getBroadcast}, and @@ -222,7 +248,7 @@ public final class PendingIntent implements Parcelable { * supplied. */ public static PendingIntent getActivity(Context context, int requestCode, - Intent intent, int flags) { + Intent intent, @Flags int flags) { return getActivity(context, requestCode, intent, flags, null); } @@ -255,7 +281,7 @@ public final class PendingIntent implements Parcelable { * supplied. */ public static PendingIntent getActivity(Context context, int requestCode, - Intent intent, int flags, Bundle options) { + @NonNull Intent intent, @Flags int flags, @Nullable Bundle options) { String packageName = context.getPackageName(); String resolvedType = intent != null ? intent.resolveTypeIfNeeded( context.getContentResolver()) : null; @@ -280,7 +306,7 @@ public final class PendingIntent implements Parcelable { * activity is started, not when the pending intent is created. */ public static PendingIntent getActivityAsUser(Context context, int requestCode, - Intent intent, int flags, Bundle options, UserHandle user) { + @NonNull Intent intent, int flags, Bundle options, UserHandle user) { String packageName = context.getPackageName(); String resolvedType = intent != null ? intent.resolveTypeIfNeeded( context.getContentResolver()) : null; @@ -345,7 +371,7 @@ public final class PendingIntent implements Parcelable { * supplied. */ public static PendingIntent getActivities(Context context, int requestCode, - Intent[] intents, int flags) { + @NonNull Intent[] intents, @Flags int flags) { return getActivities(context, requestCode, intents, flags, null); } @@ -395,7 +421,7 @@ public final class PendingIntent implements Parcelable { * supplied. */ public static PendingIntent getActivities(Context context, int requestCode, - Intent[] intents, int flags, Bundle options) { + @NonNull Intent[] intents, @Flags int flags, @Nullable Bundle options) { String packageName = context.getPackageName(); String[] resolvedTypes = new String[intents.length]; for (int i=0; i<intents.length; i++) { @@ -421,7 +447,7 @@ public final class PendingIntent implements Parcelable { * activity is started, not when the pending intent is created. */ public static PendingIntent getActivitiesAsUser(Context context, int requestCode, - Intent[] intents, int flags, Bundle options, UserHandle user) { + @NonNull Intent[] intents, int flags, Bundle options, UserHandle user) { String packageName = context.getPackageName(); String[] resolvedTypes = new String[intents.length]; for (int i=0; i<intents.length; i++) { @@ -465,7 +491,7 @@ public final class PendingIntent implements Parcelable { * supplied. */ public static PendingIntent getBroadcast(Context context, int requestCode, - Intent intent, int flags) { + Intent intent, @Flags int flags) { return getBroadcastAsUser(context, requestCode, intent, flags, new UserHandle(UserHandle.myUserId())); } @@ -519,7 +545,7 @@ public final class PendingIntent implements Parcelable { * supplied. */ public static PendingIntent getService(Context context, int requestCode, - Intent intent, int flags) { + @NonNull Intent intent, @Flags int flags) { String packageName = context.getPackageName(); String resolvedType = intent != null ? intent.resolveTypeIfNeeded( context.getContentResolver()) : null; @@ -749,6 +775,7 @@ public final class PendingIntent implements Parcelable { * @return The package name of the PendingIntent, or null if there is * none associated with it. */ + @Nullable public String getCreatorPackage() { try { return ActivityManagerNative.getDefault() @@ -807,6 +834,7 @@ public final class PendingIntent implements Parcelable { * @return The user handle of the PendingIntent, or null if there is * none associated with it. */ + @Nullable public UserHandle getCreatorUserHandle() { try { int uid = ActivityManagerNative.getDefault() @@ -861,6 +889,20 @@ public final class PendingIntent implements Parcelable { } /** + * @hide + * Return descriptive tag for this PendingIntent. + */ + public String getTag(String prefix) { + try { + return ActivityManagerNative.getDefault() + .getTagForIntentSender(mTarget, prefix); + } catch (RemoteException e) { + // Should never happen. + return null; + } + } + + /** * Comparison operator on two PendingIntent objects, such that true * is returned then they both represent the same operation from the * same package. This allows you to use {@link #getActivity}, @@ -922,8 +964,8 @@ public final class PendingIntent implements Parcelable { * @param sender The PendingIntent to write, or null. * @param out Where to write the PendingIntent. */ - public static void writePendingIntentOrNullToParcel(PendingIntent sender, - Parcel out) { + public static void writePendingIntentOrNullToParcel(@Nullable PendingIntent sender, + @NonNull Parcel out) { out.writeStrongBinder(sender != null ? sender.mTarget.asBinder() : null); } @@ -938,7 +980,8 @@ public final class PendingIntent implements Parcelable { * @return Returns the Messenger read from the Parcel, or null if null had * been written. */ - public static PendingIntent readPendingIntentOrNullFromParcel(Parcel in) { + @Nullable + public static PendingIntent readPendingIntentOrNullFromParcel(@NonNull Parcel in) { IBinder b = in.readStrongBinder(); return b != null ? new PendingIntent(b) : null; } diff --git a/core/java/android/app/ResultInfo.java b/core/java/android/app/ResultInfo.java index 48a0fc2..5e0867c 100644 --- a/core/java/android/app/ResultInfo.java +++ b/core/java/android/app/ResultInfo.java @@ -17,12 +17,8 @@ package android.app; import android.content.Intent; -import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; -import android.os.Bundle; - -import java.util.Map; /** * {@hide} diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java index d04e9db..af1810b 100644 --- a/core/java/android/app/SearchDialog.java +++ b/core/java/android/app/SearchDialog.java @@ -188,8 +188,7 @@ public class SearchDialog extends Dialog { mSearchView.findViewById(com.android.internal.R.id.search_src_text); mAppIcon = (ImageView) findViewById(com.android.internal.R.id.search_app_icon); mSearchPlate = mSearchView.findViewById(com.android.internal.R.id.search_plate); - mWorkingSpinner = getContext().getResources(). - getDrawable(com.android.internal.R.drawable.search_spinner); + mWorkingSpinner = getContext().getDrawable(com.android.internal.R.drawable.search_spinner); // TODO: Restore the spinner for slow suggestion lookups // mSearchAutoComplete.setCompoundDrawablesWithIntrinsicBounds( // null, null, mWorkingSpinner, null); @@ -458,7 +457,7 @@ public class SearchDialog extends Dialog { // optionally show one or the other. if (mSearchable.useBadgeIcon()) { - icon = mActivityContext.getResources().getDrawable(mSearchable.getIconId()); + icon = mActivityContext.getDrawable(mSearchable.getIconId()); visibility = View.VISIBLE; if (DBG) Log.d(LOG_TAG, "Using badge icon: " + mSearchable.getIconId()); } else if (mSearchable.useBadgeLabel()) { diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java index f9c245e..33c3409 100644 --- a/core/java/android/app/SearchManager.java +++ b/core/java/android/app/SearchManager.java @@ -22,7 +22,6 @@ import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; -import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.database.Cursor; import android.graphics.Rect; @@ -34,7 +33,6 @@ import android.os.ServiceManager; import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; -import android.util.Slog; import android.view.KeyEvent; import java.util.List; diff --git a/core/java/android/app/SharedPreferencesImpl.java b/core/java/android/app/SharedPreferencesImpl.java index a292ecb..1b838fb 100644 --- a/core/java/android/app/SharedPreferencesImpl.java +++ b/core/java/android/app/SharedPreferencesImpl.java @@ -42,7 +42,6 @@ import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; import libcore.io.ErrnoException; import libcore.io.IoUtils; diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java index 2045ed8..a6a04d1 100644 --- a/core/java/android/app/StatusBarManager.java +++ b/core/java/android/app/StatusBarManager.java @@ -38,8 +38,11 @@ public class StatusBarManager { public static final int DISABLE_NOTIFICATION_ICONS = View.STATUS_BAR_DISABLE_NOTIFICATION_ICONS; public static final int DISABLE_NOTIFICATION_ALERTS = View.STATUS_BAR_DISABLE_NOTIFICATION_ALERTS; + @Deprecated public static final int DISABLE_NOTIFICATION_TICKER = View.STATUS_BAR_DISABLE_NOTIFICATION_TICKER; + public static final int DISABLE_PRIVATE_NOTIFICATIONS + = View.STATUS_BAR_DISABLE_NOTIFICATION_TICKER; public static final int DISABLE_SYSTEM_INFO = View.STATUS_BAR_DISABLE_SYSTEM_INFO; public static final int DISABLE_HOME = View.STATUS_BAR_DISABLE_HOME; public static final int DISABLE_RECENT = View.STATUS_BAR_DISABLE_RECENT; diff --git a/core/java/android/app/TaskStackBuilder.java b/core/java/android/app/TaskStackBuilder.java index 3e0ac7e..0077db1 100644 --- a/core/java/android/app/TaskStackBuilder.java +++ b/core/java/android/app/TaskStackBuilder.java @@ -16,6 +16,7 @@ package android.app; +import android.annotation.NonNull; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -244,7 +245,7 @@ public class TaskStackBuilder { * * @return The obtained PendingIntent */ - public PendingIntent getPendingIntent(int requestCode, int flags) { + public PendingIntent getPendingIntent(int requestCode, @PendingIntent.Flags int flags) { return getPendingIntent(requestCode, flags, null); } @@ -263,7 +264,8 @@ public class TaskStackBuilder { * * @return The obtained PendingIntent */ - public PendingIntent getPendingIntent(int requestCode, int flags, Bundle options) { + public PendingIntent getPendingIntent(int requestCode, @PendingIntent.Flags int flags, + Bundle options) { if (mIntents.isEmpty()) { throw new IllegalStateException( "No intents added to TaskStackBuilder; cannot getPendingIntent"); @@ -294,6 +296,7 @@ public class TaskStackBuilder { * * @return An array containing the intents added to this builder. */ + @NonNull public Intent[] getIntents() { Intent[] intents = new Intent[mIntents.size()]; if (intents.length == 0) return intents; diff --git a/core/java/android/app/TimePickerDialog.java b/core/java/android/app/TimePickerDialog.java index 952227f..a85c61f 100644 --- a/core/java/android/app/TimePickerDialog.java +++ b/core/java/android/app/TimePickerDialog.java @@ -16,17 +16,19 @@ package android.app; -import com.android.internal.R; - import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.os.Bundle; +import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.widget.TimePicker; import android.widget.TimePicker.OnTimeChangedListener; +import com.android.internal.R; + + /** * A dialog that prompts the user for the time of day using a {@link TimePicker}. * @@ -38,7 +40,7 @@ public class TimePickerDialog extends AlertDialog /** * The callback interface used to indicate the user is done filling in - * the time (they clicked on the 'Set' button). + * the time (they clicked on the 'Done' button). */ public interface OnTimeSetListener { @@ -55,7 +57,7 @@ public class TimePickerDialog extends AlertDialog private static final String IS_24_HOUR = "is24hour"; private final TimePicker mTimePicker; - private final OnTimeSetListener mCallback; + private final OnTimeSetListener mTimeSetCallback; int mInitialHourOfDay; int mInitialMinute; @@ -74,6 +76,16 @@ public class TimePickerDialog extends AlertDialog this(context, 0, callBack, hourOfDay, minute, is24HourView); } + static int resolveDialogTheme(Context context, int resid) { + if (resid == 0) { + TypedValue outValue = new TypedValue(); + context.getTheme().resolveAttribute(R.attr.timePickerDialogTheme, outValue, true); + return outValue.resourceId; + } else { + return resid; + } + } + /** * @param context Parent. * @param theme the theme to apply to this dialog @@ -86,17 +98,13 @@ public class TimePickerDialog extends AlertDialog int theme, OnTimeSetListener callBack, int hourOfDay, int minute, boolean is24HourView) { - super(context, theme); - mCallback = callBack; + super(context, resolveDialogTheme(context, theme)); + mTimeSetCallback = callBack; mInitialHourOfDay = hourOfDay; mInitialMinute = minute; mIs24HourView = is24HourView; - setIcon(0); - setTitle(R.string.time_picker_dialog_title); - Context themeContext = getContext(); - setButton(BUTTON_POSITIVE, themeContext.getText(R.string.date_time_done), this); LayoutInflater inflater = (LayoutInflater) themeContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); @@ -104,7 +112,18 @@ public class TimePickerDialog extends AlertDialog setView(view); mTimePicker = (TimePicker) view.findViewById(R.id.timePicker); - // initialize state + // Initialize state + mTimePicker.setLegacyMode(false /* will show new UI */); + mTimePicker.setShowDoneButton(true); + mTimePicker.setDismissCallback(new TimePicker.TimePickerDismissCallback() { + @Override + public void dismiss(TimePicker view, boolean isCancel, int hourOfDay, int minute) { + if (!isCancel) { + mTimeSetCallback.onTimeSet(view, hourOfDay, minute); + } + TimePickerDialog.this.dismiss(); + } + }); mTimePicker.setIs24HourView(mIs24HourView); mTimePicker.setCurrentHour(mInitialHourOfDay); mTimePicker.setCurrentMinute(mInitialMinute); @@ -125,9 +144,9 @@ public class TimePickerDialog extends AlertDialog } private void tryNotifyTimeSet() { - if (mCallback != null) { + if (mTimeSetCallback != null) { mTimePicker.clearFocus(); - mCallback.onTimeSet(mTimePicker, mTimePicker.getCurrentHour(), + mTimeSetCallback.onTimeSet(mTimePicker, mTimePicker.getCurrentHour(), mTimePicker.getCurrentMinute()); } } diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index ced72f8..e6e0f35 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -41,13 +41,10 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; -import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ServiceManager; -import android.util.DisplayMetrics; import android.util.Log; -import android.view.WindowManager; import android.view.WindowManagerGlobal; import java.io.BufferedInputStream; @@ -221,24 +218,9 @@ public class WallpaperManager { private static final int MSG_CLEAR_WALLPAPER = 1; - private final Handler mHandler; - Globals(Looper looper) { IBinder b = ServiceManager.getService(Context.WALLPAPER_SERVICE); mService = IWallpaperManager.Stub.asInterface(b); - mHandler = new Handler(looper) { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_CLEAR_WALLPAPER: - synchronized (this) { - mWallpaper = null; - mDefaultWallpaper = null; - } - break; - } - } - }; } public void onWallpaperChanged() { @@ -247,7 +229,10 @@ public class WallpaperManager { * to null so if the user requests the wallpaper again then we'll * fetch it. */ - mHandler.sendEmptyMessage(MSG_CLEAR_WALLPAPER); + synchronized (this) { + mWallpaper = null; + mDefaultWallpaper = null; + } } public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault) { @@ -280,7 +265,6 @@ public class WallpaperManager { synchronized (this) { mWallpaper = null; mDefaultWallpaper = null; - mHandler.removeMessages(MSG_CLEAR_WALLPAPER); } } @@ -294,9 +278,8 @@ public class WallpaperManager { try { BitmapFactory.Options options = new BitmapFactory.Options(); - Bitmap bm = BitmapFactory.decodeFileDescriptor( + return BitmapFactory.decodeFileDescriptor( fd.getFileDescriptor(), null, options); - return generateBitmap(context, bm, width, height); } catch (OutOfMemoryError e) { Log.w(TAG, "Can't decode file", e); } finally { @@ -323,8 +306,7 @@ public class WallpaperManager { try { BitmapFactory.Options options = new BitmapFactory.Options(); - Bitmap bm = BitmapFactory.decodeStream(is, null, options); - return generateBitmap(context, bm, width, height); + return BitmapFactory.decodeStream(is, null, options); } catch (OutOfMemoryError e) { Log.w(TAG, "Can't decode stream", e); } finally { @@ -1029,62 +1011,4 @@ public class WallpaperManager { public void clear() throws IOException { setResource(com.android.internal.R.drawable.default_wallpaper); } - - static Bitmap generateBitmap(Context context, Bitmap bm, int width, int height) { - if (bm == null) { - return null; - } - - WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); - DisplayMetrics metrics = new DisplayMetrics(); - wm.getDefaultDisplay().getMetrics(metrics); - bm.setDensity(metrics.noncompatDensityDpi); - - if (width <= 0 || height <= 0 - || (bm.getWidth() == width && bm.getHeight() == height)) { - return bm; - } - - // This is the final bitmap we want to return. - try { - Bitmap newbm = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - newbm.setDensity(metrics.noncompatDensityDpi); - - Canvas c = new Canvas(newbm); - Rect targetRect = new Rect(); - targetRect.right = bm.getWidth(); - targetRect.bottom = bm.getHeight(); - - int deltaw = width - targetRect.right; - int deltah = height - targetRect.bottom; - - if (deltaw > 0 || deltah > 0) { - // We need to scale up so it covers the entire area. - float scale; - if (deltaw > deltah) { - scale = width / (float)targetRect.right; - } else { - scale = height / (float)targetRect.bottom; - } - targetRect.right = (int)(targetRect.right*scale); - targetRect.bottom = (int)(targetRect.bottom*scale); - deltaw = width - targetRect.right; - deltah = height - targetRect.bottom; - } - - targetRect.offset(deltaw/2, deltah/2); - - Paint paint = new Paint(); - paint.setFilterBitmap(true); - paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); - c.drawBitmap(bm, null, targetRect, paint); - - bm.recycle(); - c.setBitmap(null); - return newbm; - } catch (OutOfMemoryError e) { - Log.w(TAG, "Can't generate default bitmap", e); - return bm; - } - } } diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java index 30b65de..f9d9059 100644 --- a/core/java/android/app/admin/DeviceAdminReceiver.java +++ b/core/java/android/app/admin/DeviceAdminReceiver.java @@ -29,25 +29,25 @@ import android.os.Bundle; * Base class for implementing a device administration component. This * class provides a convenience for interpreting the raw intent actions * that are sent by the system. - * + * * <p>The callback methods, like the base * {@link BroadcastReceiver#onReceive(Context, Intent) BroadcastReceiver.onReceive()} * method, happen on the main thread of the process. Thus long running * operations must be done on another thread. Note that because a receiver * is done once returning from its receive function, such long-running operations * should probably be done in a {@link Service}. - * + * * <p>When publishing your DeviceAdmin subclass as a receiver, it must * handle {@link #ACTION_DEVICE_ADMIN_ENABLED} and require the * {@link android.Manifest.permission#BIND_DEVICE_ADMIN} permission. A typical * manifest entry would look like:</p> - * + * * {@sample development/samples/ApiDemos/AndroidManifest.xml device_admin_declaration} - * + * * <p>The meta-data referenced here provides addition information specific * to the device administrator, as parsed by the {@link DeviceAdminInfo} class. * A typical file would be:</p> - * + * * {@sample development/samples/ApiDemos/res/xml/device_admin_sample.xml meta_data} * * <div class="special reference"> @@ -86,7 +86,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_DEVICE_ADMIN_DISABLE_REQUESTED = "android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED"; - + /** * A CharSequence that can be shown to the user informing them of the * impact of disabling your admin. @@ -94,7 +94,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * @see #ACTION_DEVICE_ADMIN_DISABLE_REQUESTED */ public static final String EXTRA_DISABLE_WARNING = "android.app.extra.DISABLE_WARNING"; - + /** * Action sent to a device administrator when the user has disabled * it. Upon return, the application no longer has access to the @@ -107,7 +107,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_DEVICE_ADMIN_DISABLED = "android.app.action.DEVICE_ADMIN_DISABLED"; - + /** * Action sent to a device administrator when the user has changed the * password of their device. You can at this point check the characteristics @@ -115,7 +115,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * DevicePolicyManager.isActivePasswordSufficient()}. * You will generally * handle this in {@link DeviceAdminReceiver#onPasswordChanged}. - * + * * <p>The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to receive * this broadcast. @@ -123,7 +123,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_PASSWORD_CHANGED = "android.app.action.ACTION_PASSWORD_CHANGED"; - + /** * Action sent to a device administrator when the user has failed at * attempted to enter the password. You can at this point check the @@ -131,7 +131,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * {@link DevicePolicyManager#getCurrentFailedPasswordAttempts * DevicePolicyManager.getCurrentFailedPasswordAttempts()}. You will generally * handle this in {@link DeviceAdminReceiver#onPasswordFailed}. - * + * * <p>The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} to receive * this broadcast. @@ -139,11 +139,11 @@ public class DeviceAdminReceiver extends BroadcastReceiver { @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_PASSWORD_FAILED = "android.app.action.ACTION_PASSWORD_FAILED"; - + /** * Action sent to a device administrator when the user has successfully * entered their password, after failing one or more times. - * + * * <p>The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} to receive * this broadcast. @@ -165,15 +165,31 @@ public class DeviceAdminReceiver extends BroadcastReceiver { = "android.app.action.ACTION_PASSWORD_EXPIRING"; /** + * Broadcast Action: This broadcast is sent to the newly created profile when + * the provisioning of a managed profile has completed successfully. + * + * <p>The broadcast is limited to the package which started the provisioning as specified in + * the extra {@link DevicePolicyManager#EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME} of the + * {@link DevicePolicyManager#ACTION_PROVISION_MANAGED_PROFILE} intent that started the + * provisioning. It is also limited to the managed profile. + * + * <p>Input: Nothing.</p> + * <p>Output: Nothing</p> + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_PROFILE_PROVISIONING_COMPLETE = + "android.app.action.ACTION_PROFILE_PROVISIONING_COMPLETE"; + + /** * Name under which a DevicePolicy component publishes information * about itself. This meta-data must reference an XML resource containing * a device-admin tag. XXX TO DO: describe syntax. */ public static final String DEVICE_ADMIN_META_DATA = "android.app.device_admin"; - + private DevicePolicyManager mManager; private ComponentName mWho; - + /** * Retrieve the DevicePolicyManager interface for this administrator to work * with the system. @@ -186,7 +202,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { Context.DEVICE_POLICY_SERVICE); return mManager; } - + /** * Retrieve the ComponentName describing who this device administrator is, for * use in {@link DevicePolicyManager} APIs that require the administrator to @@ -199,7 +215,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { mWho = new ComponentName(context, getClass()); return mWho; } - + /** * Called after the administrator is first enabled, as a result of * receiving {@link #ACTION_DEVICE_ADMIN_ENABLED}. At this point you @@ -209,7 +225,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { */ public void onEnabled(Context context, Intent intent) { } - + /** * Called when the user has asked to disable the administrator, as a result of * receiving {@link #ACTION_DEVICE_ADMIN_DISABLE_REQUESTED}, giving you @@ -224,7 +240,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { public CharSequence onDisableRequested(Context context, Intent intent) { return null; } - + /** * Called prior to the administrator being disabled, as a result of * receiving {@link #ACTION_DEVICE_ADMIN_DISABLED}. Upon return, you @@ -235,7 +251,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { */ public void onDisabled(Context context, Intent intent) { } - + /** * Called after the user has changed their password, as a result of * receiving {@link #ACTION_PASSWORD_CHANGED}. At this point you @@ -247,7 +263,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { */ public void onPasswordChanged(Context context, Intent intent) { } - + /** * Called after the user has failed at entering their current password, as a result of * receiving {@link #ACTION_PASSWORD_FAILED}. At this point you @@ -258,7 +274,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { */ public void onPasswordFailed(Context context, Intent intent) { } - + /** * Called after the user has succeeded at entering their current password, * as a result of receiving {@link #ACTION_PASSWORD_SUCCEEDED}. This will @@ -292,6 +308,26 @@ public class DeviceAdminReceiver extends BroadcastReceiver { } /** + * Called on the new profile when managed profile provisioning has completed. + * Managed profile provisioning is the process of setting up the device so that it has a + * separate profile which is managed by the mobile device management(mdm) application that + * triggered the provisioning. + * + * <p>As part of provisioning a new profile is created, the mdm is moved to the new profile and + * set as the owner of the profile so that it has full control over it. + * This intent is only received by the mdm package that is set as profile owner during + * provisioning. + * + * <p>Provisioning can be triggered via an intent with the action + * android.managedprovisioning.ACTION_PROVISION_MANAGED_PROFILE. + * + * @param context The running context as per {@link #onReceive}. + * @param intent The received intent as per {@link #onReceive}. + */ + public void onProfileProvisioningComplete(Context context, Intent intent) { + } + + /** * Intercept standard device administrator broadcasts. Implementations * should not override this method; it is better to implement the * convenience callbacks for each action. @@ -299,6 +335,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); + if (ACTION_PASSWORD_CHANGED.equals(action)) { onPasswordChanged(context, intent); } else if (ACTION_PASSWORD_FAILED.equals(action)) { @@ -317,6 +354,8 @@ public class DeviceAdminReceiver extends BroadcastReceiver { onDisabled(context, intent); } else if (ACTION_PASSWORD_EXPIRING.equals(action)) { onPasswordExpiring(context, intent); + } else if (ACTION_PROFILE_PROVISIONING_COMPLETE.equals(action)) { + onProfileProvisioningComplete(context, intent); } } } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index ab82531..e06cf38 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -22,10 +22,12 @@ import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.content.ComponentName; import android.content.Context; +import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.Handler; +import android.os.Process; import android.os.RemoteCallback; import android.os.RemoteException; import android.os.ServiceManager; @@ -75,6 +77,43 @@ public class DevicePolicyManager { } /** + * Activity action: Starts the provisioning flow which sets up a managed profile. + * This intent will typically be sent by a mobile device management application(mdm). + * Managed profile provisioning creates a profile, moves the mdm to the profile, + * sets the mdm as the profile owner and removes all non required applications from the profile. + * As a profile owner the mdm than has full control over the managed profile. + * + * <p>The intent must contain the extras {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME} and + * {@link #EXTRA_PROVISIONING_DEFAULT_MANAGED_PROFILE_NAME}. + * + * <p> When managed provisioning has completed, an intent of the type + * {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE} is broadcasted to the + * mdm app on the managed profile. + * + * <p>Input: Nothing.</p> + * <p>Output: Nothing</p> + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_PROVISION_MANAGED_PROFILE + = "android.managedprovisioning.ACTION_PROVISION_MANAGED_PROFILE"; + + /** + * A String extra holding the name of the package of the mobile device management application + * that starts the managed provisioning flow. This package will be set as the profile owner. + * <p>Use with {@link #ACTION_PROVISION_MANAGED_PROFILE}. + */ + public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME + = "deviceAdminPackageName"; + + /** + * A String extra holding the default name of the profile that is created during managed profile + * provisioning. + * <p>Use with {@link #ACTION_PROVISION_MANAGED_PROFILE} + */ + public static final String EXTRA_PROVISIONING_DEFAULT_MANAGED_PROFILE_NAME + = "defaultManagedProfileName"; + + /** * Activity action: ask the user to add a new device administrator to the system. * The desired policy is the ComponentName of the policy in the * {@link #EXTRA_DEVICE_ADMIN} extra field. This will invoke a UI to @@ -1153,7 +1192,9 @@ public class DevicePolicyManager { } exclSpec = listBuilder.toString(); } - android.net.Proxy.validate(hostName, Integer.toString(port), exclSpec); + if (android.net.Proxy.validate(hostName, Integer.toString(port), exclSpec) + != android.net.Proxy.PROXY_VALID) + throw new IllegalArgumentException(); } return mService.setGlobalProxy(admin, hostSpec, exclSpec, UserHandle.myUserId()); } catch (RemoteException e) { @@ -1681,4 +1722,137 @@ public class DevicePolicyManager { } return null; } + + /** + * @hide + * Sets the given package as the profile owner of the given user profile. The package must + * already be installed and there shouldn't be an existing profile owner registered for this + * user. Also, this method must be called before the user has been used for the first time. + * @param packageName the package name of the application to be registered as profile owner. + * @param ownerName the human readable name of the organisation associated with this DPM. + * @param userHandle the userId to set the profile owner for. + * @return whether the package was successfully registered as the profile owner. + * @throws IllegalArgumentException if packageName is null, the package isn't installed, or + * the user has already been set up. + */ + public boolean setProfileOwner(String packageName, String ownerName, int userHandle) + throws IllegalArgumentException { + if (mService != null) { + try { + return mService.setProfileOwner(packageName, ownerName, userHandle); + } catch (RemoteException re) { + Log.w(TAG, "Failed to set profile owner", re); + throw new IllegalArgumentException("Couldn't set profile owner.", re); + } + } + return false; + } + + /** + * Used to determine if a particular package is registered as the Profile Owner for the + * current user. A profile owner is a special device admin that has additional priviledges + * within the managed profile. + * + * @param packageName The package name of the app to compare with the registered profile owner. + * @return Whether or not the package is registered as the profile owner. + */ + public boolean isProfileOwnerApp(String packageName) { + if (mService != null) { + try { + String profileOwnerPackage = mService.getProfileOwner( + Process.myUserHandle().getIdentifier()); + return profileOwnerPackage != null && profileOwnerPackage.equals(packageName); + } catch (RemoteException re) { + Log.w(TAG, "Failed to check profile owner"); + } + } + return false; + } + + /** + * @hide + * @return the packageName of the owner of the given user profile or null if no profile + * owner has been set for that user. + * @throws IllegalArgumentException if the userId is invalid. + */ + public String getProfileOwner() throws IllegalArgumentException { + if (mService != null) { + try { + return mService.getProfileOwner(Process.myUserHandle().getIdentifier()); + } catch (RemoteException re) { + Log.w(TAG, "Failed to get profile owner"); + throw new IllegalArgumentException( + "Requested profile owner for invalid userId", re); + } + } + return null; + } + + /** + * @hide + * @return the human readable name of the organisation associated with this DPM or null if + * one is not set. + * @throws IllegalArgumentException if the userId is invalid. + */ + public String getProfileOwnerName() throws IllegalArgumentException { + if (mService != null) { + try { + return mService.getProfileOwnerName(Process.myUserHandle().getIdentifier()); + } catch (RemoteException re) { + Log.w(TAG, "Failed to get profile owner"); + throw new IllegalArgumentException( + "Requested profile owner for invalid userId", re); + } + } + return null; + } + + /** + * Called by a profile owner or device owner to add a default intent handler activity for + * intents that match a certain intent filter. This activity will remain the default intent + * handler even if the set of potential event handlers for the intent filter changes and if + * the intent preferences are reset. + * + * <p>The default disambiguation mechanism takes over if the activity is not installed + * (anymore). When the activity is (re)installed, it is automatically reset as default + * intent handler for the filter. + * + * <p>The calling device admin must be a profile owner or device owner. If it is not, a + * security exception will be thrown. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param filter The IntentFilter for which a default handler is added. + * @param activity The Activity that is added as default intent handler. + */ + public void addPersistentPreferredActivity(ComponentName admin, IntentFilter filter, + ComponentName activity) { + if (mService != null) { + try { + mService.addPersistentPreferredActivity(admin, filter, activity); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + } + + /** + * Called by a profile owner or device owner to remove all persistent intent handler preferences + * associated with the given package that were set by {@link #addPersistentPreferredActivity}. + * + * <p>The calling device admin must be a profile owner. If it is not, a security + * exception will be thrown. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param packageName The name of the package for which preferences are removed. + */ + public void clearPackagePersistentPreferredActivities(ComponentName admin, + String packageName) { + if (mService != null) { + try { + mService.clearPackagePersistentPreferredActivities(admin, packageName); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 172c47c..8119585 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -18,6 +18,7 @@ package android.app.admin; import android.content.ComponentName; +import android.content.IntentFilter; import android.os.RemoteCallback; /** @@ -103,6 +104,13 @@ interface IDevicePolicyManager { String getDeviceOwner(); String getDeviceOwnerName(); + boolean setProfileOwner(String packageName, String ownerName, int userHandle); + String getProfileOwner(int userHandle); + String getProfileOwnerName(int userHandle); + boolean installCaCert(in byte[] certBuffer); void uninstallCaCert(in byte[] certBuffer); + + void addPersistentPreferredActivity(in ComponentName admin, in IntentFilter filter, in ComponentName activity); + void clearPackagePersistentPreferredActivities(in ComponentName admin, String packageName); } diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java index cb0737e..cfd0a65 100644 --- a/core/java/android/app/backup/FullBackup.java +++ b/core/java/android/app/backup/FullBackup.java @@ -16,9 +16,6 @@ package android.app.backup; -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; import android.os.ParcelFileDescriptor; import android.util.Log; diff --git a/core/java/android/app/backup/SharedPreferencesBackupHelper.java b/core/java/android/app/backup/SharedPreferencesBackupHelper.java index 213bd31..939616b 100644 --- a/core/java/android/app/backup/SharedPreferencesBackupHelper.java +++ b/core/java/android/app/backup/SharedPreferencesBackupHelper.java @@ -18,7 +18,6 @@ package android.app.backup; import android.app.QueuedWork; import android.content.Context; -import android.content.SharedPreferences; import android.os.ParcelFileDescriptor; import android.util.Log; diff --git a/core/java/android/app/maintenance/IIdleCallback.aidl b/core/java/android/app/maintenance/IIdleCallback.aidl new file mode 100644 index 0000000..582dede --- /dev/null +++ b/core/java/android/app/maintenance/IIdleCallback.aidl @@ -0,0 +1,53 @@ +/** + * Copyright 2014, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.maintenance; + +import android.app.maintenance.IIdleService; + +/** + * The server side of the idle maintenance IPC protocols. The app-side implementation + * invokes on this interface to indicate completion of the (asynchronous) instructions + * issued by the server. + * + * In all cases, the 'who' parameter is the caller's service binder, used to track + * which idle service instance is reporting. + * + * {@hide} + */ +interface IIdleCallback { + /** + * Acknowledge receipt and processing of the asynchronous "start idle work" incall. + * 'result' is true if the app wants some time to perform ongoing background + * idle-time work; or false if the app declares that it does not need any time + * for such work. + */ + void acknowledgeStart(int token, boolean result); + + /** + * Acknowledge receipt and processing of the asynchronous "stop idle work" incall. + */ + void acknowledgeStop(int token); + + /* + * Tell the idle service manager that we're done with our idle maintenance, so that + * it can go on to the next one and stop attributing wakelock time to us etc. + * + * @param opToken The identifier passed in the startIdleMaintenance() call that + * indicated the beginning of this service's idle timeslice. + */ + void idleFinished(int token); +} diff --git a/core/java/android/app/maintenance/IIdleService.aidl b/core/java/android/app/maintenance/IIdleService.aidl new file mode 100644 index 0000000..54abccd --- /dev/null +++ b/core/java/android/app/maintenance/IIdleService.aidl @@ -0,0 +1,34 @@ +/** + * Copyright 2014, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.maintenance; + +import android.app.maintenance.IIdleCallback; + +/** + * Interface that the framework uses to communicate with application code + * that implements an idle-time "maintenance" service. End user code does + * not implement this interface directly; instead, the app's idle service + * implementation will extend android.app.maintenance.IdleService. + * {@hide} + */ +oneway interface IIdleService { + /** + * Begin your idle-time work. + */ + void startIdleMaintenance(IIdleCallback callbackBinder, int token); + void stopIdleMaintenance(IIdleCallback callbackBinder, int token); +} diff --git a/core/java/android/app/maintenance/IdleService.java b/core/java/android/app/maintenance/IdleService.java new file mode 100644 index 0000000..2331b81 --- /dev/null +++ b/core/java/android/app/maintenance/IdleService.java @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.maintenance; + +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; +import android.app.Service; +import android.content.Intent; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.util.Log; +import android.util.Slog; + +/** + * Idle maintenance API. Full docs TBW (to be written). + */ +public abstract class IdleService extends Service { + private static final String TAG = "IdleService"; + + static final int MSG_START = 1; + static final int MSG_STOP = 2; + static final int MSG_FINISH = 3; + + IdleHandler mHandler; + IIdleCallback mCallbackBinder; + int mToken; + final Object mHandlerLock = new Object(); + + void ensureHandler() { + synchronized (mHandlerLock) { + if (mHandler == null) { + mHandler = new IdleHandler(getMainLooper()); + } + } + } + + /** + * TBW: the idle service should supply an intent-filter handling this intent + * <p> + * <p class="note">The application must also protect the idle service with the + * {@code "android.permission.BIND_IDLE_SERVICE"} permission to ensure that other + * applications cannot maliciously bind to it. If an idle service's manifest + * declaration does not require that permission, it will never be invoked. + * </p> + */ + @SdkConstant(SdkConstantType.SERVICE_ACTION) + public static final String SERVICE_INTERFACE = + "android.service.idle.IdleService"; + + /** + * Idle services must be protected with this permission: + * + * <pre class="prettyprint"> + * <service android:name="MyIdleService" + * android:permission="android.permission.BIND_IDLE_SERVICE" > + * ... + * </service> + * </pre> + * + * <p>If an idle service is declared in the manifest but not protected with this + * permission, that service will be ignored by the OS. + */ + public static final String PERMISSION_BIND = + "android.permission.BIND_IDLE_SERVICE"; + + // Trampoline: the callbacks are always run on the main thread + IIdleService mBinder = new IIdleService.Stub() { + @Override + public void startIdleMaintenance(IIdleCallback callbackBinder, int token) + throws RemoteException { + ensureHandler(); + Message msg = mHandler.obtainMessage(MSG_START, token, 0, callbackBinder); + mHandler.sendMessage(msg); + } + + @Override + public void stopIdleMaintenance(IIdleCallback callbackBinder, int token) + throws RemoteException { + ensureHandler(); + Message msg = mHandler.obtainMessage(MSG_STOP, token, 0, callbackBinder); + mHandler.sendMessage(msg); + } + }; + + /** + * Your application may begin doing "idle" maintenance work in the background. + * <p> + * Your application may continue to run in the background until it receives a call + * to {@link #onIdleStop()}, at which point you <i>must</i> cease doing work. The + * OS will hold a wakelock on your application's behalf from the time this method is + * called until after the following call to {@link #onIdleStop()} returns. + * </p> + * <p> + * Returning {@code false} from this method indicates that you have no ongoing work + * to do at present. The OS will respond by immediately calling {@link #onIdleStop()} + * and returning your application to its normal stopped state. Returning {@code true} + * indicates that the application is indeed performing ongoing work, so the OS will + * let your application run in this state until it's no longer appropriate. + * </p> + * <p> + * You will always receive a matching call to {@link #onIdleStop()} even if your + * application returns {@code false} from this method. + * + * @return {@code true} to indicate that the application wishes to perform some ongoing + * background work; {@code false} to indicate that it does not need to perform such + * work at present. + */ + public abstract boolean onIdleStart(); + + /** + * Your app's maintenance opportunity is over. Once the application returns from + * this method, the wakelock held by the OS on its behalf will be released. + */ + public abstract void onIdleStop(); + + /** + * Tell the OS that you have finished your idle work. Calling this more than once, + * or calling it when you have not received an {@link #onIdleStart()} callback, is + * an error. + * + * <p>It is safe to call {@link #finishIdle()} from any thread. + */ + public final void finishIdle() { + ensureHandler(); + mHandler.sendEmptyMessage(MSG_FINISH); + } + + class IdleHandler extends Handler { + IdleHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_START: { + // Call the concrete onIdleStart(), reporting its return value back to + // the OS. If onIdleStart() throws, report it as a 'false' return but + // rethrow the exception at the offending app. + boolean result = false; + IIdleCallback callbackBinder = (IIdleCallback) msg.obj; + mCallbackBinder = callbackBinder; + final int token = mToken = msg.arg1; + try { + result = IdleService.this.onIdleStart(); + } catch (Exception e) { + Log.e(TAG, "Unable to start idle workload", e); + throw new RuntimeException(e); + } finally { + // don't bother if the service already called finishIdle() + if (mCallbackBinder != null) { + try { + callbackBinder.acknowledgeStart(token, result); + } catch (RemoteException re) { + Log.e(TAG, "System unreachable to start idle workload"); + } + } + } + break; + } + + case MSG_STOP: { + // Structured just like MSG_START for the stop-idle bookend call. + IIdleCallback callbackBinder = (IIdleCallback) msg.obj; + final int token = msg.arg1; + try { + IdleService.this.onIdleStop(); + } catch (Exception e) { + Log.e(TAG, "Unable to stop idle workload", e); + throw new RuntimeException(e); + } finally { + if (mCallbackBinder != null) { + try { + callbackBinder.acknowledgeStop(token); + } catch (RemoteException re) { + Log.e(TAG, "System unreachable to stop idle workload"); + } + } + } + break; + } + + case MSG_FINISH: { + if (mCallbackBinder != null) { + try { + mCallbackBinder.idleFinished(mToken); + } catch (RemoteException e) { + Log.e(TAG, "System unreachable to finish idling"); + } finally { + mCallbackBinder = null; + } + } else { + Log.e(TAG, "finishIdle() called but the idle service is not started"); + } + break; + } + + default: { + Slog.w(TAG, "Unknown message " + msg.what); + } + } + } + } + + /** @hide */ + @Override + public final IBinder onBind(Intent intent) { + return mBinder.asBinder(); + } + +} diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java index f104d71..84d3835 100644 --- a/core/java/android/appwidget/AppWidgetHost.java +++ b/core/java/android/appwidget/AppWidgetHost.java @@ -19,7 +19,6 @@ package android.appwidget; import java.util.ArrayList; import java.util.HashMap; -import android.app.ActivityThread; import android.content.Context; import android.os.Binder; import android.os.Handler; @@ -31,7 +30,6 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; import android.util.DisplayMetrics; -import android.util.Log; import android.util.TypedValue; import android.widget.RemoteViews; import android.widget.RemoteViews.OnClickHandler; diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java index d1c7bec..8a89cbc 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -16,7 +16,6 @@ package android.appwidget; -import android.app.ActivityManagerNative; import android.content.ComponentName; import android.content.Context; import android.content.Intent; diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java index 7b8b286..4b33799 100644 --- a/core/java/android/appwidget/AppWidgetProviderInfo.java +++ b/core/java/android/appwidget/AppWidgetProviderInfo.java @@ -172,7 +172,7 @@ public class AppWidgetProviderInfo implements Parcelable { * <p>This field corresponds to the <code>android:previewImage</code> attribute in * the <code><receiver></code> element in the AndroidManifest.xml file. */ - public int previewImage; + public int previewImage; /** * The rules by which a widget can be resized. See {@link #RESIZE_NONE}, diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index d1f1f2a..38a71aa 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -19,9 +19,7 @@ package android.bluetooth; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.content.Context; -import android.os.Binder; import android.os.IBinder; -import android.os.Message; import android.os.ParcelUuid; import android.os.RemoteException; import android.os.ServiceManager; @@ -37,7 +35,6 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Locale; import java.util.Map; -import java.util.Random; import java.util.Set; import java.util.UUID; diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index d789a94..a396a05 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -19,12 +19,10 @@ package android.bluetooth; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.content.Context; -import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.os.ParcelUuid; import android.os.RemoteException; -import android.os.ServiceManager; import android.util.Log; import java.io.IOException; @@ -947,8 +945,13 @@ public final class BluetoothDevice implements Parcelable { * was started. */ public boolean fetchUuidsWithSdp() { + IBluetooth service = sService; + if (service == null) { + Log.e(TAG, "BT not enabled. Cannot fetchUuidsWithSdp"); + return false; + } try { - return sService.fetchRemoteUuids(this); + return service.fetchRemoteUuids(this); } catch (RemoteException e) {Log.e(TAG, "", e);} return false; } diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java index cd093c5..e3820a2 100644 --- a/core/java/android/bluetooth/BluetoothGatt.java +++ b/core/java/android/bluetooth/BluetoothGatt.java @@ -18,18 +18,9 @@ package android.bluetooth; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; -import android.bluetooth.BluetoothProfile.ServiceListener; -import android.bluetooth.IBluetoothManager; -import android.bluetooth.IBluetoothStateChangeCallback; - -import android.content.ComponentName; import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.IBinder; import android.os.ParcelUuid; import android.os.RemoteException; -import android.os.ServiceManager; import android.util.Log; import java.util.ArrayList; diff --git a/core/java/android/bluetooth/BluetoothGattCharacteristic.java b/core/java/android/bluetooth/BluetoothGattCharacteristic.java index f0ecbb4..a86677c 100644 --- a/core/java/android/bluetooth/BluetoothGattCharacteristic.java +++ b/core/java/android/bluetooth/BluetoothGattCharacteristic.java @@ -16,7 +16,6 @@ package android.bluetooth; import java.util.ArrayList; -import java.util.IllegalFormatConversionException; import java.util.List; import java.util.UUID; diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java index 153215c..0c00c06 100644 --- a/core/java/android/bluetooth/BluetoothGattServer.java +++ b/core/java/android/bluetooth/BluetoothGattServer.java @@ -19,18 +19,9 @@ package android.bluetooth; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; -import android.bluetooth.BluetoothProfile.ServiceListener; -import android.bluetooth.IBluetoothManager; -import android.bluetooth.IBluetoothStateChangeCallback; - -import android.content.ComponentName; import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.IBinder; import android.os.ParcelUuid; import android.os.RemoteException; -import android.os.ServiceManager; import android.util.Log; import java.util.ArrayList; diff --git a/core/java/android/bluetooth/BluetoothGattServerCallback.java b/core/java/android/bluetooth/BluetoothGattServerCallback.java index f9f1d97..fc3ffe8 100644 --- a/core/java/android/bluetooth/BluetoothGattServerCallback.java +++ b/core/java/android/bluetooth/BluetoothGattServerCallback.java @@ -18,8 +18,6 @@ package android.bluetooth; import android.bluetooth.BluetoothDevice; -import android.util.Log; - /** * This abstract class is used to implement {@link BluetoothGattServer} callbacks. */ diff --git a/core/java/android/bluetooth/BluetoothHealth.java b/core/java/android/bluetooth/BluetoothHealth.java index 2e950fa..daf3bad 100644 --- a/core/java/android/bluetooth/BluetoothHealth.java +++ b/core/java/android/bluetooth/BluetoothHealth.java @@ -23,7 +23,6 @@ import android.content.ServiceConnection; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; -import android.os.ServiceManager; import android.util.Log; import java.util.ArrayList; diff --git a/core/java/android/bluetooth/BluetoothInputDevice.java b/core/java/android/bluetooth/BluetoothInputDevice.java index c48b15d..333f825 100644 --- a/core/java/android/bluetooth/BluetoothInputDevice.java +++ b/core/java/android/bluetooth/BluetoothInputDevice.java @@ -24,7 +24,6 @@ import android.content.Intent; import android.content.ServiceConnection; import android.os.IBinder; import android.os.RemoteException; -import android.os.ServiceManager; import android.util.Log; import java.util.ArrayList; diff --git a/core/java/android/bluetooth/BluetoothMap.java b/core/java/android/bluetooth/BluetoothMap.java index 92a2f1e..5a1b7aa 100644 --- a/core/java/android/bluetooth/BluetoothMap.java +++ b/core/java/android/bluetooth/BluetoothMap.java @@ -24,7 +24,6 @@ import android.content.Intent; import android.content.ServiceConnection; import android.os.RemoteException; import android.os.IBinder; -import android.os.ServiceManager; import android.util.Log; /** diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java index b7a37f4..e72832c 100644 --- a/core/java/android/bluetooth/BluetoothPan.java +++ b/core/java/android/bluetooth/BluetoothPan.java @@ -24,7 +24,6 @@ import android.content.Intent; import android.content.ServiceConnection; import android.os.IBinder; import android.os.RemoteException; -import android.os.ServiceManager; import android.util.Log; import java.util.ArrayList; diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java index 7f45652..8522ee0 100644 --- a/core/java/android/bluetooth/BluetoothPbap.java +++ b/core/java/android/bluetooth/BluetoothPbap.java @@ -22,7 +22,6 @@ import android.content.Intent; import android.content.ServiceConnection; import android.os.RemoteException; import android.os.IBinder; -import android.os.ServiceManager; import android.util.Log; /** diff --git a/core/java/android/bluetooth/BluetoothServerSocket.java b/core/java/android/bluetooth/BluetoothServerSocket.java index 96be8a2..bc56e55 100644 --- a/core/java/android/bluetooth/BluetoothServerSocket.java +++ b/core/java/android/bluetooth/BluetoothServerSocket.java @@ -17,7 +17,6 @@ package android.bluetooth; import android.os.Handler; -import android.os.Message; import android.os.ParcelUuid; import java.io.Closeable; diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java index 1e75fc2..f532f7c 100644 --- a/core/java/android/bluetooth/BluetoothSocket.java +++ b/core/java/android/bluetooth/BluetoothSocket.java @@ -16,21 +16,16 @@ package android.bluetooth; -import android.os.IBinder; import android.os.ParcelUuid; import android.os.ParcelFileDescriptor; import android.os.RemoteException; -import android.os.ServiceManager; import android.util.Log; import java.io.Closeable; import java.io.FileDescriptor; -import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.util.List; import java.util.Locale; import java.util.UUID; import android.net.LocalSocket; @@ -462,8 +457,10 @@ public final class BluetoothSocket implements Closeable { mSocket.close(); mSocket = null; } - if(mPfd != null) - mPfd.detachFd(); + if (mPfd != null) { + mPfd.close(); + mPfd = null; + } } } } diff --git a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java index a9b7176..6dd551e 100644 --- a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java +++ b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java @@ -17,9 +17,6 @@ package android.bluetooth; import android.net.BaseNetworkStateTracker; -import android.os.IBinder; -import android.os.ServiceManager; -import android.os.INetworkManagementService; import android.content.Context; import android.net.ConnectivityManager; import android.net.DhcpResults; @@ -35,11 +32,6 @@ import android.os.Message; import android.os.Messenger; import android.text.TextUtils; import android.util.Log; -import java.net.InterfaceAddress; -import android.net.LinkAddress; -import android.net.RouteInfo; -import java.net.Inet4Address; -import android.os.SystemProperties; import com.android.internal.util.AsyncChannel; @@ -143,11 +135,6 @@ public class BluetoothTetheringDataTracker extends BaseNetworkStateTracker { } @Override - public void captivePortalCheckComplete() { - // not implemented - } - - @Override public void captivePortalCheckCompleted(boolean isCaptivePortal) { // not implemented } diff --git a/core/java/android/content/AsyncTaskLoader.java b/core/java/android/content/AsyncTaskLoader.java index eb7426e..7241e0d 100644 --- a/core/java/android/content/AsyncTaskLoader.java +++ b/core/java/android/content/AsyncTaskLoader.java @@ -20,7 +20,7 @@ import android.os.AsyncTask; import android.os.Handler; import android.os.OperationCanceledException; import android.os.SystemClock; -import android.util.Slog; +import android.util.Log; import android.util.TimeUtils; import java.io.FileDescriptor; @@ -64,10 +64,10 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> { /* Runs on a worker thread */ @Override protected D doInBackground(Void... params) { - if (DEBUG) Slog.v(TAG, this + " >>> doInBackground"); + if (DEBUG) Log.v(TAG, this + " >>> doInBackground"); try { D data = AsyncTaskLoader.this.onLoadInBackground(); - if (DEBUG) Slog.v(TAG, this + " <<< doInBackground"); + if (DEBUG) Log.v(TAG, this + " <<< doInBackground"); return data; } catch (OperationCanceledException ex) { if (!isCancelled()) { @@ -79,7 +79,7 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> { // So we treat this case as an unhandled exception. throw ex; } - if (DEBUG) Slog.v(TAG, this + " <<< doInBackground (was canceled)", ex); + if (DEBUG) Log.v(TAG, this + " <<< doInBackground (was canceled)", ex); return null; } } @@ -87,7 +87,7 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> { /* Runs on the UI thread */ @Override protected void onPostExecute(D data) { - if (DEBUG) Slog.v(TAG, this + " onPostExecute"); + if (DEBUG) Log.v(TAG, this + " onPostExecute"); try { AsyncTaskLoader.this.dispatchOnLoadComplete(this, data); } finally { @@ -98,7 +98,7 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> { /* Runs on the UI thread */ @Override protected void onCancelled(D data) { - if (DEBUG) Slog.v(TAG, this + " onCancelled"); + if (DEBUG) Log.v(TAG, this + " onCancelled"); try { AsyncTaskLoader.this.dispatchOnCancelled(this, data); } finally { @@ -162,18 +162,18 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> { super.onForceLoad(); cancelLoad(); mTask = new LoadTask(); - if (DEBUG) Slog.v(TAG, "Preparing load: mTask=" + mTask); + if (DEBUG) Log.v(TAG, "Preparing load: mTask=" + mTask); executePendingTask(); } @Override protected boolean onCancelLoad() { - if (DEBUG) Slog.v(TAG, "onCancelLoad: mTask=" + mTask); + if (DEBUG) Log.v(TAG, "onCancelLoad: mTask=" + mTask); if (mTask != null) { if (mCancellingTask != null) { // There was a pending task already waiting for a previous // one being canceled; just drop it. - if (DEBUG) Slog.v(TAG, + if (DEBUG) Log.v(TAG, "cancelLoad: still waiting for cancelled task; dropping next"); if (mTask.waiting) { mTask.waiting = false; @@ -184,14 +184,14 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> { } else if (mTask.waiting) { // There is a task, but it is waiting for the time it should // execute. We can just toss it. - if (DEBUG) Slog.v(TAG, "cancelLoad: task is waiting, dropping it"); + if (DEBUG) Log.v(TAG, "cancelLoad: task is waiting, dropping it"); mTask.waiting = false; mHandler.removeCallbacks(mTask); mTask = null; return false; } else { boolean cancelled = mTask.cancel(false); - if (DEBUG) Slog.v(TAG, "cancelLoad: cancelled=" + cancelled); + if (DEBUG) Log.v(TAG, "cancelLoad: cancelled=" + cancelled); if (cancelled) { mCancellingTask = mTask; cancelLoadInBackground(); @@ -223,7 +223,7 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> { long now = SystemClock.uptimeMillis(); if (now < (mLastLoadCompleteTime+mUpdateThrottle)) { // Not yet time to do another load. - if (DEBUG) Slog.v(TAG, "Waiting until " + if (DEBUG) Log.v(TAG, "Waiting until " + (mLastLoadCompleteTime+mUpdateThrottle) + " to execute: " + mTask); mTask.waiting = true; @@ -231,7 +231,7 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> { return; } } - if (DEBUG) Slog.v(TAG, "Executing: " + mTask); + if (DEBUG) Log.v(TAG, "Executing: " + mTask); mTask.executeOnExecutor(mExecutor, (Void[]) null); } } @@ -239,11 +239,11 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> { void dispatchOnCancelled(LoadTask task, D data) { onCanceled(data); if (mCancellingTask == task) { - if (DEBUG) Slog.v(TAG, "Cancelled task is now canceled!"); + if (DEBUG) Log.v(TAG, "Cancelled task is now canceled!"); rollbackContentChanged(); mLastLoadCompleteTime = SystemClock.uptimeMillis(); mCancellingTask = null; - if (DEBUG) Slog.v(TAG, "Delivering cancellation"); + if (DEBUG) Log.v(TAG, "Delivering cancellation"); deliverCancellation(); executePendingTask(); } @@ -251,7 +251,7 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> { void dispatchOnLoadComplete(LoadTask task, D data) { if (mTask != task) { - if (DEBUG) Slog.v(TAG, "Load complete of old task, trying to cancel"); + if (DEBUG) Log.v(TAG, "Load complete of old task, trying to cancel"); dispatchOnCancelled(task, data); } else { if (isAbandoned()) { @@ -261,7 +261,7 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> { commitContentChanged(); mLastLoadCompleteTime = SystemClock.uptimeMillis(); mTask = null; - if (DEBUG) Slog.v(TAG, "Delivering result"); + if (DEBUG) Log.v(TAG, "Delivering result"); deliverResult(data); } } diff --git a/core/java/android/content/ClipboardManager.java b/core/java/android/content/ClipboardManager.java index 73e6fd0..5653cad 100644 --- a/core/java/android/content/ClipboardManager.java +++ b/core/java/android/content/ClipboardManager.java @@ -22,8 +22,6 @@ import android.os.RemoteException; import android.os.Handler; import android.os.IBinder; import android.os.ServiceManager; -import android.os.StrictMode; -import android.util.Log; import java.util.ArrayList; diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 2bf4d7d..f3c803d 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -141,7 +141,7 @@ public abstract class ContentResolver { public static final String SYNC_EXTRAS_PRIORITY = "sync_priority"; /** {@hide} Flag to allow sync to occur on metered network. */ - public static final String SYNC_EXTRAS_DISALLOW_METERED = "disallow_metered"; + public static final String SYNC_EXTRAS_DISALLOW_METERED = "allow_metered"; /** * Set by the SyncManager to request that the SyncAdapter initialize itself for @@ -192,6 +192,14 @@ public abstract class ContentResolver { */ public static final String CURSOR_DIR_BASE_TYPE = "vnd.android.cursor.dir"; + /** + * This is the Android platform's generic MIME type to match any MIME + * type of the form "{@link #CURSOR_ITEM_BASE_TYPE}/{@code SUB_TYPE}". + * {@code SUB_TYPE} is the sub-type of the application-dependent + * content, e.g., "audio", "video", "playlist". + */ + public static final String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*"; + /** @hide */ public static final int SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS = 1; /** @hide */ @@ -368,9 +376,7 @@ public abstract class ContentResolver { } /** - * <p> * Query the given URI, returning a {@link Cursor} over the result set. - * </p> * <p> * For best performance, the caller should follow these guidelines: * <ul> @@ -405,9 +411,8 @@ public abstract class ContentResolver { } /** - * <p> - * Query the given URI, returning a {@link Cursor} over the result set. - * </p> + * Query the given URI, returning a {@link Cursor} over the result set + * with optional support for cancellation. * <p> * For best performance, the caller should follow these guidelines: * <ul> @@ -1751,7 +1756,7 @@ public abstract class ContentResolver { new SyncRequest.Builder() .setSyncAdapter(account, authority) .setExtras(extras) - .syncOnce() + .syncOnce() // Immediate sync. .build(); requestSync(request); } @@ -1759,9 +1764,6 @@ public abstract class ContentResolver { /** * Register a sync with the SyncManager. These requests are built using the * {@link SyncRequest.Builder}. - * - * @param request The immutable SyncRequest object containing the sync parameters. Use - * {@link SyncRequest.Builder} to construct these. */ public static void requestSync(SyncRequest request) { try { @@ -1829,8 +1831,21 @@ public abstract class ContentResolver { */ public static void cancelSync(Account account, String authority) { try { - getContentService().cancelSync(account, authority); + getContentService().cancelSync(account, authority, null); + } catch (RemoteException e) { + } + } + + /** + * Cancel any active or pending syncs that are running on this service. + * + * @param cname the service for which to cancel all active/pending operations. + */ + public static void cancelSync(ComponentName cname) { + try { + getContentService().cancelSync(null, null, cname); } catch (RemoteException e) { + } } @@ -1897,12 +1912,13 @@ public abstract class ContentResolver { * {@link #SYNC_EXTRAS_INITIALIZE}, {@link #SYNC_EXTRAS_FORCE}, * {@link #SYNC_EXTRAS_EXPEDITED}, {@link #SYNC_EXTRAS_MANUAL} set to true. * If any are supplied then an {@link IllegalArgumentException} will be thrown. - * <p>As of API level 19 this function introduces a default flexibility of ~4% (up to a maximum - * of one hour in the day) into the requested period. Use - * {@link SyncRequest.Builder#syncPeriodic(long, long)} to set this flexibility manually. * * <p>This method requires the caller to hold the permission * {@link android.Manifest.permission#WRITE_SYNC_SETTINGS}. + * <p>The bundle for a periodic sync can be queried by applications with the correct + * permissions using + * {@link ContentResolver#getPeriodicSyncs(Account account, String provider)}, so no + * sensitive data should be transferred here. * * @param account the account to specify in the sync * @param authority the provider to specify in the sync request @@ -1932,6 +1948,26 @@ public abstract class ContentResolver { } /** + * {@hide} + * Helper function to throw an <code>IllegalArgumentException</code> if any illegal + * extras were set for a periodic sync. + * + * @param extras bundle to validate. + */ + public static boolean invalidPeriodicExtras(Bundle extras) { + if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false) + || extras.getBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, false) + || extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false) + || extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false) + || extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false) + || extras.getBoolean(ContentResolver.SYNC_EXTRAS_FORCE, false) + || extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false)) { + return true; + } + return false; + } + + /** * Remove a periodic sync. Has no affect if account, authority and extras don't match * an existing periodic sync. * <p>This method requires the caller to hold the permission @@ -1951,6 +1987,31 @@ public abstract class ContentResolver { } /** + * Remove the specified sync. This will cancel any pending or active syncs. If the request is + * for a periodic sync, this call will remove any future occurrences. + * <p>If a periodic sync is specified, the caller must hold the permission + * {@link android.Manifest.permission#WRITE_SYNC_SETTINGS}. If this SyncRequest targets a + * SyncService adapter,the calling application must be signed with the same certificate as the + * adapter. + *</p>It is possible to cancel a sync using a SyncRequest object that is not the same object + * with which you requested the sync. Do so by building a SyncRequest with the same + * service/adapter, frequency, <b>and</b> extras bundle. + * + * @param request SyncRequest object containing information about sync to cancel. + */ + public static void cancelSync(SyncRequest request) { + if (request == null) { + throw new IllegalArgumentException("request cannot be null"); + } + try { + getContentService().cancelRequest(request); + } catch (RemoteException e) { + // exception ignored; if this is thrown then it means the runtime is in the midst of + // being restarted + } + } + + /** * Get the list of information about the periodic syncs for the given account and authority. * <p>This method requires the caller to hold the permission * {@link android.Manifest.permission#READ_SYNC_SETTINGS}. @@ -1961,7 +2022,23 @@ public abstract class ContentResolver { */ public static List<PeriodicSync> getPeriodicSyncs(Account account, String authority) { try { - return getContentService().getPeriodicSyncs(account, authority); + return getContentService().getPeriodicSyncs(account, authority, null); + } catch (RemoteException e) { + throw new RuntimeException("the ContentService should always be reachable", e); + } + } + + /** + * Return periodic syncs associated with the provided component. + * <p>The calling application must be signed with the same certificate as the target component, + * otherwise this call will fail. + */ + public static List<PeriodicSync> getPeriodicSyncs(ComponentName cname) { + if (cname == null) { + throw new IllegalArgumentException("Component must not be null"); + } + try { + return getContentService().getPeriodicSyncs(null, null, cname); } catch (RemoteException e) { throw new RuntimeException("the ContentService should always be reachable", e); } @@ -1997,6 +2074,38 @@ public abstract class ContentResolver { } /** + * Set whether the provided {@link SyncService} is available to process work. + * <p>This method requires the caller to hold the permission + * {@link android.Manifest.permission#WRITE_SYNC_SETTINGS}. + * <p>The calling application must be signed with the same certificate as the target component, + * otherwise this call will fail. + */ + public static void setServiceActive(ComponentName cname, boolean active) { + try { + getContentService().setServiceActive(cname, active); + } catch (RemoteException e) { + // exception ignored; if this is thrown then it means the runtime is in the midst of + // being restarted + } + } + + /** + * Query the state of this sync service. + * <p>Set with {@link #setServiceActive(ComponentName cname, boolean active)}. + * <p>The calling application must be signed with the same certificate as the target component, + * otherwise this call will fail. + * @param cname ComponentName referring to a {@link SyncService} + * @return true if jobs will be run on this service, false otherwise. + */ + public static boolean isServiceActive(ComponentName cname) { + try { + return getContentService().isServiceActive(cname); + } catch (RemoteException e) { + throw new RuntimeException("the ContentService should always be reachable", e); + } + } + + /** * Gets the master auto-sync setting that applies to all the providers and accounts. * If this is false then the per-provider auto-sync setting is ignored. * <p>This method requires the caller to hold the permission @@ -2030,8 +2139,8 @@ public abstract class ContentResolver { } /** - * Returns true if there is currently a sync operation for the given - * account or authority in the pending list, or actively being processed. + * Returns true if there is currently a sync operation for the given account or authority + * actively being processed. * <p>This method requires the caller to hold the permission * {@link android.Manifest.permission#READ_SYNC_STATS}. * @param account the account whose setting we are querying @@ -2039,8 +2148,26 @@ public abstract class ContentResolver { * @return true if a sync is active for the given account or authority. */ public static boolean isSyncActive(Account account, String authority) { + if (account == null) { + throw new IllegalArgumentException("account must not be null"); + } + if (authority == null) { + throw new IllegalArgumentException("authority must not be null"); + } + try { - return getContentService().isSyncActive(account, authority); + return getContentService().isSyncActive(account, authority, null); + } catch (RemoteException e) { + throw new RuntimeException("the ContentService should always be reachable", e); + } + } + + public static boolean isSyncActive(ComponentName cname) { + if (cname == null) { + throw new IllegalArgumentException("component name must not be null"); + } + try { + return getContentService().isSyncActive(null, null, cname); } catch (RemoteException e) { throw new RuntimeException("the ContentService should always be reachable", e); } @@ -2098,7 +2225,7 @@ public abstract class ContentResolver { */ public static SyncStatusInfo getSyncStatus(Account account, String authority) { try { - return getContentService().getSyncStatus(account, authority); + return getContentService().getSyncStatus(account, authority, null); } catch (RemoteException e) { throw new RuntimeException("the ContentService should always be reachable", e); } @@ -2114,7 +2241,15 @@ public abstract class ContentResolver { */ public static boolean isSyncPending(Account account, String authority) { try { - return getContentService().isSyncPending(account, authority); + return getContentService().isSyncPending(account, authority, null); + } catch (RemoteException e) { + throw new RuntimeException("the ContentService should always be reachable", e); + } + } + + public static boolean isSyncPending(ComponentName cname) { + try { + return getContentService().isSyncPending(null, null, cname); } catch (RemoteException e) { throw new RuntimeException("the ContentService should always be reachable", e); } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 2e4e209..81a886a 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -16,6 +16,10 @@ package android.content; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.StringDef; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.AssetManager; @@ -47,6 +51,8 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * Interface to global information about an application environment. This is @@ -132,6 +138,20 @@ public abstract class Context { */ public static final int MODE_ENABLE_WRITE_AHEAD_LOGGING = 0x0008; + /** @hide */ + @IntDef(flag = true, + value = { + BIND_AUTO_CREATE, + BIND_AUTO_CREATE, + BIND_DEBUG_UNBIND, + BIND_NOT_FOREGROUND, + BIND_ABOVE_CLIENT, + BIND_ALLOW_OOM_MANAGEMENT, + BIND_WAIVE_PRIORITY + }) + @Retention(RetentionPolicy.SOURCE) + public @interface BindServiceFlags {} + /** * Flag for {@link #bindService}: automatically create the service as long * as the binding exists. Note that while this will create the service, @@ -356,6 +376,19 @@ public abstract class Context { return getResources().getString(resId, formatArgs); } + /** + * Return a drawable object associated with a particular resource ID and + * styled for the current theme. + * + * @param id The desired resource identifier, as generated by the aapt + * tool. This integer encodes the package, type, and resource + * entry. The value 0 is an invalid identifier. + * @return Drawable An object that can be used to draw this resource. + */ + public final Drawable getDrawable(int id) { + return getResources().getDrawable(id, getTheme()); + } + /** * Set the base theme for this context. Note that this should be called * before any views are instantiated in the Context (for example before @@ -495,7 +528,7 @@ public abstract class Context { * and {@link #MODE_WORLD_WRITEABLE} to control permissions. The bit * {@link #MODE_MULTI_PROCESS} can also be used if multiple processes * are mutating the same SharedPreferences file. {@link #MODE_MULTI_PROCESS} - * is always on in apps targetting Gingerbread (Android 2.3) and below, and + * is always on in apps targeting Gingerbread (Android 2.3) and below, and * off by default in later versions. * * @return The single {@link SharedPreferences} instance that can be used @@ -674,7 +707,8 @@ public abstract class Context { * @see #getFilesDir * @see android.os.Environment#getExternalStoragePublicDirectory */ - public abstract File getExternalFilesDir(String type); + @Nullable + public abstract File getExternalFilesDir(@Nullable String type); /** * Returns absolute paths to application-specific directories on all @@ -707,7 +741,7 @@ public abstract class Context { * Returned paths may be {@code null} if a storage device is unavailable. * * @see #getExternalFilesDir(String) - * @see Environment#getStorageState(File) + * @see Environment#getExternalStorageState(File) */ public abstract File[] getExternalFilesDirs(String type); @@ -771,7 +805,7 @@ public abstract class Context { * Returned paths may be {@code null} if a storage device is unavailable. * * @see #getObbDir() - * @see Environment#getStorageState(File) + * @see Environment#getExternalStorageState(File) */ public abstract File[] getObbDirs(); @@ -840,6 +874,7 @@ public abstract class Context { * * @see #getCacheDir */ + @Nullable public abstract File getExternalCacheDir(); /** @@ -873,7 +908,7 @@ public abstract class Context { * Returned paths may be {@code null} if a storage device is unavailable. * * @see #getExternalCacheDir() - * @see Environment#getStorageState(File) + * @see Environment#getExternalStorageState(File) */ public abstract File[] getExternalCacheDirs(); @@ -960,7 +995,8 @@ public abstract class Context { * @see #deleteDatabase */ public abstract SQLiteDatabase openOrCreateDatabase(String name, - int mode, CursorFactory factory, DatabaseErrorHandler errorHandler); + int mode, CursorFactory factory, + @Nullable DatabaseErrorHandler errorHandler); /** * Delete an existing private SQLiteDatabase associated with this Context's @@ -1106,7 +1142,7 @@ public abstract class Context { * @see #startActivity(Intent) * @see PackageManager#resolveActivity */ - public abstract void startActivity(Intent intent, Bundle options); + public abstract void startActivity(Intent intent, @Nullable Bundle options); /** * Version of {@link #startActivity(Intent, Bundle)} that allows you to specify the @@ -1122,7 +1158,7 @@ public abstract class Context { * @throws ActivityNotFoundException * @hide */ - public void startActivityAsUser(Intent intent, Bundle options, UserHandle userId) { + public void startActivityAsUser(Intent intent, @Nullable Bundle options, UserHandle userId) { throw new RuntimeException("Not implemented. Must override in a subclass."); } @@ -1241,7 +1277,7 @@ public abstract class Context { * @see #startIntentSender(IntentSender, Intent, int, int, int) */ public abstract void startIntentSender(IntentSender intent, - Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, + @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) throws IntentSender.SendIntentException; /** @@ -1291,11 +1327,11 @@ public abstract class Context { * @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle) */ public abstract void sendBroadcast(Intent intent, - String receiverPermission); + @Nullable String receiverPermission); /** * Like {@link #sendBroadcast(Intent, String)}, but also allows specification - * of an assocated app op as per {@link android.app.AppOpsManager}. + * of an associated app op as per {@link android.app.AppOpsManager}. * @hide */ public abstract void sendBroadcast(Intent intent, @@ -1322,7 +1358,7 @@ public abstract class Context { * @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle) */ public abstract void sendOrderedBroadcast(Intent intent, - String receiverPermission); + @Nullable String receiverPermission); /** * Version of {@link #sendBroadcast(Intent)} that allows you to @@ -1366,15 +1402,15 @@ public abstract class Context { * @see #registerReceiver * @see android.app.Activity#RESULT_OK */ - public abstract void sendOrderedBroadcast(Intent intent, - String receiverPermission, BroadcastReceiver resultReceiver, - Handler scheduler, int initialCode, String initialData, - Bundle initialExtras); + public abstract void sendOrderedBroadcast(@NonNull Intent intent, + @Nullable String receiverPermission, BroadcastReceiver resultReceiver, + @Nullable Handler scheduler, int initialCode, @Nullable String initialData, + @Nullable Bundle initialExtras); /** * Like {@link #sendOrderedBroadcast(Intent, String, BroadcastReceiver, android.os.Handler, * int, String, android.os.Bundle)}, but also allows specification - * of an assocated app op as per {@link android.app.AppOpsManager}. + * of an associated app op as per {@link android.app.AppOpsManager}. * @hide */ public abstract void sendOrderedBroadcast(Intent intent, @@ -1409,7 +1445,7 @@ public abstract class Context { * @see #sendBroadcast(Intent, String) */ public abstract void sendBroadcastAsUser(Intent intent, UserHandle user, - String receiverPermission); + @Nullable String receiverPermission); /** * Version of @@ -1442,8 +1478,9 @@ public abstract class Context { * @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle) */ public abstract void sendOrderedBroadcastAsUser(Intent intent, UserHandle user, - String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler, - int initialCode, String initialData, Bundle initialExtras); + @Nullable String receiverPermission, BroadcastReceiver resultReceiver, + @Nullable Handler scheduler, int initialCode, @Nullable String initialData, + @Nullable Bundle initialExtras); /** * Perform a {@link #sendBroadcast(Intent)} that is "sticky," meaning the @@ -1508,8 +1545,8 @@ public abstract class Context { */ public abstract void sendStickyOrderedBroadcast(Intent intent, BroadcastReceiver resultReceiver, - Handler scheduler, int initialCode, String initialData, - Bundle initialExtras); + @Nullable Handler scheduler, int initialCode, @Nullable String initialData, + @Nullable Bundle initialExtras); /** * Remove the data previously sent with {@link #sendStickyBroadcast}, @@ -1569,8 +1606,8 @@ public abstract class Context { */ public abstract void sendStickyOrderedBroadcastAsUser(Intent intent, UserHandle user, BroadcastReceiver resultReceiver, - Handler scheduler, int initialCode, String initialData, - Bundle initialExtras); + @Nullable Handler scheduler, int initialCode, @Nullable String initialData, + @Nullable Bundle initialExtras); /** * Version of {@link #removeStickyBroadcast(Intent)} that allows you to specify the @@ -1637,7 +1674,8 @@ public abstract class Context { * @see #sendBroadcast * @see #unregisterReceiver */ - public abstract Intent registerReceiver(BroadcastReceiver receiver, + @Nullable + public abstract Intent registerReceiver(@Nullable BroadcastReceiver receiver, IntentFilter filter); /** @@ -1671,8 +1709,10 @@ public abstract class Context { * @see #sendBroadcast * @see #unregisterReceiver */ + @Nullable public abstract Intent registerReceiver(BroadcastReceiver receiver, - IntentFilter filter, String broadcastPermission, Handler scheduler); + IntentFilter filter, @Nullable String broadcastPermission, + @Nullable Handler scheduler); /** * @hide @@ -1698,9 +1738,10 @@ public abstract class Context { * @see #sendBroadcast * @see #unregisterReceiver */ + @Nullable public abstract Intent registerReceiverAsUser(BroadcastReceiver receiver, - UserHandle user, IntentFilter filter, String broadcastPermission, - Handler scheduler); + UserHandle user, IntentFilter filter, @Nullable String broadcastPermission, + @Nullable Handler scheduler); /** * Unregister a previously registered BroadcastReceiver. <em>All</em> @@ -1759,6 +1800,7 @@ public abstract class Context { * @see #stopService * @see #bindService */ + @Nullable public abstract ComponentName startService(Intent service); /** @@ -1798,7 +1840,7 @@ public abstract class Context { * @hide like {@link #stopService(Intent)} but for a specific user. */ public abstract boolean stopServiceAsUser(Intent service, UserHandle user); - + /** * Connect to an application service, creating it if needed. This defines * a dependency between your application and the service. The given @@ -1846,8 +1888,8 @@ public abstract class Context { * @see #BIND_DEBUG_UNBIND * @see #BIND_NOT_FOREGROUND */ - public abstract boolean bindService(Intent service, ServiceConnection conn, - int flags); + public abstract boolean bindService(Intent service, @NonNull ServiceConnection conn, + @BindServiceFlags int flags); /** * Same as {@link #bindService(Intent, ServiceConnection, int)}, but with an explicit userHandle @@ -1868,7 +1910,7 @@ public abstract class Context { * * @see #bindService */ - public abstract void unbindService(ServiceConnection conn); + public abstract void unbindService(@NonNull ServiceConnection conn); /** * Start executing an {@link android.app.Instrumentation} class. The given @@ -1893,8 +1935,65 @@ public abstract class Context { * @return {@code true} if the instrumentation was successfully started, * else {@code false} if it could not be found. */ - public abstract boolean startInstrumentation(ComponentName className, - String profileFile, Bundle arguments); + public abstract boolean startInstrumentation(@NonNull ComponentName className, + @Nullable String profileFile, @Nullable Bundle arguments); + + /** @hide */ + @StringDef({ + POWER_SERVICE, + WINDOW_SERVICE, + LAYOUT_INFLATER_SERVICE, + ACCOUNT_SERVICE, + ACTIVITY_SERVICE, + ALARM_SERVICE, + NOTIFICATION_SERVICE, + ACCESSIBILITY_SERVICE, + CAPTIONING_SERVICE, + KEYGUARD_SERVICE, + LOCATION_SERVICE, + //@hide: COUNTRY_DETECTOR, + SEARCH_SERVICE, + SENSOR_SERVICE, + STORAGE_SERVICE, + WALLPAPER_SERVICE, + VIBRATOR_SERVICE, + //@hide: STATUS_BAR_SERVICE, + CONNECTIVITY_SERVICE, + //@hide: UPDATE_LOCK_SERVICE, + //@hide: NETWORKMANAGEMENT_SERVICE, + //@hide: NETWORK_STATS_SERVICE, + //@hide: NETWORK_POLICY_SERVICE, + WIFI_SERVICE, + WIFI_P2P_SERVICE, + NSD_SERVICE, + AUDIO_SERVICE, + MEDIA_ROUTER_SERVICE, + TELEPHONY_SERVICE, + CLIPBOARD_SERVICE, + INPUT_METHOD_SERVICE, + TEXT_SERVICES_MANAGER_SERVICE, + //@hide: APPWIDGET_SERVICE, + //@hide: BACKUP_SERVICE, + DROPBOX_SERVICE, + DEVICE_POLICY_SERVICE, + UI_MODE_SERVICE, + DOWNLOAD_SERVICE, + NFC_SERVICE, + BLUETOOTH_SERVICE, + //@hide: SIP_SERVICE, + USB_SERVICE, + //@hide: SERIAL_SERVICE, + INPUT_SERVICE, + DISPLAY_SERVICE, + //@hide: SCHEDULING_POLICY_SERVICE, + USER_SERVICE, + //@hide: APP_OPS_SERVICE + CAMERA_SERVICE, + PRINT_SERVICE, + MEDIA_SESSION_SERVICE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ServiceName {} /** * Return the handle to a system-level service by name. The class of the @@ -1995,7 +2094,7 @@ public abstract class Context { * @see #DOWNLOAD_SERVICE * @see android.app.DownloadManager */ - public abstract Object getSystemService(String name); + public abstract Object getSystemService(@ServiceName @NonNull String name); /** * Use with {@link #getSystemService} to retrieve a @@ -2253,6 +2352,15 @@ public abstract class Context { /** * Use with {@link #getSystemService} to retrieve a + * {@link android.media.session.MediaSessionManager} for managing media Sessions. + * + * @see #getSystemService + * @see android.media.session.MediaSessionManager + */ + public static final String MEDIA_SESSION_SERVICE = "media_session"; + + /** + * Use with {@link #getSystemService} to retrieve a * {@link android.telephony.TelephonyManager} for handling management the * telephony features of the device. * @@ -2431,7 +2539,6 @@ public abstract class Context { * * @see #getSystemService * @see android.hardware.camera2.CameraManager - * @hide */ public static final String CAMERA_SERVICE = "camera"; @@ -2470,7 +2577,8 @@ public abstract class Context { * @see PackageManager#checkPermission(String, String) * @see #checkCallingPermission */ - public abstract int checkPermission(String permission, int pid, int uid); + @PackageManager.PermissionResult + public abstract int checkPermission(@NonNull String permission, int pid, int uid); /** * Determine whether the calling process of an IPC you are handling has been @@ -2493,7 +2601,8 @@ public abstract class Context { * @see #checkPermission * @see #checkCallingOrSelfPermission */ - public abstract int checkCallingPermission(String permission); + @PackageManager.PermissionResult + public abstract int checkCallingPermission(@NonNull String permission); /** * Determine whether the calling process of an IPC <em>or you</em> have been @@ -2511,7 +2620,8 @@ public abstract class Context { * @see #checkPermission * @see #checkCallingPermission */ - public abstract int checkCallingOrSelfPermission(String permission); + @PackageManager.PermissionResult + public abstract int checkCallingOrSelfPermission(@NonNull String permission); /** * If the given permission is not allowed for a particular process @@ -2526,7 +2636,7 @@ public abstract class Context { * @see #checkPermission(String, int, int) */ public abstract void enforcePermission( - String permission, int pid, int uid, String message); + @NonNull String permission, int pid, int uid, @Nullable String message); /** * If the calling process of an IPC you are handling has not been @@ -2547,7 +2657,7 @@ public abstract class Context { * @see #checkCallingPermission(String) */ public abstract void enforceCallingPermission( - String permission, String message); + @NonNull String permission, @Nullable String message); /** * If neither you nor the calling process of an IPC you are @@ -2563,7 +2673,7 @@ public abstract class Context { * @see #checkCallingOrSelfPermission(String) */ public abstract void enforceCallingOrSelfPermission( - String permission, String message); + @NonNull String permission, @Nullable String message); /** * Grant permission to access a specific Uri to another package, regardless @@ -2599,7 +2709,7 @@ public abstract class Context { * @see #revokeUriPermission */ public abstract void grantUriPermission(String toPackage, Uri uri, - int modeFlags); + @Intent.GrantUriMode int modeFlags); /** * Remove all permissions to access a particular content provider Uri @@ -2618,7 +2728,7 @@ public abstract class Context { * * @see #grantUriPermission */ - public abstract void revokeUriPermission(Uri uri, int modeFlags); + public abstract void revokeUriPermission(Uri uri, @Intent.GrantUriMode int modeFlags); /** * Determine whether a particular process and user ID has been granted @@ -2641,7 +2751,8 @@ public abstract class Context { * * @see #checkCallingUriPermission */ - public abstract int checkUriPermission(Uri uri, int pid, int uid, int modeFlags); + public abstract int checkUriPermission(Uri uri, int pid, int uid, + @Intent.GrantUriMode int modeFlags); /** * Determine whether the calling process and user ID has been @@ -2664,7 +2775,7 @@ public abstract class Context { * * @see #checkUriPermission(Uri, int, int, int) */ - public abstract int checkCallingUriPermission(Uri uri, int modeFlags); + public abstract int checkCallingUriPermission(Uri uri, @Intent.GrantUriMode int modeFlags); /** * Determine whether the calling process of an IPC <em>or you</em> has been granted @@ -2683,7 +2794,8 @@ public abstract class Context { * * @see #checkCallingUriPermission */ - public abstract int checkCallingOrSelfUriPermission(Uri uri, int modeFlags); + public abstract int checkCallingOrSelfUriPermission(Uri uri, + @Intent.GrantUriMode int modeFlags); /** * Check both a Uri and normal permission. This allows you to perform @@ -2695,7 +2807,7 @@ public abstract class Context { * @param readPermission The permission that provides overall read access, * or null to not do this check. * @param writePermission The permission that provides overall write - * acess, or null to not do this check. + * access, or null to not do this check. * @param pid The process ID being checked against. Must be > 0. * @param uid The user ID being checked against. A uid of 0 is the root * user, which will pass every permission check. @@ -2707,8 +2819,9 @@ public abstract class Context { * is allowed to access that uri or holds one of the given permissions, or * {@link PackageManager#PERMISSION_DENIED} if it is not. */ - public abstract int checkUriPermission(Uri uri, String readPermission, - String writePermission, int pid, int uid, int modeFlags); + public abstract int checkUriPermission(@Nullable Uri uri, @Nullable String readPermission, + @Nullable String writePermission, int pid, int uid, + @Intent.GrantUriMode int modeFlags); /** * If a particular process and user ID has not been granted @@ -2730,7 +2843,7 @@ public abstract class Context { * @see #checkUriPermission(Uri, int, int, int) */ public abstract void enforceUriPermission( - Uri uri, int pid, int uid, int modeFlags, String message); + Uri uri, int pid, int uid, @Intent.GrantUriMode int modeFlags, String message); /** * If the calling process and user ID has not been granted @@ -2752,7 +2865,7 @@ public abstract class Context { * @see #checkCallingUriPermission(Uri, int) */ public abstract void enforceCallingUriPermission( - Uri uri, int modeFlags, String message); + Uri uri, @Intent.GrantUriMode int modeFlags, String message); /** * If the calling process of an IPC <em>or you</em> has not been @@ -2771,7 +2884,7 @@ public abstract class Context { * @see #checkCallingOrSelfUriPermission(Uri, int) */ public abstract void enforceCallingOrSelfUriPermission( - Uri uri, int modeFlags, String message); + Uri uri, @Intent.GrantUriMode int modeFlags, String message); /** * Enforce both a Uri and normal permission. This allows you to perform @@ -2783,7 +2896,7 @@ public abstract class Context { * @param readPermission The permission that provides overall read access, * or null to not do this check. * @param writePermission The permission that provides overall write - * acess, or null to not do this check. + * access, or null to not do this check. * @param pid The process ID being checked against. Must be > 0. * @param uid The user ID being checked against. A uid of 0 is the root * user, which will pass every permission check. @@ -2795,8 +2908,15 @@ public abstract class Context { * @see #checkUriPermission(Uri, String, String, int, int, int) */ public abstract void enforceUriPermission( - Uri uri, String readPermission, String writePermission, - int pid, int uid, int modeFlags, String message); + @Nullable Uri uri, @Nullable String readPermission, + @Nullable String writePermission, int pid, int uid, @Intent.GrantUriMode int modeFlags, + @Nullable String message); + + /** @hide */ + @IntDef(flag = true, + value = {CONTEXT_INCLUDE_CODE, CONTEXT_IGNORE_SECURITY, CONTEXT_RESTRICTED}) + @Retention(RetentionPolicy.SOURCE) + public @interface CreatePackageOptions {} /** * Flag for use with {@link #createPackageContext}: include the application @@ -2854,7 +2974,7 @@ public abstract class Context { * the given package name. */ public abstract Context createPackageContext(String packageName, - int flags) throws PackageManager.NameNotFoundException; + @CreatePackageOptions int flags) throws PackageManager.NameNotFoundException; /** * Similar to {@link #createPackageContext(String, int)}, but with a @@ -2890,7 +3010,8 @@ public abstract class Context { * * @return A {@link Context} with the given configuration override. */ - public abstract Context createConfigurationContext(Configuration overrideConfiguration); + public abstract Context createConfigurationContext( + @NonNull Configuration overrideConfiguration); /** * Return a new Context object for the current Context but whose resources @@ -2910,7 +3031,7 @@ public abstract class Context { * * @return A {@link Context} for the display. */ - public abstract Context createDisplayContext(Display display); + public abstract Context createDisplayContext(@NonNull Display display); /** * Gets the display adjustments holder for this context. This information diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index a708dad..93f6cdf 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -16,9 +16,6 @@ package android.content; -import android.app.Activity; -import android.app.ActivityManagerNative; -import android.app.LoadedApk; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.AssetManager; @@ -33,7 +30,6 @@ import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Looper; -import android.os.RemoteException; import android.os.UserHandle; import android.view.DisplayAdjustments; import android.view.Display; diff --git a/core/java/android/content/CursorLoader.java b/core/java/android/content/CursorLoader.java index 5d7d677..c78871c 100644 --- a/core/java/android/content/CursorLoader.java +++ b/core/java/android/content/CursorLoader.java @@ -16,7 +16,6 @@ package android.content; -import android.database.ContentObserver; import android.database.Cursor; import android.net.Uri; import android.os.CancellationSignal; diff --git a/core/java/android/content/Entity.java b/core/java/android/content/Entity.java index 7842de0..607cb3f 100644 --- a/core/java/android/content/Entity.java +++ b/core/java/android/content/Entity.java @@ -16,10 +16,7 @@ package android.content; -import android.os.Parcelable; -import android.os.Parcel; import android.net.Uri; -import android.util.Log; import java.util.ArrayList; diff --git a/core/java/android/content/IContentService.aidl b/core/java/android/content/IContentService.aidl index 9ad5a19..73a76e8 100644 --- a/core/java/android/content/IContentService.aidl +++ b/core/java/android/content/IContentService.aidl @@ -17,6 +17,7 @@ package android.content; import android.accounts.Account; +import android.content.ComponentName; import android.content.SyncInfo; import android.content.ISyncStatusObserver; import android.content.SyncAdapterType; @@ -55,8 +56,14 @@ interface IContentService { int userHandle); void requestSync(in Account account, String authority, in Bundle extras); + /** + * Start a sync given a request. + */ void sync(in SyncRequest request); - void cancelSync(in Account account, String authority); + void cancelSync(in Account account, String authority, in ComponentName cname); + + /** Cancel a sync, providing information about the sync to be cancelled. */ + void cancelRequest(in SyncRequest request); /** * Check if the provider should be synced when a network tickle is received @@ -74,12 +81,14 @@ interface IContentService { void setSyncAutomatically(in Account account, String providerName, boolean sync); /** - * Get the frequency of the periodic poll, if any. - * @param providerName the provider whose setting we are querying - * @return the frequency of the periodic sync in seconds. If 0 then no periodic syncs - * will take place. + * Get a list of periodic operations for a specified authority, or service. + * @param account account for authority, must be null if cname is non-null. + * @param providerName name of provider, must be null if cname is non-null. + * @param cname component to identify sync service, must be null if account/providerName are + * non-null. */ - List<PeriodicSync> getPeriodicSyncs(in Account account, String providerName); + List<PeriodicSync> getPeriodicSyncs(in Account account, String providerName, + in ComponentName cname); /** * Set whether or not the provider is to be synced on a periodic basis. @@ -112,15 +121,22 @@ interface IContentService { */ void setIsSyncable(in Account account, String providerName, int syncable); - void setMasterSyncAutomatically(boolean flag); - - boolean getMasterSyncAutomatically(); + /** + * Corresponds roughly to setIsSyncable(String account, String provider) for syncs that bind + * to a SyncService. + */ + void setServiceActive(in ComponentName cname, boolean active); /** - * Returns true if there is currently a sync operation for the given - * account or authority in the pending list, or actively being processed. + * Corresponds roughly to getIsSyncable(String account, String provider) for syncs that bind + * to a SyncService. + * @return 0 if this SyncService is not enabled, 1 if enabled, <0 if unknown. */ - boolean isSyncActive(in Account account, String authority); + boolean isServiceActive(in ComponentName cname); + + void setMasterSyncAutomatically(boolean flag); + + boolean getMasterSyncAutomatically(); List<SyncInfo> getCurrentSyncs(); @@ -131,17 +147,33 @@ interface IContentService { SyncAdapterType[] getSyncAdapterTypes(); /** + * Returns true if there is currently a operation for the given account/authority or service + * actively being processed. + * @param account account for authority, must be null if cname is non-null. + * @param providerName name of provider, must be null if cname is non-null. + * @param cname component to identify sync service, must be null if account/providerName are + * non-null. + */ + boolean isSyncActive(in Account account, String authority, in ComponentName cname); + + /** * Returns the status that matches the authority. If there are multiples accounts for * the authority, the one with the latest "lastSuccessTime" status is returned. - * @param authority the authority whose row should be selected - * @return the SyncStatusInfo for the authority, or null if none exists + * @param account account for authority, must be null if cname is non-null. + * @param providerName name of provider, must be null if cname is non-null. + * @param cname component to identify sync service, must be null if account/providerName are + * non-null. */ - SyncStatusInfo getSyncStatus(in Account account, String authority); + SyncStatusInfo getSyncStatus(in Account account, String authority, in ComponentName cname); /** * Return true if the pending status is true of any matching authorities. + * @param account account for authority, must be null if cname is non-null. + * @param providerName name of provider, must be null if cname is non-null. + * @param cname component to identify sync service, must be null if account/providerName are + * non-null. */ - boolean isSyncPending(in Account account, String authority); + boolean isSyncPending(in Account account, String authority, in ComponentName cname); void addStatusChangeListener(int mask, ISyncStatusObserver callback); diff --git a/core/java/android/content/IAnonymousSyncAdapter.aidl b/core/java/android/content/ISyncServiceAdapter.aidl index a80cea3..d419307 100644 --- a/core/java/android/content/IAnonymousSyncAdapter.aidl +++ b/core/java/android/content/ISyncServiceAdapter.aidl @@ -24,7 +24,7 @@ import android.content.ISyncContext; * Provider specified). See {@link android.content.AbstractThreadedSyncAdapter}. * {@hide} */ -oneway interface IAnonymousSyncAdapter { +oneway interface ISyncServiceAdapter { /** * Initiate a sync. SyncAdapter-specific parameters may be specified in diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 65e2268..96479e2 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -21,6 +21,7 @@ import android.util.ArraySet; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import android.annotation.IntDef; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.content.pm.ActivityInfo; @@ -45,6 +46,8 @@ import com.android.internal.util.XmlUtils; import java.io.IOException; import java.io.Serializable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; @@ -3350,6 +3353,12 @@ public class Intent implements Parcelable, Cloneable { // --------------------------------------------------------------------- // Intent flags (see mFlags variable). + /** @hide */ + @IntDef(flag = true, + value = {FLAG_GRANT_READ_URI_PERMISSION, FLAG_GRANT_WRITE_URI_PERMISSION}) + @Retention(RetentionPolicy.SOURCE) + public @interface GrantUriMode {} + /** * If set, the recipient of this Intent will be granted permission to * perform read operations on the URI in the Intent's data and any URIs @@ -6394,6 +6403,21 @@ public class Intent implements Parcelable, Cloneable { } } + /** @hide */ + @IntDef(flag = true, + value = { + FILL_IN_ACTION, + FILL_IN_DATA, + FILL_IN_CATEGORIES, + FILL_IN_COMPONENT, + FILL_IN_PACKAGE, + FILL_IN_SOURCE_BOUNDS, + FILL_IN_SELECTOR, + FILL_IN_CLIP_DATA + }) + @Retention(RetentionPolicy.SOURCE) + public @interface FillInFlags {} + /** * Use with {@link #fillIn} to allow the current action value to be * overwritten, even if it is already set. @@ -6487,10 +6511,12 @@ public class Intent implements Parcelable, Cloneable { * * @return Returns a bit mask of {@link #FILL_IN_ACTION}, * {@link #FILL_IN_DATA}, {@link #FILL_IN_CATEGORIES}, {@link #FILL_IN_PACKAGE}, - * {@link #FILL_IN_COMPONENT}, {@link #FILL_IN_SOURCE_BOUNDS}, and - * {@link #FILL_IN_SELECTOR} indicating which fields were changed. + * {@link #FILL_IN_COMPONENT}, {@link #FILL_IN_SOURCE_BOUNDS}, + * {@link #FILL_IN_SELECTOR} and {@link #FILL_IN_CLIP_DATA indicating which fields were + * changed. */ - public int fillIn(Intent other, int flags) { + @FillInFlags + public int fillIn(Intent other, @FillInFlags int flags) { int changes = 0; if (other.mAction != null && (mAction == null || (flags&FILL_IN_ACTION) != 0)) { diff --git a/core/java/android/content/Loader.java b/core/java/android/content/Loader.java index a045b3a..e9d82af 100644 --- a/core/java/android/content/Loader.java +++ b/core/java/android/content/Loader.java @@ -413,7 +413,7 @@ public class Loader<D> { * {@link #onReset()} happens. You can retrieve the current abandoned * state with {@link #isAbandoned}. */ - protected void onAbandon() { + protected void onAbandon() { } /** diff --git a/core/java/android/content/PeriodicSync.java b/core/java/android/content/PeriodicSync.java index b586eec..836c6f8 100644 --- a/core/java/android/content/PeriodicSync.java +++ b/core/java/android/content/PeriodicSync.java @@ -29,13 +29,17 @@ public class PeriodicSync implements Parcelable { public final Account account; /** The authority of the sync. Can be null. */ public final String authority; + /** The service for syncing, if this is an anonymous sync. Can be null.*/ + public final ComponentName service; /** Any extras that parameters that are to be passed to the sync adapter. */ public final Bundle extras; /** How frequently the sync should be scheduled, in seconds. Kept around for API purposes. */ public final long period; + /** Whether this periodic sync runs on a {@link SyncService}. */ + public final boolean isService; /** - * {@hide} * How much flexibility can be taken in scheduling the sync, in seconds. + * {@hide} */ public final long flexTime; @@ -48,44 +52,74 @@ public class PeriodicSync implements Parcelable { public PeriodicSync(Account account, String authority, Bundle extras, long periodInSeconds) { this.account = account; this.authority = authority; + this.service = null; + this.isService = false; if (extras == null) { this.extras = new Bundle(); } else { this.extras = new Bundle(extras); } this.period = periodInSeconds; - // Initialise to a sane value. + // Old API uses default flex time. No-one should be using this ctor anyway. this.flexTime = 0L; } /** - * {@hide} * Create a copy of a periodic sync. + * {@hide} */ public PeriodicSync(PeriodicSync other) { this.account = other.account; this.authority = other.authority; + this.service = other.service; + this.isService = other.isService; this.extras = new Bundle(other.extras); this.period = other.period; this.flexTime = other.flexTime; } /** - * {@hide} * A PeriodicSync for a sync with a specified provider. + * {@hide} */ public PeriodicSync(Account account, String authority, Bundle extras, long period, long flexTime) { this.account = account; this.authority = authority; + this.service = null; + this.isService = false; + this.extras = new Bundle(extras); + this.period = period; + this.flexTime = flexTime; + } + + /** + * A PeriodicSync for a sync with a specified SyncService. + * {@hide} + */ + public PeriodicSync(ComponentName service, Bundle extras, + long period, + long flexTime) { + this.account = null; + this.authority = null; + this.service = service; + this.isService = true; this.extras = new Bundle(extras); this.period = period; this.flexTime = flexTime; } private PeriodicSync(Parcel in) { - this.account = in.readParcelable(null); - this.authority = in.readString(); + this.isService = (in.readInt() != 0); + if (this.isService) { + this.service = in.readParcelable(null); + this.account = null; + this.authority = null; + } else { + this.account = in.readParcelable(null); + this.authority = in.readString(); + this.service = null; + } this.extras = in.readBundle(); this.period = in.readLong(); this.flexTime = in.readLong(); @@ -98,8 +132,13 @@ public class PeriodicSync implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeParcelable(account, flags); - dest.writeString(authority); + dest.writeInt(isService ? 1 : 0); + if (account == null && authority == null) { + dest.writeParcelable(service, flags); + } else { + dest.writeParcelable(account, flags); + dest.writeString(authority); + } dest.writeBundle(extras); dest.writeLong(period); dest.writeLong(flexTime); @@ -126,14 +165,24 @@ public class PeriodicSync implements Parcelable { return false; } final PeriodicSync other = (PeriodicSync) o; - return account.equals(other.account) - && authority.equals(other.authority) + if (this.isService != other.isService) { + return false; + } + boolean equal = false; + if (this.isService) { + equal = service.equals(other.service); + } else { + equal = account.equals(other.account) + && authority.equals(other.authority); + } + return equal && period == other.period && syncExtrasEquals(extras, other.extras); } /** - * Periodic sync extra comparison function. + * Periodic sync extra comparison function. Duplicated from + * {@link com.android.server.content.SyncManager#syncExtrasEquals(Bundle b1, Bundle b2)} * {@hide} */ public static boolean syncExtrasEquals(Bundle b1, Bundle b2) { @@ -158,6 +207,7 @@ public class PeriodicSync implements Parcelable { public String toString() { return "account: " + account + ", authority: " + authority + + ", service: " + service + ". period: " + period + "s " + ", flex: " + flexTime; } diff --git a/core/java/android/content/RestrictionEntry.java b/core/java/android/content/RestrictionEntry.java index 283a097..3ff53bf 100644 --- a/core/java/android/content/RestrictionEntry.java +++ b/core/java/android/content/RestrictionEntry.java @@ -19,8 +19,6 @@ package android.content; import android.os.Parcel; import android.os.Parcelable; -import java.lang.annotation.Inherited; - /** * Applications can expose restrictions for a restricted user on a * multiuser device. The administrator can configure these restrictions that will then be diff --git a/core/java/android/content/SyncActivityTooManyDeletes.java b/core/java/android/content/SyncActivityTooManyDeletes.java index 350f35e..093fb08 100644 --- a/core/java/android/content/SyncActivityTooManyDeletes.java +++ b/core/java/android/content/SyncActivityTooManyDeletes.java @@ -95,7 +95,7 @@ public class SyncActivityTooManyDeletes extends Activity // try { // final Context authContext = createPackageContext(desc.packageName, 0); // ImageView imageView = new ImageView(this); -// imageView.setImageDrawable(authContext.getResources().getDrawable(desc.iconId)); +// imageView.setImageDrawable(authContext.getDrawable(desc.iconId)); // ll.addView(imageView, lp); // } catch (PackageManager.NameNotFoundException e) { // } diff --git a/core/java/android/content/SyncInfo.java b/core/java/android/content/SyncInfo.java index cffc653..146dd99 100644 --- a/core/java/android/content/SyncInfo.java +++ b/core/java/android/content/SyncInfo.java @@ -19,7 +19,6 @@ package android.content; import android.accounts.Account; import android.os.Parcel; import android.os.Parcelable; -import android.os.Parcelable.Creator; /** * Information about the sync operation that is currently underway. @@ -29,16 +28,24 @@ public class SyncInfo implements Parcelable { public final int authorityId; /** - * The {@link Account} that is currently being synced. + * The {@link Account} that is currently being synced. Will be null if this sync is running via + * a {@link SyncService}. */ public final Account account; /** - * The authority of the provider that is currently being synced. + * The authority of the provider that is currently being synced. Will be null if this sync + * is running via a {@link SyncService}. */ public final String authority; /** + * The {@link SyncService} that is targeted by this operation. Null if this sync is running via + * a {@link AbstractThreadedSyncAdapter}. + */ + public final ComponentName service; + + /** * The start time of the current sync operation in milliseconds since boot. * This is represented in elapsed real time. * See {@link android.os.SystemClock#elapsedRealtime()}. @@ -46,12 +53,13 @@ public class SyncInfo implements Parcelable { public final long startTime; /** @hide */ - public SyncInfo(int authorityId, Account account, String authority, + public SyncInfo(int authorityId, Account account, String authority, ComponentName service, long startTime) { this.authorityId = authorityId; this.account = account; this.authority = authority; this.startTime = startTime; + this.service = service; } /** @hide */ @@ -60,6 +68,7 @@ public class SyncInfo implements Parcelable { this.account = new Account(other.account.name, other.account.type); this.authority = other.authority; this.startTime = other.startTime; + this.service = other.service; } /** @hide */ @@ -70,17 +79,20 @@ public class SyncInfo implements Parcelable { /** @hide */ public void writeToParcel(Parcel parcel, int flags) { parcel.writeInt(authorityId); - account.writeToParcel(parcel, 0); + parcel.writeParcelable(account, flags); parcel.writeString(authority); parcel.writeLong(startTime); + parcel.writeParcelable(service, flags); + } /** @hide */ SyncInfo(Parcel parcel) { authorityId = parcel.readInt(); - account = new Account(parcel); + account = parcel.readParcelable(Account.class.getClassLoader()); authority = parcel.readString(); startTime = parcel.readLong(); + service = parcel.readParcelable(ComponentName.class.getClassLoader()); } /** @hide */ diff --git a/core/java/android/content/SyncRequest.java b/core/java/android/content/SyncRequest.java index 6ca283d..a9a62a7 100644 --- a/core/java/android/content/SyncRequest.java +++ b/core/java/android/content/SyncRequest.java @@ -23,15 +23,15 @@ import android.os.Parcelable; public class SyncRequest implements Parcelable { private static final String TAG = "SyncRequest"; - /** Account to pass to the sync adapter. May be null. */ + /** Account to pass to the sync adapter. Can be null. */ private final Account mAccountToSync; /** Authority string that corresponds to a ContentProvider. */ private final String mAuthority; - /** Sync service identifier. May be null.*/ + /** {@link SyncService} identifier. */ private final ComponentName mComponentInfo; /** Bundle containing user info as well as sync settings. */ private final Bundle mExtras; - /** Disallow this sync request on metered networks. */ + /** Don't allow this sync request on metered networks. */ private final boolean mDisallowMetered; /** * Anticipated upload size in bytes. @@ -69,18 +69,14 @@ public class SyncRequest implements Parcelable { return mIsPeriodic; } - /** - * {@hide} - * @return whether this is an expedited sync. - */ public boolean isExpedited() { return mIsExpedited; } /** * {@hide} - * @return true if this sync uses an account/authority pair, or false if this sync is bound to - * a Sync Service. + * @return true if this sync uses an account/authority pair, or false if + * this is an anonymous sync bound to an @link AnonymousSyncService. */ public boolean hasAuthority() { return mIsAuthority; @@ -88,34 +84,51 @@ public class SyncRequest implements Parcelable { /** * {@hide} + * * @return account object for this sync. - * @throws IllegalArgumentException if this function is called for a request that does not - * specify an account/provider authority. + * @throws IllegalArgumentException if this function is called for a request that targets a + * sync service. */ public Account getAccount() { if (!hasAuthority()) { - throw new IllegalArgumentException("Cannot getAccount() for a sync that does not" - + "specify an authority."); + throw new IllegalArgumentException("Cannot getAccount() for a sync that targets a sync" + + "service."); } return mAccountToSync; } /** * {@hide} + * * @return provider for this sync. - * @throws IllegalArgumentException if this function is called for a request that does not - * specify an account/provider authority. + * @throws IllegalArgumentException if this function is called for a request that targets a + * sync service. */ public String getProvider() { if (!hasAuthority()) { - throw new IllegalArgumentException("Cannot getProvider() for a sync that does not" - + "specify a provider."); + throw new IllegalArgumentException("Cannot getProvider() for a sync that targets a" + + "sync service."); } return mAuthority; } /** * {@hide} + * Throws a runtime IllegalArgumentException if this function is called for a + * SyncRequest that is bound to an account/provider. + * + * @return ComponentName for the service that this sync will bind to. + */ + public ComponentName getService() { + if (hasAuthority()) { + throw new IllegalArgumentException( + "Cannot getAnonymousService() for a sync that has specified a provider."); + } + return mComponentInfo; + } + + /** + * {@hide} * Retrieve bundle for this SyncRequest. Will not be null. */ public Bundle getBundle() { @@ -129,7 +142,6 @@ public class SyncRequest implements Parcelable { public long getSyncFlexTime() { return mSyncFlexTimeSecs; } - /** * {@hide} * @return the last point in time at which this sync must scheduled. @@ -216,7 +228,7 @@ public class SyncRequest implements Parcelable { } /** - * Builder class for a {@link SyncRequest}. As you build your SyncRequest this class will also + * Builder class for a @link SyncRequest. As you build your SyncRequest this class will also * perform validation. */ public static class Builder { @@ -232,9 +244,12 @@ public class SyncRequest implements Parcelable { private static final int SYNC_TARGET_SERVICE = 1; /** Specify that this is a sync with a provider. */ private static final int SYNC_TARGET_ADAPTER = 2; - /** Earliest point of displacement into the future at which this sync can occur. */ + /** + * Earliest point of displacement into the future at which this sync can + * occur. + */ private long mSyncFlexTimeSecs; - /** Latest point of displacement into the future at which this sync must occur. */ + /** Displacement into the future at which this sync must occur. */ private long mSyncRunTimeSecs; /** * Sync configuration information - custom user data explicitly provided by the developer. @@ -283,8 +298,9 @@ public class SyncRequest implements Parcelable { private boolean mExpedited; /** - * The sync component that contains the sync logic if this is a provider-less sync, - * otherwise null. + * The {@link SyncService} component that + * contains the sync logic if this is a provider-less sync, otherwise + * null. */ private ComponentName mComponentName; /** @@ -320,11 +336,15 @@ public class SyncRequest implements Parcelable { /** * Build a periodic sync. Either this or syncOnce() <b>must</b> be called for this builder. - * Syncs are identified by target {@link android.provider}/{@link android.accounts.Account} - * and by the contents of the extras bundle. - * You cannot reuse the same builder for one-time syncs (by calling this function) after - * having specified a periodic sync. If you do, an <code>IllegalArgumentException</code> + * Syncs are identified by target {@link SyncService}/{@link android.provider} and by the + * contents of the extras bundle. + * You cannot reuse the same builder for one-time syncs after having specified a periodic + * sync (by calling this function). If you do, an <code>IllegalArgumentException</code> * will be thrown. + * <p>The bundle for a periodic sync can be queried by applications with the correct + * permissions using + * {@link ContentResolver#getPeriodicSyncs(Account account, String provider)}, so no + * sensitive data should be transferred here. * * Example usage. * @@ -375,7 +395,6 @@ public class SyncRequest implements Parcelable { } /** - * {@hide} * Developer can provide insight into their payload size; optional. -1 specifies unknown, * so that you are not restricted to defining both fields. * @@ -389,20 +408,28 @@ public class SyncRequest implements Parcelable { } /** - * @see android.net.ConnectivityManager#isActiveNetworkMetered() - * @param disallow true to enforce that this transfer not occur on metered networks. - * Default false. + * Will throw an <code>IllegalArgumentException</code> if called and + * {@link #setIgnoreSettings(boolean ignoreSettings)} has already been called. + * @param disallow true to allow this transfer on metered networks. Default false. + * */ public Builder setDisallowMetered(boolean disallow) { + if (mIgnoreSettings && disallow) { + throw new IllegalArgumentException("setDisallowMetered(true) after having" + + "specified that settings are ignored."); + } mDisallowMetered = disallow; return this; } /** - * Specify an authority and account for this transfer. + * Specify an authority and account for this transfer. Cannot be used with + * {@link #setSyncAdapter(ComponentName cname)}. * - * @param authority String identifying which content provider to sync. - * @param account Account to sync. Can be null unless this is a periodic sync. + * @param authority + * @param account Account to sync. Can be null unless this is a periodic + * sync, for which verification by the ContentResolver will + * fail. If a sync is performed without an account, the */ public Builder setSyncAdapter(Account account, String authority) { if (mSyncTarget != SYNC_TARGET_UNKNOWN) { @@ -419,10 +446,26 @@ public class SyncRequest implements Parcelable { } /** - * Optional developer-provided extras handed back in - * {@link AbstractThreadedSyncAdapter#onPerformSync(Account, Bundle, String, - * ContentProviderClient, SyncResult)} occurs. This bundle is copied into the SyncRequest - * returned by {@link #build()}. + * Specify the {@link SyncService} component for this sync. This is not validated until + * sync time so providing an incorrect component name here will not fail. Cannot be used + * with {@link #setSyncAdapter(Account account, String authority)}. + * + * @param cname ComponentName to identify your Anonymous service + */ + public Builder setSyncAdapter(ComponentName cname) { + if (mSyncTarget != SYNC_TARGET_UNKNOWN) { + throw new IllegalArgumentException("Sync target has already been defined."); + } + mSyncTarget = SYNC_TARGET_SERVICE; + mComponentName = cname; + mAccount = null; + mAuthority = null; + return this; + } + + /** + * Developer-provided extras handed back when sync actually occurs. This bundle is copied + * into the SyncRequest returned by {@link #build()}. * * Example: * <pre> @@ -436,7 +479,7 @@ public class SyncRequest implements Parcelable { * Bundle extras = new Bundle(); * extras.setString("data", syncData); * builder.setExtras(extras); - * ContentResolver.sync(builder.build()); // Each sync() request is for a unique sync. + * ContentResolver.sync(builder.build()); // Each sync() request creates a unique sync. * } * </pre> * Only values of the following types may be used in the extras bundle: @@ -477,13 +520,19 @@ public class SyncRequest implements Parcelable { /** * Convenience function for setting {@link ContentResolver#SYNC_EXTRAS_IGNORE_SETTINGS}. * - * A sync can specify that system sync settings be ignored (user has turned sync off). Not - * valid for periodic sync and will throw an <code>IllegalArgumentException</code> in + * Not valid for periodic sync and will throw an <code>IllegalArgumentException</code> in * {@link #build()}. + * <p>Throws <code>IllegalArgumentException</code> if called and + * {@link #setDisallowMetered(boolean)} has been set. + * * * @param ignoreSettings true to ignore the sync automatically settings. Default false. */ public Builder setIgnoreSettings(boolean ignoreSettings) { + if (mDisallowMetered && ignoreSettings) { + throw new IllegalArgumentException("setIgnoreSettings(true) after having specified" + + " sync settings with this builder."); + } mIgnoreSettings = ignoreSettings; return this; } @@ -491,13 +540,13 @@ public class SyncRequest implements Parcelable { /** * Convenience function for setting {@link ContentResolver#SYNC_EXTRAS_IGNORE_BACKOFF}. * - * Force the sync scheduling process to ignore any back-off that was the result of a failed - * sync, as well as to invalidate any {@link SyncResult#delayUntil} value that may have - * been set by the adapter. Successive failures will not honor this flag. Not valid for - * periodic sync and will throw an <code>IllegalArgumentException</code> in - * {@link #build()}. + * Ignoring back-off will force the sync scheduling process to ignore any back-off that was + * the result of a failed sync, as well as to invalidate any {@link SyncResult#delayUntil} + * value that may have been set by the adapter. Successive failures will not honor this + * flag. Not valid for periodic sync and will throw an <code>IllegalArgumentException</code> + * in {@link #build()}. * - * @param ignoreBackoff ignore back-off settings. Default false. + * @param ignoreBackoff ignore back off settings. Default false. */ public Builder setIgnoreBackoff(boolean ignoreBackoff) { mIgnoreBackoff = ignoreBackoff; @@ -507,9 +556,8 @@ public class SyncRequest implements Parcelable { /** * Convenience function for setting {@link ContentResolver#SYNC_EXTRAS_MANUAL}. * - * A manual sync is functionally equivalent to calling {@link #setIgnoreBackoff(boolean)} - * and {@link #setIgnoreSettings(boolean)}. Not valid for periodic sync and will throw an - * <code>IllegalArgumentException</code> in {@link #build()}. + * Not valid for periodic sync and will throw an <code>IllegalArgumentException</code> in + * {@link #build()}. * * @param isManual User-initiated sync or not. Default false. */ @@ -519,7 +567,7 @@ public class SyncRequest implements Parcelable { } /** - * An expedited sync runs immediately and will preempt another non-expedited running sync. + * An expedited sync runs immediately and can preempt other non-expedited running syncs. * * Not valid for periodic sync and will throw an <code>IllegalArgumentException</code> in * {@link #build()}. @@ -532,7 +580,6 @@ public class SyncRequest implements Parcelable { } /** - * {@hide} * @param priority the priority of this request among all requests from the calling app. * Range of [-2,2] similar to how this is done with notifications. */ @@ -552,11 +599,11 @@ public class SyncRequest implements Parcelable { * builder. */ public SyncRequest build() { + // Validate the extras bundle + ContentResolver.validateSyncExtrasBundle(mCustomExtras); if (mCustomExtras == null) { mCustomExtras = new Bundle(); } - // Validate the extras bundle - ContentResolver.validateSyncExtrasBundle(mCustomExtras); // Combine builder extra flags into the config bundle. mSyncConfigExtras = new Bundle(); if (mIgnoreBackoff) { @@ -575,51 +622,33 @@ public class SyncRequest implements Parcelable { mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true); } if (mIsManual) { - mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); + mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true); + mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true); } mSyncConfigExtras.putLong(ContentResolver.SYNC_EXTRAS_EXPECTED_UPLOAD, mTxBytes); mSyncConfigExtras.putLong(ContentResolver.SYNC_EXTRAS_EXPECTED_DOWNLOAD, mRxBytes); mSyncConfigExtras.putInt(ContentResolver.SYNC_EXTRAS_PRIORITY, mPriority); if (mSyncType == SYNC_TYPE_PERIODIC) { // If this is a periodic sync ensure than invalid extras were not set. - validatePeriodicExtras(mCustomExtras); - validatePeriodicExtras(mSyncConfigExtras); - // Verify that account and provider are not null. - if (mAccount == null) { - throw new IllegalArgumentException("Account must not be null for periodic" - + " sync."); - } - if (mAuthority == null) { - throw new IllegalArgumentException("Authority must not be null for periodic" - + " sync."); + if (ContentResolver.invalidPeriodicExtras(mCustomExtras) || + ContentResolver.invalidPeriodicExtras(mSyncConfigExtras)) { + throw new IllegalArgumentException("Illegal extras were set"); } } else if (mSyncType == SYNC_TYPE_UNKNOWN) { throw new IllegalArgumentException("Must call either syncOnce() or syncPeriodic()"); } + if (mSyncTarget == SYNC_TARGET_SERVICE) { + if (mSyncConfigExtras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)) { + throw new IllegalArgumentException("Cannot specify an initialisation sync" + + " that targets a service."); + } + } // Ensure that a target for the sync has been set. if (mSyncTarget == SYNC_TARGET_UNKNOWN) { - throw new IllegalArgumentException("Must specify an adapter with " - + "setSyncAdapter(Account, String"); + throw new IllegalArgumentException("Must specify an adapter with one of" + + "setSyncAdapter(ComponentName) or setSyncAdapter(Account, String"); } return new SyncRequest(this); } - - /** - * Helper function to throw an <code>IllegalArgumentException</code> if any illegal - * extras were set for a periodic sync. - * - * @param extras bundle to validate. - */ - private void validatePeriodicExtras(Bundle extras) { - if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false) - || extras.getBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, false) - || extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false) - || extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false) - || extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false) - || extras.getBoolean(ContentResolver.SYNC_EXTRAS_FORCE, false) - || extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false)) { - throw new IllegalArgumentException("Illegal extras were set"); - } - } - } + } } diff --git a/core/java/android/content/SyncService.java b/core/java/android/content/SyncService.java new file mode 100644 index 0000000..4df998c --- /dev/null +++ b/core/java/android/content/SyncService.java @@ -0,0 +1,211 @@ +/* + * 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.content; + +import android.app.Service; +import android.os.Bundle; +import android.os.IBinder; +import android.os.Process; +import android.os.Trace; +import android.util.SparseArray; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; + +/** + * Simplified @link android.content.AbstractThreadedSyncAdapter. Folds that + * behaviour into a service to which the system can bind when requesting an + * anonymous (providerless/accountless) sync. + * <p> + * In order to perform an anonymous sync operation you must extend this service, implementing the + * abstract methods. This service must be declared in the application's manifest as usual. You + * can use this service for other work, however you <b> must not </b> override the onBind() method + * unless you know what you're doing, which limits the usefulness of this service for other work. + * <p>A {@link SyncService} can either be active or inactive. Different to an + * {@link AbstractThreadedSyncAdapter}, there is no + * {@link ContentResolver#setSyncAutomatically(android.accounts.Account account, String provider, boolean sync)}, + * as well as no concept of initialisation (you can handle your own if needed). + * + * <pre> + * <service android:name=".MySyncService"/> + * </pre> + * Like @link android.content.AbstractThreadedSyncAdapter this service supports + * multiple syncs at the same time. Each incoming startSync() with a unique tag + * will spawn a thread to do the work of that sync. If startSync() is called + * with a tag that already exists, a SyncResult.ALREADY_IN_PROGRESS is returned. + * Remember that your service will spawn multiple threads if you schedule multiple syncs + * at once, so if you mutate local objects you must ensure synchronization. + */ +public abstract class SyncService extends Service { + private static final String TAG = "SyncService"; + + private final SyncAdapterImpl mSyncAdapter = new SyncAdapterImpl(); + + /** Keep track of on-going syncs, keyed by bundle. */ + @GuardedBy("mSyncThreadLock") + private final SparseArray<SyncThread> + mSyncThreads = new SparseArray<SyncThread>(); + /** Lock object for accessing the SyncThreads HashMap. */ + private final Object mSyncThreadLock = new Object(); + /** + * Default key for if this sync service does not support parallel operations. Currently not + * sure if null keys will make it into the ArrayMap for KLP, so keeping our default for now. + */ + private static final int KEY_DEFAULT = 0; + /** Identifier for this sync service. */ + private ComponentName mServiceComponent; + + /** {@hide} */ + public IBinder onBind(Intent intent) { + mServiceComponent = new ComponentName(this, getClass()); + return mSyncAdapter.asBinder(); + } + + /** {@hide} */ + private class SyncAdapterImpl extends ISyncServiceAdapter.Stub { + @Override + public void startSync(ISyncContext syncContext, Bundle extras) { + // Wrap the provided Sync Context because it may go away by the time + // we call it. + final SyncContext syncContextClient = new SyncContext(syncContext); + boolean alreadyInProgress = false; + final int extrasAsKey = extrasToKey(extras); + synchronized (mSyncThreadLock) { + if (mSyncThreads.get(extrasAsKey) == null) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "starting sync for : " + mServiceComponent); + } + // Start sync. + SyncThread syncThread = new SyncThread(syncContextClient, extras); + mSyncThreads.put(extrasAsKey, syncThread); + syncThread.start(); + } else { + // Don't want to call back to SyncManager while still + // holding lock. + alreadyInProgress = true; + } + } + if (alreadyInProgress) { + syncContextClient.onFinished(SyncResult.ALREADY_IN_PROGRESS); + } + } + + /** + * Used by the SM to cancel a specific sync using the + * com.android.server.content.SyncManager.ActiveSyncContext as a handle. + */ + @Override + public void cancelSync(ISyncContext syncContext) { + SyncThread runningSync = null; + synchronized (mSyncThreadLock) { + for (int i = 0; i < mSyncThreads.size(); i++) { + SyncThread thread = mSyncThreads.valueAt(i); + if (thread.mSyncContext.getSyncContextBinder() == syncContext.asBinder()) { + runningSync = thread; + break; + } + } + } + if (runningSync != null) { + runningSync.interrupt(); + } + } + } + + /** + * + * @param extras Bundle for which to compute hash + * @return an integer hash that is equal to that of another bundle if they both contain the + * same key -> value mappings, however, not necessarily in order. + * Based on the toString() representation of the value mapped. + */ + private int extrasToKey(Bundle extras) { + int hash = KEY_DEFAULT; // Empty bundle, or no parallel operations enabled. + if (parallelSyncsEnabled()) { + for (String key : extras.keySet()) { + String mapping = key + " " + extras.get(key).toString(); + hash += mapping.hashCode(); + } + } + return hash; + } + + /** + * {@hide} + * Similar to {@link android.content.AbstractThreadedSyncAdapter.SyncThread}. However while + * the ATSA considers an already in-progress sync to be if the account provided is currently + * syncing, this anonymous sync has no notion of account and considers a sync unique if the + * provided bundle is different. + */ + private class SyncThread extends Thread { + private final SyncContext mSyncContext; + private final Bundle mExtras; + private final int mThreadsKey; + + public SyncThread(SyncContext syncContext, Bundle extras) { + mSyncContext = syncContext; + mExtras = extras; + mThreadsKey = extrasToKey(extras); + } + + @Override + public void run() { + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + + Trace.traceBegin(Trace.TRACE_TAG_SYNC_MANAGER, getApplication().getPackageName()); + + SyncResult syncResult = new SyncResult(); + try { + if (isCancelled()) return; + // Run the sync. + SyncService.this.onPerformSync(mExtras, syncResult); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_SYNC_MANAGER); + if (!isCancelled()) { + mSyncContext.onFinished(syncResult); + } + // Synchronize so that the assignment will be seen by other + // threads that also synchronize accesses to mSyncThreads. + synchronized (mSyncThreadLock) { + mSyncThreads.remove(mThreadsKey); + } + } + } + + private boolean isCancelled() { + return Thread.currentThread().isInterrupted(); + } + } + + /** + * Initiate an anonymous sync using this service. SyncAdapter-specific + * parameters may be specified in extras, which is guaranteed to not be + * null. + */ + public abstract void onPerformSync(Bundle extras, SyncResult syncResult); + + /** + * Override this function to indicated whether you want to support parallel syncs. + * <p>If you override and return true multiple threads will be spawned within your Service to + * handle each concurrent sync request. + * + * @return false to indicate that this service does not support parallel operations by default. + */ + protected boolean parallelSyncsEnabled() { + return false; + } +} diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index b8ac3bf..40275d8 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -16,11 +16,15 @@ package android.content.pm; +import android.annotation.IntDef; import android.content.res.Configuration; import android.os.Parcel; import android.os.Parcelable; import android.util.Printer; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Information you can retrieve about a particular application * activity or receiver. This corresponds to information collected @@ -212,6 +216,28 @@ public class ActivityInfo extends ComponentInfo */ public int flags; + /** @hide */ + @IntDef({ + SCREEN_ORIENTATION_UNSPECIFIED, + SCREEN_ORIENTATION_LANDSCAPE, + SCREEN_ORIENTATION_PORTRAIT, + SCREEN_ORIENTATION_USER, + SCREEN_ORIENTATION_BEHIND, + SCREEN_ORIENTATION_SENSOR, + SCREEN_ORIENTATION_NOSENSOR, + SCREEN_ORIENTATION_SENSOR_LANDSCAPE, + SCREEN_ORIENTATION_SENSOR_PORTRAIT, + SCREEN_ORIENTATION_REVERSE_LANDSCAPE, + SCREEN_ORIENTATION_REVERSE_PORTRAIT, + SCREEN_ORIENTATION_FULL_SENSOR, + SCREEN_ORIENTATION_USER_LANDSCAPE, + SCREEN_ORIENTATION_USER_PORTRAIT, + SCREEN_ORIENTATION_FULL_USER, + SCREEN_ORIENTATION_LOCKED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ScreenOrientation {} + /** * Constant corresponding to <code>unspecified</code> in * the {@link android.R.attr#screenOrientation} attribute. @@ -323,6 +349,7 @@ public class ActivityInfo extends ComponentInfo * {@link #SCREEN_ORIENTATION_FULL_USER}, * {@link #SCREEN_ORIENTATION_LOCKED}, */ + @ScreenOrientation public int screenOrientation = SCREEN_ORIENTATION_UNSPECIFIED; /** diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 9c46d96..8434c5d 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -338,7 +338,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * the normal application lifecycle. * * <p>Comes from the - * {@link android.R.styleable#AndroidManifestApplication_cantSaveState android:cantSaveState} + * android.R.styleable#AndroidManifestApplication_cantSaveState * attribute of the <application> tag. * * {@hide} @@ -456,7 +456,13 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * behavior was introduced. */ public int targetSdkVersion; - + + /** + * The app's declared version code. + * @hide + */ + public int versionCode; + /** * When false, indicates that all components within this application are * considered disabled, regardless of their individually set enabled status. @@ -508,7 +514,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { if (sharedLibraryFiles != null) { pw.println(prefix + "sharedLibraryFiles=" + sharedLibraryFiles); } - pw.println(prefix + "enabled=" + enabled + " targetSdkVersion=" + targetSdkVersion); + pw.println(prefix + "enabled=" + enabled + " targetSdkVersion=" + targetSdkVersion + + " versionCode=" + versionCode); if (manageSpaceActivityName != null) { pw.println(prefix + "manageSpaceActivityName="+manageSpaceActivityName); } @@ -576,6 +583,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { dataDir = orig.dataDir; uid = orig.uid; targetSdkVersion = orig.targetSdkVersion; + versionCode = orig.versionCode; enabled = orig.enabled; enabledSetting = orig.enabledSetting; installLocation = orig.installLocation; @@ -616,6 +624,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { dest.writeString(dataDir); dest.writeInt(uid); dest.writeInt(targetSdkVersion); + dest.writeInt(versionCode); dest.writeInt(enabled ? 1 : 0); dest.writeInt(enabledSetting); dest.writeInt(installLocation); @@ -655,6 +664,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { dataDir = source.readString(); uid = source.readInt(); targetSdkVersion = source.readInt(); + versionCode = source.readInt(); enabled = source.readInt() != 0; enabledSetting = source.readInt(); installLocation = source.readInt(); diff --git a/core/java/android/content/pm/FeatureInfo.java b/core/java/android/content/pm/FeatureInfo.java index 89394f9..d919fc3 100644 --- a/core/java/android/content/pm/FeatureInfo.java +++ b/core/java/android/content/pm/FeatureInfo.java @@ -18,7 +18,6 @@ package android.content.pm; import android.os.Parcel; import android.os.Parcelable; -import android.os.Parcelable.Creator; /** * A single feature that can be requested by an application. This corresponds diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 20002ad..c9fb530 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -237,6 +237,10 @@ interface IPackageManager { int getPreferredActivities(out List<IntentFilter> outFilters, out List<ComponentName> outActivities, String packageName); + void addPersistentPreferredActivity(in IntentFilter filter, in ComponentName activity, int userId); + + void clearPackagePersistentPreferredActivities(String packageName, int userId); + /** * Report the set of 'Home' activity candidates, plus (if any) which of them * is the current "always use this one" setting. diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java index 785f2b4..ef0c4d5 100644 --- a/core/java/android/content/pm/PackageInfo.java +++ b/core/java/android/content/pm/PackageInfo.java @@ -209,6 +209,19 @@ public class PackageInfo implements Parcelable { */ public static final int INSTALL_LOCATION_PREFER_EXTERNAL = 2; /** + * Flag for {@link #requiredForProfile} + * The application will always be installed for a restricted profile. + * @hide + */ + public static final int RESTRICTED_PROFILE = 1; + /** + * Flag for {@link #requiredForProfile} + * The application will always be installed for a managed profile. + * @hide + */ + public static final int MANAGED_PROFILE = 2; + + /** * The install location requested by the activity. From the * {@link android.R.attr#installLocation} attribute, one of * {@link #INSTALL_LOCATION_AUTO}, @@ -218,6 +231,12 @@ public class PackageInfo implements Parcelable { */ public int installLocation = INSTALL_LOCATION_INTERNAL_ONLY; + /** + * Defines which profiles this app is required for. + * @hide + */ + public int requiredForProfile; + /** @hide */ public boolean requiredForAllUsers; @@ -276,6 +295,7 @@ public class PackageInfo implements Parcelable { dest.writeTypedArray(reqFeatures, parcelableFlags); dest.writeInt(installLocation); dest.writeInt(requiredForAllUsers ? 1 : 0); + dest.writeInt(requiredForProfile); dest.writeString(restrictedAccountType); dest.writeString(requiredAccountType); dest.writeString(overlayTarget); @@ -318,6 +338,7 @@ public class PackageInfo implements Parcelable { reqFeatures = source.createTypedArray(FeatureInfo.CREATOR); installLocation = source.readInt(); requiredForAllUsers = source.readInt() != 0; + requiredForProfile = source.readInt(); restrictedAccountType = source.readString(); requiredAccountType = source.readString(); overlayTarget = source.readString(); diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 6440f0b..2facef6 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -16,6 +16,7 @@ package android.content.pm; +import android.annotation.IntDef; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.content.ComponentName; @@ -33,6 +34,8 @@ import android.util.AndroidException; import android.util.DisplayMetrics; import java.io.File; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.List; /** @@ -190,6 +193,11 @@ public abstract class PackageManager { */ public static final int MATCH_DEFAULT_ONLY = 0x00010000; + /** @hide */ + @IntDef({PERMISSION_GRANTED, PERMISSION_DENIED}) + @Retention(RetentionPolicy.SOURCE) + public @interface PermissionResult {} + /** * Permission check result: this is returned by {@link #checkPermission} * if the permission has been granted to the given package. diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index a07ec1a..f76aada 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -306,6 +306,7 @@ public class PackageParser { if ((pi.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0 || (pi.applicationInfo.flags&ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) { pi.requiredForAllUsers = p.mRequiredForAllUsers; + pi.requiredForProfile = p.mRequiredForProfile; } pi.restrictedAccountType = p.mRestrictedAccountType; pi.requiredAccountType = p.mRequiredAccountType; @@ -988,7 +989,7 @@ public class PackageParser { TypedArray sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.AndroidManifest); - pkg.mVersionCode = sa.getInteger( + pkg.mVersionCode = pkg.applicationInfo.versionCode = sa.getInteger( com.android.internal.R.styleable.AndroidManifest_versionCode, 0); pkg.mVersionName = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifest_versionName, 0); @@ -1986,6 +1987,8 @@ public class PackageParser { false)) { owner.mRequiredForAllUsers = true; } + owner.mRequiredForProfile = sa.getInt( + com.android.internal.R.styleable.AndroidManifestApplication_requiredForProfile, 0); String restrictedAccountType = sa.getString(com.android.internal.R.styleable .AndroidManifestApplication_restrictedAccountType); @@ -3586,6 +3589,9 @@ public class PackageParser { /* An app that's required for all users and cannot be uninstalled for a user */ public boolean mRequiredForAllUsers; + /* For which types of profile this app is required */ + public int mRequiredForProfile; + /* The restricted account authenticator type that is used by this application */ public String mRestrictedAccountType; diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java index 4c87830..6f1d4f8 100644 --- a/core/java/android/content/pm/UserInfo.java +++ b/core/java/android/content/pm/UserInfo.java @@ -18,6 +18,7 @@ package android.content.pm; import android.os.Parcel; import android.os.Parcelable; +import android.os.SystemProperties; import android.os.UserHandle; /** @@ -63,6 +64,15 @@ public class UserInfo implements Parcelable { */ public static final int FLAG_INITIALIZED = 0x00000010; + /** + * Indicates that this user is a profile of another user, for example holding a users + * corporate data. + */ + public static final int FLAG_MANAGED_PROFILE = 0x00000020; + + + public static final int NO_RELATED_GROUP_ID = -1; + public int id; public int serialNumber; public String name; @@ -70,6 +80,7 @@ public class UserInfo implements Parcelable { public int flags; public long creationTime; public long lastLoggedInTime; + public int relatedGroupId; /** User is only partially created. */ public boolean partial; @@ -83,6 +94,7 @@ public class UserInfo implements Parcelable { this.name = name; this.flags = flags; this.iconPath = iconPath; + this.relatedGroupId = NO_RELATED_GROUP_ID; } public boolean isPrimary() { @@ -101,6 +113,18 @@ public class UserInfo implements Parcelable { return (flags & FLAG_RESTRICTED) == FLAG_RESTRICTED; } + public boolean isManagedProfile() { + return (flags & FLAG_MANAGED_PROFILE) == FLAG_MANAGED_PROFILE; + } + + /** + * @return true if this user can be switched to. + **/ + public boolean supportsSwitchTo() { + // TODO remove fw.show_hidden_users when we have finished developing managed profiles. + return !isManagedProfile() || SystemProperties.getBoolean("fw.show_hidden_users", false); + } + public UserInfo() { } @@ -113,6 +137,7 @@ public class UserInfo implements Parcelable { creationTime = orig.creationTime; lastLoggedInTime = orig.lastLoggedInTime; partial = orig.partial; + relatedGroupId = orig.relatedGroupId; } public UserHandle getUserHandle() { @@ -137,6 +162,7 @@ public class UserInfo implements Parcelable { dest.writeLong(creationTime); dest.writeLong(lastLoggedInTime); dest.writeInt(partial ? 1 : 0); + dest.writeInt(relatedGroupId); } public static final Parcelable.Creator<UserInfo> CREATOR @@ -158,5 +184,6 @@ public class UserInfo implements Parcelable { creationTime = source.readLong(); lastLoggedInTime = source.readLong(); partial = source.readInt() != 0; + relatedGroupId = source.readInt(); } } diff --git a/core/java/android/content/pm/XmlSerializerAndParser.java b/core/java/android/content/pm/XmlSerializerAndParser.java index 935fc02..20cb61c 100644 --- a/core/java/android/content/pm/XmlSerializerAndParser.java +++ b/core/java/android/content/pm/XmlSerializerAndParser.java @@ -19,7 +19,6 @@ package android.content.pm; import org.xmlpull.v1.XmlSerializer; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; -import android.os.Parcel; import java.io.IOException; diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index 9ce17e4..a41b4f9 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -17,7 +17,6 @@ package android.content.res; import android.os.ParcelFileDescriptor; -import android.os.Trace; import android.util.Log; import android.util.TypedValue; @@ -536,6 +535,9 @@ public final class AssetManager { } public final class AssetInputStream extends InputStream { + /** + * @hide + */ public final int getAssetInt() { throw new UnsupportedOperationException(); } diff --git a/core/java/android/content/res/ColorStateList.java b/core/java/android/content/res/ColorStateList.java index bd23db4..419abf2 100644 --- a/core/java/android/content/res/ColorStateList.java +++ b/core/java/android/content/res/ColorStateList.java @@ -16,12 +16,15 @@ package android.content.res; +import android.graphics.Color; + import com.android.internal.util.ArrayUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import android.util.AttributeSet; +import android.util.MathUtils; import android.util.SparseArray; import android.util.StateSet; import android.util.Xml; @@ -171,7 +174,7 @@ public class ColorStateList implements Parcelable { * Fill in this object based on the contents of an XML "selector" element. */ private void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) - throws XmlPullParserException, IOException { + throws XmlPullParserException, IOException { int type; @@ -194,6 +197,8 @@ public class ColorStateList implements Parcelable { continue; } + int alphaRes = 0; + float alpha = 1.0f; int colorRes = 0; int color = 0xffff0000; boolean haveColor = false; @@ -205,17 +210,20 @@ public class ColorStateList implements Parcelable { for (i = 0; i < numAttrs; i++) { final int stateResId = attrs.getAttributeNameResource(i); if (stateResId == 0) break; - if (stateResId == com.android.internal.R.attr.color) { + if (stateResId == com.android.internal.R.attr.alpha) { + alphaRes = attrs.getAttributeResourceValue(i, 0); + if (alphaRes == 0) { + alpha = attrs.getAttributeFloatValue(i, 1.0f); + } + } else if (stateResId == com.android.internal.R.attr.color) { colorRes = attrs.getAttributeResourceValue(i, 0); - if (colorRes == 0) { color = attrs.getAttributeIntValue(i, color); haveColor = true; } } else { stateSpec[j++] = attrs.getAttributeBooleanValue(i, false) - ? stateResId - : -stateResId; + ? stateResId : -stateResId; } } stateSpec = StateSet.trimStateSet(stateSpec, j); @@ -228,10 +236,18 @@ public class ColorStateList implements Parcelable { + ": <item> tag requires a 'android:color' attribute."); } + if (alphaRes != 0) { + alpha = r.getFraction(alphaRes, 1, 1); + } + + // Apply alpha modulation. + final int alphaMod = MathUtils.constrain((int) (Color.alpha(color) * alpha), 0, 255); + color = (color & 0xFFFFFF) | (alphaMod << 24); + if (listSize == 0 || stateSpec.length == 0) { mDefaultColor = color; } - + if (listSize + 1 >= listAllocated) { listAllocated = ArrayUtils.idealIntArraySize(listSize + 1); @@ -259,7 +275,17 @@ public class ColorStateList implements Parcelable { public boolean isStateful() { return mStateSpecs.length > 1; } - + + public boolean isOpaque() { + final int n = mColors.length; + for (int i = 0; i < n; i++) { + if (Color.alpha(mColors[i]) != 0xFF) { + return false; + } + } + return true; + } + /** * Return the color associated with the given set of {@link android.view.View} states. * @@ -289,6 +315,25 @@ public class ColorStateList implements Parcelable { return mDefaultColor; } + /** + * Return the states in this {@link ColorStateList}. + * @return the states in this {@link ColorStateList} + * @hide + */ + public int[][] getStates() { + return mStateSpecs; + } + + /** + * Return the colors in this {@link ColorStateList}. + * @return the colors in this {@link ColorStateList} + * @hide + */ + public int[] getColors() { + return mColors; + } + + @Override public String toString() { return "ColorStateList{" + "mStateSpecs=" + Arrays.deepToString(mStateSpecs) + @@ -296,14 +341,16 @@ public class ColorStateList implements Parcelable { "mDefaultColor=" + mDefaultColor + '}'; } + @Override public int describeContents() { return 0; } + @Override public void writeToParcel(Parcel dest, int flags) { final int N = mStateSpecs.length; dest.writeInt(N); - for (int i=0; i<N; i++) { + for (int i = 0; i < N; i++) { dest.writeIntArray(mStateSpecs[i]); } dest.writeIntArray(mColors); @@ -311,17 +358,19 @@ public class ColorStateList implements Parcelable { public static final Parcelable.Creator<ColorStateList> CREATOR = new Parcelable.Creator<ColorStateList>() { + @Override public ColorStateList[] newArray(int size) { return new ColorStateList[size]; } + @Override public ColorStateList createFromParcel(Parcel source) { final int N = source.readInt(); - int[][] stateSpecs = new int[N][]; - for (int i=0; i<N; i++) { + final int[][] stateSpecs = new int[N][]; + for (int i = 0; i < N; i++) { stateSpecs[i] = source.createIntArray(); } - int[] colors = source.createIntArray(); + final int[] colors = source.createIntArray(); return new ColorStateList(stateSpecs, colors); } }; diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 3d9daca..5c27072 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -36,7 +36,6 @@ import android.util.Log; import android.util.Slog; import android.util.TypedValue; import android.util.LongSparseArray; -import android.view.DisplayAdjustments; import java.io.IOException; import java.io.InputStream; @@ -72,16 +71,19 @@ import libcore.icu.NativePluralRules; */ public class Resources { static final String TAG = "Resources"; + private static final boolean DEBUG_LOAD = false; private static final boolean DEBUG_CONFIG = false; private static final boolean DEBUG_ATTRIBUTES_CACHE = false; private static final boolean TRACE_FOR_PRELOAD = false; private static final boolean TRACE_FOR_MISS_PRELOAD = false; + private static final int LAYOUT_DIR_CONFIG = ActivityInfo.activityInfoConfigToNative( + ActivityInfo.CONFIG_LAYOUT_DIRECTION); + private static final int ID_OTHER = 0x01000004; private static final Object sSync = new Object(); - /*package*/ static Resources mSystem = null; // Information about preloaded resources. Note that they are not // protected by a lock, because while preloading in zygote we are all @@ -92,32 +94,35 @@ public class Resources { private static final LongSparseArray<ColorStateList> sPreloadedColorStateLists = new LongSparseArray<ColorStateList>(); + // Used by BridgeResources in layoutlib + static Resources mSystem = null; + private static boolean sPreloaded; private static int sPreloadedDensity; // These are protected by mAccessLock. + private final Object mAccessLock = new Object(); + private final Configuration mTmpConfig = new Configuration(); + private final LongSparseArray<WeakReference<Drawable.ConstantState>> mDrawableCache + = new LongSparseArray<WeakReference<Drawable.ConstantState>>(0); + private final LongSparseArray<WeakReference<ColorStateList>> mColorStateListCache + = new LongSparseArray<WeakReference<ColorStateList>>(0); + private final LongSparseArray<WeakReference<Drawable.ConstantState>> mColorDrawableCache + = new LongSparseArray<WeakReference<Drawable.ConstantState>>(0); - /*package*/ final Object mAccessLock = new Object(); - /*package*/ final Configuration mTmpConfig = new Configuration(); - /*package*/ TypedValue mTmpValue = new TypedValue(); - /*package*/ final LongSparseArray<WeakReference<Drawable.ConstantState> > mDrawableCache - = new LongSparseArray<WeakReference<Drawable.ConstantState> >(0); - /*package*/ final LongSparseArray<WeakReference<ColorStateList> > mColorStateListCache - = new LongSparseArray<WeakReference<ColorStateList> >(0); - /*package*/ final LongSparseArray<WeakReference<Drawable.ConstantState> > mColorDrawableCache - = new LongSparseArray<WeakReference<Drawable.ConstantState> >(0); - /*package*/ boolean mPreloading; + private TypedValue mTmpValue = new TypedValue(); + private boolean mPreloading; - /*package*/ TypedArray mCachedStyledAttributes = null; - RuntimeException mLastRetrievedAttrs = null; + private TypedArray mCachedStyledAttributes = null; + private RuntimeException mLastRetrievedAttrs = null; private int mLastCachedXmlBlockIndex = -1; private final int[] mCachedXmlBlockIds = { 0, 0, 0, 0 }; private final XmlBlock[] mCachedXmlBlocks = new XmlBlock[4]; - /*package*/ final AssetManager mAssets; + private final AssetManager mAssets; private final Configuration mConfiguration = new Configuration(); - /*package*/ final DisplayMetrics mMetrics = new DisplayMetrics(); + private final DisplayMetrics mMetrics = new DisplayMetrics(); private NativePluralRules mPluralRule; private CompatibilityInfo mCompatibilityInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO; @@ -681,12 +686,27 @@ public class Resources { * @param id The desired resource identifier, as generated by the aapt * tool. This integer encodes the package, type, and resource * entry. The value 0 is an invalid identifier. - * - * @throws NotFoundException Throws NotFoundException if the given ID does not exist. - * * @return Drawable An object that can be used to draw this resource. + * @throws NotFoundException Throws NotFoundException if the given ID does + * not exist. */ public Drawable getDrawable(int id) throws NotFoundException { + return getDrawable(id, null); + } + + /** + * Return a drawable object associated with a particular resource ID and + * styled for the specified theme. + * + * @param id The desired resource identifier, as generated by the aapt + * tool. This integer encodes the package, type, and resource + * entry. The value 0 is an invalid identifier. + * @param theme The theme used to style the drawable attributes. + * @return Drawable An object that can be used to draw this resource. + * @throws NotFoundException Throws NotFoundException if the given ID does + * not exist. + */ + public Drawable getDrawable(int id, Theme theme) throws NotFoundException { TypedValue value; synchronized (mAccessLock) { value = mTmpValue; @@ -697,7 +717,7 @@ public class Resources { } getValue(id, value, true); } - Drawable res = loadDrawable(value, id); + final Drawable res = loadDrawable(value, id, theme); synchronized (mAccessLock) { if (mTmpValue == null) { mTmpValue = value; @@ -715,17 +735,36 @@ public class Resources { * depending on the underlying resource -- for example, a solid color, PNG * image, scalable image, etc. The Drawable API hides these implementation * details. - * + * * @param id The desired resource identifier, as generated by the aapt tool. * This integer encodes the package, type, and resource entry. * The value 0 is an invalid identifier. * @param density the desired screen density indicated by the resource as * found in {@link DisplayMetrics}. + * @return Drawable An object that can be used to draw this resource. * @throws NotFoundException Throws NotFoundException if the given ID does * not exist. - * @return Drawable An object that can be used to draw this resource. + * @see #getDrawableForDensity(int, int, Theme) */ public Drawable getDrawableForDensity(int id, int density) throws NotFoundException { + return getDrawableForDensity(id, density, null); + } + + /** + * Return a drawable object associated with a particular resource ID for the + * given screen density in DPI and styled for the specified theme. + * + * @param id The desired resource identifier, as generated by the aapt tool. + * This integer encodes the package, type, and resource entry. + * The value 0 is an invalid identifier. + * @param density The desired screen density indicated by the resource as + * found in {@link DisplayMetrics}. + * @param theme The theme used to style the drawable attributes. + * @return Drawable An object that can be used to draw this resource. + * @throws NotFoundException Throws NotFoundException if the given ID does + * not exist. + */ + public Drawable getDrawableForDensity(int id, int density, Theme theme) { TypedValue value; synchronized (mAccessLock) { value = mTmpValue; @@ -752,7 +791,7 @@ public class Resources { } } - Drawable res = loadDrawable(value, id); + final Drawable res = loadDrawable(value, id, theme); synchronized (mAccessLock) { if (mTmpValue == null) { mTmpValue = value; @@ -1246,8 +1285,9 @@ public class Resources { * @see #obtainStyledAttributes(AttributeSet, int[], int, int) */ public TypedArray obtainStyledAttributes(int[] attrs) { - int len = attrs.length; - TypedArray array = getCachedStyledAttributes(len); + final int len = attrs.length; + final TypedArray array = getCachedStyledAttributes(len); + array.mTheme = this; array.mRsrcs = attrs; AssetManager.applyStyle(mTheme, 0, 0, 0, attrs, array.mData, array.mIndices); @@ -1274,10 +1314,10 @@ public class Resources { * @see #obtainStyledAttributes(int[]) * @see #obtainStyledAttributes(AttributeSet, int[], int, int) */ - public TypedArray obtainStyledAttributes(int resid, int[] attrs) - throws NotFoundException { - int len = attrs.length; - TypedArray array = getCachedStyledAttributes(len); + public TypedArray obtainStyledAttributes(int resid, int[] attrs) throws NotFoundException { + final int len = attrs.length; + final TypedArray array = getCachedStyledAttributes(len); + array.mTheme = this; array.mRsrcs = attrs; AssetManager.applyStyle(mTheme, 0, resid, 0, attrs, @@ -1361,19 +1401,18 @@ public class Resources { */ public TypedArray obtainStyledAttributes(AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) { - int len = attrs.length; - TypedArray array = getCachedStyledAttributes(len); + final int len = attrs.length; + final TypedArray array = getCachedStyledAttributes(len); // XXX note that for now we only work with compiled XML files. // To support generic XML files we will need to manually parse // out the attributes from the XML file (applying type information // contained in the resources and such). - XmlBlock.Parser parser = (XmlBlock.Parser)set; - AssetManager.applyStyle( - mTheme, defStyleAttr, defStyleRes, - parser != null ? parser.mParseState : 0, attrs, - array.mData, array.mIndices); + final XmlBlock.Parser parser = (XmlBlock.Parser)set; + AssetManager.applyStyle(mTheme, defStyleAttr, defStyleRes, + parser != null ? parser.mParseState : 0, attrs, array.mData, array.mIndices); + array.mTheme = this; array.mRsrcs = attrs; array.mXml = parser; @@ -1439,6 +1478,21 @@ public class Resources { } /** + * Return a drawable object associated with a particular resource ID + * and styled for the Theme. + * + * @param id The desired resource identifier, as generated by the aapt + * tool. This integer encodes the package, type, and resource + * entry. The value 0 is an invalid identifier. + * @return Drawable An object that can be used to draw this resource. + * @throws NotFoundException Throws NotFoundException if the given ID + * does not exist. + */ + public Drawable getDrawable(int id) throws NotFoundException { + return Resources.this.getDrawable(id, this); + } + + /** * Print contents of this theme out to the log. For debugging only. * * @param priority The log priority to use. @@ -1448,7 +1502,8 @@ public class Resources { public void dump(int priority, String tag, String prefix) { AssetManager.dumpTheme(mTheme, priority, tag, prefix); } - + + @Override protected void finalize() throws Throwable { super.finalize(); mAssets.releaseTheme(mTheme); @@ -1459,6 +1514,7 @@ public class Resources { mTheme = mAssets.createTheme(); } + @SuppressWarnings("hiding") private final AssetManager mAssets; private final long mTheme; } @@ -1569,7 +1625,7 @@ public class Resources { String locale = null; if (mConfiguration.locale != null) { - locale = mConfiguration.locale.toLanguageTag(); + locale = adjustLanguageTag(localeToLanguageTag(mConfiguration.locale)); } int width, height; if (mMetrics.widthPixels >= mMetrics.heightPixels) { @@ -1650,6 +1706,47 @@ public class Resources { } } + // Locale.toLanguageTag() is not available in Java6. LayoutLib overrides + // this method to enable users to use Java6. + private String localeToLanguageTag(Locale locale) { + return locale.toLanguageTag(); + } + + /** + * {@code Locale.toLanguageTag} will transform the obsolete (and deprecated) + * language codes "in", "ji" and "iw" to "id", "yi" and "he" respectively. + * + * All released versions of android prior to "L" used the deprecated language + * tags, so we will need to support them for backwards compatibility. + * + * Note that this conversion needs to take place *after* the call to + * {@code toLanguageTag} because that will convert all the deprecated codes to + * the new ones, even if they're set manually. + */ + private static String adjustLanguageTag(String languageTag) { + final int separator = languageTag.indexOf('-'); + final String language; + final String remainder; + + if (separator == -1) { + language = languageTag; + remainder = ""; + } else { + language = languageTag.substring(0, separator); + remainder = languageTag.substring(separator); + } + + if ("id".equals(language)) { + return "in" + remainder; + } else if ("yi".equals(language)) { + return "ji" + remainder; + } else if ("he".equals(language)) { + return "iw" + remainder; + } else { + return languageTag; + } + } + /** * Update the system resources configuration if they have previously * been initialized. @@ -2020,17 +2117,14 @@ public class Resources { return true; } - static private final int LAYOUT_DIR_CONFIG = ActivityInfo.activityInfoConfigToNative( - ActivityInfo.CONFIG_LAYOUT_DIRECTION); - - /*package*/ Drawable loadDrawable(TypedValue value, int id) - throws NotFoundException { - + /*package*/ Drawable loadDrawable(TypedValue value, int id, Theme theme) throws NotFoundException { if (TRACE_FOR_PRELOAD) { // Log only framework resources if ((id >>> 24) == 0x1) { final String name = getResourceName(id); - if (name != null) android.util.Log.d("PreloadDrawable", name); + if (name != null) { + Log.d("PreloadDrawable", name); + } } } @@ -2235,12 +2329,12 @@ public class Resources { "Resource is not a ColorStateList (color or path): " + value); } - String file = value.string.toString(); + final String file = value.string.toString(); if (file.endsWith(".xml")) { Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file); try { - XmlResourceParser rp = loadXmlResourceParser( + final XmlResourceParser rp = loadXmlResourceParser( file, id, value.assetCookie, "colorstatelist"); csl = ColorStateList.createFromXml(this, rp); rp.close(); @@ -2363,6 +2457,15 @@ public class Resources { + Integer.toHexString(id)); } + /*package*/ void recycleCachedStyledAttributes(TypedArray attrs) { + synchronized (mAccessLock) { + final TypedArray cached = mCachedStyledAttributes; + if (cached == null || cached.mData.length < attrs.mData.length) { + mCachedStyledAttributes = attrs; + } + } + } + private TypedArray getCachedStyledAttributes(int len) { synchronized (mAccessLock) { TypedArray attrs = mCachedStyledAttributes; diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java index 83d48aa..4858d08 100644 --- a/core/java/android/content/res/TypedArray.java +++ b/core/java/android/content/res/TypedArray.java @@ -16,7 +16,6 @@ package android.content.res; -import android.content.pm.ActivityInfo; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.util.DisplayMetrics; @@ -38,13 +37,16 @@ import java.util.Arrays; */ public class TypedArray { private final Resources mResources; + private final DisplayMetrics mMetrics; + private final AssetManager mAssets; /*package*/ XmlBlock.Parser mXml; /*package*/ int[] mRsrcs; /*package*/ int[] mData; /*package*/ int[] mIndices; /*package*/ int mLength; /*package*/ TypedValue mValue = new TypedValue(); - + /*package*/ Resources.Theme mTheme; + /** * Return the number of values in this array. */ @@ -393,7 +395,7 @@ public class TypedArray { return defValue; } else if (type == TypedValue.TYPE_DIMENSION) { return TypedValue.complexToDimension( - data[index+AssetManager.STYLE_DATA], mResources.mMetrics); + data[index+AssetManager.STYLE_DATA], mMetrics); } throw new UnsupportedOperationException("Can't convert to dimension: type=0x" @@ -425,7 +427,7 @@ public class TypedArray { return defValue; } else if (type == TypedValue.TYPE_DIMENSION) { return TypedValue.complexToDimensionPixelOffset( - data[index+AssetManager.STYLE_DATA], mResources.mMetrics); + data[index+AssetManager.STYLE_DATA], mMetrics); } throw new UnsupportedOperationException("Can't convert to dimension: type=0x" @@ -458,7 +460,7 @@ public class TypedArray { return defValue; } else if (type == TypedValue.TYPE_DIMENSION) { return TypedValue.complexToDimensionPixelSize( - data[index+AssetManager.STYLE_DATA], mResources.mMetrics); + data[index+AssetManager.STYLE_DATA], mMetrics); } throw new UnsupportedOperationException("Can't convert to dimension: type=0x" @@ -486,7 +488,7 @@ public class TypedArray { return data[index+AssetManager.STYLE_DATA]; } else if (type == TypedValue.TYPE_DIMENSION) { return TypedValue.complexToDimensionPixelSize( - data[index+AssetManager.STYLE_DATA], mResources.mMetrics); + data[index+AssetManager.STYLE_DATA], mMetrics); } throw new RuntimeException(getPositionDescription() @@ -515,7 +517,7 @@ public class TypedArray { return data[index+AssetManager.STYLE_DATA]; } else if (type == TypedValue.TYPE_DIMENSION) { return TypedValue.complexToDimensionPixelSize( - data[index+AssetManager.STYLE_DATA], mResources.mMetrics); + data[index+AssetManager.STYLE_DATA], mMetrics); } return defValue; @@ -599,7 +601,7 @@ public class TypedArray { + " cookie=" + value.assetCookie); System.out.println("******************************************************************"); } - return mResources.loadDrawable(value, value.resourceId); + return mResources.loadDrawable(value, value.resourceId, mTheme); } return null; } @@ -688,13 +690,11 @@ public class TypedArray { * Give back a previously retrieved array, for later re-use. */ public void recycle() { - synchronized (mResources.mAccessLock) { - TypedArray cached = mResources.mCachedStyledAttributes; - if (cached == null || cached.mData.length < mData.length) { - mXml = null; - mResources.mCachedStyledAttributes = this; - } - } + mResources.recycleCachedStyledAttributes(this); + + mXml = null; + mRsrcs = null; + mTheme = null; } private boolean getValueAt(int index, TypedValue outValue) { @@ -723,18 +723,19 @@ public class TypedArray { } return null; } - //System.out.println("Getting pooled from: " + v); - return mResources.mAssets.getPooledString( - cookie, data[index+AssetManager.STYLE_DATA]); + return mAssets.getPooledString(cookie, data[index+AssetManager.STYLE_DATA]); } /*package*/ TypedArray(Resources resources, int[] data, int[] indices, int len) { mResources = resources; + mMetrics = mResources.getDisplayMetrics(); + mAssets = mResources.getAssets(); mData = data; mIndices = indices; mLength = len; } + @Override public String toString() { return Arrays.toString(mData); } diff --git a/core/java/android/database/CursorToBulkCursorAdaptor.java b/core/java/android/database/CursorToBulkCursorAdaptor.java index 82a61d4..7dcfae2 100644 --- a/core/java/android/database/CursorToBulkCursorAdaptor.java +++ b/core/java/android/database/CursorToBulkCursorAdaptor.java @@ -20,7 +20,6 @@ import android.net.Uri; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; -import android.util.Log; /** diff --git a/core/java/android/database/sqlite/SQLiteOpenHelper.java b/core/java/android/database/sqlite/SQLiteOpenHelper.java index 431eca2..2dd4800 100644 --- a/core/java/android/database/sqlite/SQLiteOpenHelper.java +++ b/core/java/android/database/sqlite/SQLiteOpenHelper.java @@ -18,7 +18,6 @@ package android.database.sqlite; import android.content.Context; import android.database.DatabaseErrorHandler; -import android.database.DefaultDatabaseErrorHandler; import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.util.Log; diff --git a/core/java/android/ddm/DdmHandleNativeHeap.java b/core/java/android/ddm/DdmHandleNativeHeap.java index 6bd65aa..775c570 100644 --- a/core/java/android/ddm/DdmHandleNativeHeap.java +++ b/core/java/android/ddm/DdmHandleNativeHeap.java @@ -20,7 +20,6 @@ import org.apache.harmony.dalvik.ddmc.Chunk; import org.apache.harmony.dalvik.ddmc.ChunkHandler; import org.apache.harmony.dalvik.ddmc.DdmServer; import android.util.Log; -import java.nio.ByteBuffer; /** * Handle thread-related traffic. diff --git a/core/java/android/ddm/DdmHandleProfiling.java b/core/java/android/ddm/DdmHandleProfiling.java index 537763d..cce4dd2 100644 --- a/core/java/android/ddm/DdmHandleProfiling.java +++ b/core/java/android/ddm/DdmHandleProfiling.java @@ -21,7 +21,6 @@ import org.apache.harmony.dalvik.ddmc.ChunkHandler; import org.apache.harmony.dalvik.ddmc.DdmServer; import android.os.Debug; import android.util.Log; -import java.io.IOException; import java.nio.ByteBuffer; /** diff --git a/core/java/android/gesture/GestureOverlayView.java b/core/java/android/gesture/GestureOverlayView.java index 2d47f28..6e3a00f 100644 --- a/core/java/android/gesture/GestureOverlayView.java +++ b/core/java/android/gesture/GestureOverlayView.java @@ -134,11 +134,16 @@ public class GestureOverlayView extends FrameLayout { this(context, attrs, com.android.internal.R.attr.gestureOverlayViewStyle); } - public GestureOverlayView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public GestureOverlayView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public GestureOverlayView( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); - TypedArray a = context.obtainStyledAttributes(attrs, - R.styleable.GestureOverlayView, defStyle, 0); + final TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.GestureOverlayView, defStyleAttr, defStyleRes); mGestureStrokeWidth = a.getFloat(R.styleable.GestureOverlayView_gestureStrokeWidth, mGestureStrokeWidth); diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java index 111062d..6e2a099 100644 --- a/core/java/android/hardware/Camera.java +++ b/core/java/android/hardware/Camera.java @@ -46,7 +46,6 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import java.util.concurrent.locks.ReentrantLock; /** * The Camera class is used to set image capture settings, start/stop preview, diff --git a/core/java/android/hardware/GeomagneticField.java b/core/java/android/hardware/GeomagneticField.java index 0369825..ef05732 100644 --- a/core/java/android/hardware/GeomagneticField.java +++ b/core/java/android/hardware/GeomagneticField.java @@ -361,7 +361,7 @@ public class GeomagneticField { mP[0] = new float[] { 1.0f }; mPDeriv[0] = new float[] { 0.0f }; for (int n = 1; n <= maxN; n++) { - mP[n] = new float[n + 1]; + mP[n] = new float[n + 1]; mPDeriv[n] = new float[n + 1]; for (int m = 0; m <= n; m++) { if (n == m) { diff --git a/core/java/android/hardware/ICameraService.aidl b/core/java/android/hardware/ICameraService.aidl index 542af6a..4c50dda 100644 --- a/core/java/android/hardware/ICameraService.aidl +++ b/core/java/android/hardware/ICameraService.aidl @@ -61,4 +61,12 @@ interface ICameraService int removeListener(ICameraServiceListener listener); int getCameraCharacteristics(int cameraId, out CameraMetadataNative info); + + /** + * The java stubs for this method are not intended to be used. Please use + * the native stub in frameworks/av/include/camera/ICameraService.h instead. + * The BinderHolder output is being used as a placeholder, and will not be + * well-formatted in the generated java method. + */ + int getCameraVendorTagDescriptor(out BinderHolder desc); } diff --git a/core/java/android/hardware/SerialManager.java b/core/java/android/hardware/SerialManager.java index c5e1c2b..e0680bf 100644 --- a/core/java/android/hardware/SerialManager.java +++ b/core/java/android/hardware/SerialManager.java @@ -17,16 +17,12 @@ package android.hardware; -import android.app.PendingIntent; import android.content.Context; -import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.RemoteException; -import android.os.SystemProperties; import android.util.Log; import java.io.IOException; -import java.util.HashMap; /** * @hide diff --git a/core/java/android/hardware/SerialPort.java b/core/java/android/hardware/SerialPort.java index f50cdef..5d83d9c 100644 --- a/core/java/android/hardware/SerialPort.java +++ b/core/java/android/hardware/SerialPort.java @@ -17,14 +17,9 @@ package android.hardware; import android.os.ParcelFileDescriptor; -import android.util.Log; import java.io.FileDescriptor; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.InputStream; import java.io.IOException; -import java.io.OutputStream; import java.nio.ByteBuffer; diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index a38beec..d27485b 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -126,206 +126,279 @@ public final class CameraCharacteristics extends CameraMetadata { * modify the comment blocks at the start or end. *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~*/ + /** - * <p> - * Which set of antibanding modes are - * supported - * </p> + * <p>The set of auto-exposure antibanding modes that are + * supported by this camera device.</p> + * <p>Not all of the auto-exposure anti-banding modes may be + * supported by a given camera device. This field lists the + * valid anti-banding modes that the application may request + * for this camera device; they must include AUTO.</p> */ public static final Key<byte[]> CONTROL_AE_AVAILABLE_ANTIBANDING_MODES = new Key<byte[]>("android.control.aeAvailableAntibandingModes", byte[].class); /** - * <p> - * List of frame rate ranges supported by the - * AE algorithm/hardware - * </p> + * <p>The set of auto-exposure modes that are supported by this + * camera device.</p> + * <p>Not all the auto-exposure modes may be supported by a + * given camera device, especially if no flash unit is + * available. This entry lists the valid modes for + * {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} for this camera device.</p> + * <p>All camera devices support ON, and all camera devices with + * flash units support ON_AUTO_FLASH and + * ON_ALWAYS_FLASH.</p> + * <p>Full-capability camera devices always support OFF mode, + * which enables application control of camera exposure time, + * sensitivity, and frame duration.</p> + * + * @see CaptureRequest#CONTROL_AE_MODE + */ + public static final Key<byte[]> CONTROL_AE_AVAILABLE_MODES = + new Key<byte[]>("android.control.aeAvailableModes", byte[].class); + + /** + * <p>List of frame rate ranges supported by the + * AE algorithm/hardware</p> */ public static final Key<int[]> CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES = new Key<int[]>("android.control.aeAvailableTargetFpsRanges", int[].class); /** - * <p> - * Maximum and minimum exposure compensation + * <p>Maximum and minimum exposure compensation * setting, in counts of - * android.control.aeCompensationStepSize - * </p> + * {@link CameraCharacteristics#CONTROL_AE_COMPENSATION_STEP android.control.aeCompensationStep}.</p> + * + * @see CameraCharacteristics#CONTROL_AE_COMPENSATION_STEP */ public static final Key<int[]> CONTROL_AE_COMPENSATION_RANGE = new Key<int[]>("android.control.aeCompensationRange", int[].class); /** - * <p> - * Smallest step by which exposure compensation - * can be changed - * </p> + * <p>Smallest step by which exposure compensation + * can be changed</p> */ public static final Key<Rational> CONTROL_AE_COMPENSATION_STEP = new Key<Rational>("android.control.aeCompensationStep", Rational.class); /** - * <p> - * List of AF modes that can be - * selected - * </p> + * <p>List of AF modes that can be + * selected with {@link CaptureRequest#CONTROL_AF_MODE android.control.afMode}.</p> + * <p>Not all the auto-focus modes may be supported by a + * given camera device. This entry lists the valid modes for + * {@link CaptureRequest#CONTROL_AF_MODE android.control.afMode} for this camera device.</p> + * <p>All camera devices will support OFF mode, and all camera devices with + * adjustable focuser units (<code>{@link CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE android.lens.info.minimumFocusDistance} > 0</code>) + * will support AUTO mode.</p> + * + * @see CaptureRequest#CONTROL_AF_MODE + * @see CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE */ public static final Key<byte[]> CONTROL_AF_AVAILABLE_MODES = new Key<byte[]>("android.control.afAvailableModes", byte[].class); /** - * <p> - * what subset of the full color effect enum - * list is supported - * </p> + * <p>List containing the subset of color effects + * specified in {@link CaptureRequest#CONTROL_EFFECT_MODE android.control.effectMode} that is supported by + * this device.</p> + * <p>This list contains the color effect modes that can be applied to + * images produced by the camera device. Only modes that have + * been fully implemented for the current device may be included here. + * Implementations are not expected to be consistent across all devices. + * If no color effect modes are available for a device, this should + * simply be set to OFF.</p> + * <p>A color effect will only be applied if + * {@link CaptureRequest#CONTROL_MODE android.control.mode} != OFF.</p> + * + * @see CaptureRequest#CONTROL_EFFECT_MODE + * @see CaptureRequest#CONTROL_MODE */ public static final Key<byte[]> CONTROL_AVAILABLE_EFFECTS = new Key<byte[]>("android.control.availableEffects", byte[].class); /** - * <p> - * what subset of the scene mode enum list is - * supported. - * </p> + * <p>List containing a subset of scene modes + * specified in {@link CaptureRequest#CONTROL_SCENE_MODE android.control.sceneMode}.</p> + * <p>This list contains scene modes that can be set for the camera device. + * Only scene modes that have been fully implemented for the + * camera device may be included here. Implementations are not expected + * to be consistent across all devices. If no scene modes are supported + * by the camera device, this will be set to <code>[DISABLED]</code>.</p> + * + * @see CaptureRequest#CONTROL_SCENE_MODE */ public static final Key<byte[]> CONTROL_AVAILABLE_SCENE_MODES = new Key<byte[]>("android.control.availableSceneModes", byte[].class); /** - * <p> - * List of video stabilization modes that can - * be supported - * </p> + * <p>List of video stabilization modes that can + * be supported</p> */ public static final Key<byte[]> CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES = new Key<byte[]>("android.control.availableVideoStabilizationModes", byte[].class); /** + * <p>The set of auto-white-balance modes ({@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode}) + * that are supported by this camera device.</p> + * <p>Not all the auto-white-balance modes may be supported by a + * given camera device. This entry lists the valid modes for + * {@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode} for this camera device.</p> + * <p>All camera devices will support ON mode.</p> + * <p>Full-capability camera devices will always support OFF mode, + * which enables application control of white balance, by using + * {@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform} and {@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains}({@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} must be set to TRANSFORM_MATRIX).</p> + * + * @see CaptureRequest#COLOR_CORRECTION_GAINS + * @see CaptureRequest#COLOR_CORRECTION_MODE + * @see CaptureRequest#COLOR_CORRECTION_TRANSFORM + * @see CaptureRequest#CONTROL_AWB_MODE */ public static final Key<byte[]> CONTROL_AWB_AVAILABLE_MODES = new Key<byte[]>("android.control.awbAvailableModes", byte[].class); /** - * <p> - * For AE, AWB, and AF, how many individual - * regions can be listed for metering? - * </p> + * <p>List of the maximum number of regions that can be used for metering in + * auto-exposure (AE), auto-white balance (AWB), and auto-focus (AF); + * this corresponds to the the maximum number of elements in + * {@link CaptureRequest#CONTROL_AE_REGIONS android.control.aeRegions}, {@link CaptureRequest#CONTROL_AWB_REGIONS android.control.awbRegions}, + * and {@link CaptureRequest#CONTROL_AF_REGIONS android.control.afRegions}.</p> + * + * @see CaptureRequest#CONTROL_AE_REGIONS + * @see CaptureRequest#CONTROL_AF_REGIONS + * @see CaptureRequest#CONTROL_AWB_REGIONS */ - public static final Key<Integer> CONTROL_MAX_REGIONS = - new Key<Integer>("android.control.maxRegions", int.class); + public static final Key<int[]> CONTROL_MAX_REGIONS = + new Key<int[]>("android.control.maxRegions", int[].class); /** - * <p> - * Whether this camera has a - * flash - * </p> - * <p> - * If no flash, none of the flash controls do - * anything. All other metadata should return 0 - * </p> + * <p>Whether this camera device has a + * flash.</p> + * <p>If no flash, none of the flash controls do + * anything. All other metadata should return 0.</p> */ - public static final Key<Byte> FLASH_INFO_AVAILABLE = - new Key<Byte>("android.flash.info.available", byte.class); + public static final Key<Boolean> FLASH_INFO_AVAILABLE = + new Key<Boolean>("android.flash.info.available", boolean.class); /** - * <p> - * Supported resolutions for the JPEG - * thumbnail - * </p> + * <p>Supported resolutions for the JPEG thumbnail</p> + * <p>Below condiditions will be satisfied for this size list:</p> + * <ul> + * <li>The sizes will be sorted by increasing pixel area (width x height). + * If several resolutions have the same area, they will be sorted by increasing width.</li> + * <li>The aspect ratio of the largest thumbnail size will be same as the + * aspect ratio of largest JPEG output size in {@link CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS android.scaler.availableStreamConfigurations}. + * The largest size is defined as the size that has the largest pixel area + * in a given size list.</li> + * <li>Each output JPEG size in {@link CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS android.scaler.availableStreamConfigurations} will have at least + * one corresponding size that has the same aspect ratio in availableThumbnailSizes, + * and vice versa.</li> + * <li>All non (0, 0) sizes will have non-zero widths and heights.</li> + * </ul> + * + * @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS */ public static final Key<android.hardware.camera2.Size[]> JPEG_AVAILABLE_THUMBNAIL_SIZES = new Key<android.hardware.camera2.Size[]>("android.jpeg.availableThumbnailSizes", android.hardware.camera2.Size[].class); /** - * <p> - * List of supported aperture - * values - * </p> - * <p> - * If variable aperture not available, only setting - * should be for the fixed aperture - * </p> + * <p>List of supported aperture + * values.</p> + * <p>If the camera device doesn't support variable apertures, + * listed value will be the fixed aperture.</p> + * <p>If the camera device supports variable apertures, the aperture value + * in this list will be sorted in ascending order.</p> */ public static final Key<float[]> LENS_INFO_AVAILABLE_APERTURES = new Key<float[]>("android.lens.info.availableApertures", float[].class); /** - * <p> - * List of supported ND filter - * values - * </p> - * <p> - * If not available, only setting is 0. Otherwise, - * lists the available exposure index values for dimming - * (2 would mean the filter is set to reduce incoming - * light by two stops) - * </p> + * <p>List of supported neutral density filter values for + * {@link CaptureRequest#LENS_FILTER_DENSITY android.lens.filterDensity}.</p> + * <p>If changing {@link CaptureRequest#LENS_FILTER_DENSITY android.lens.filterDensity} is not supported, + * availableFilterDensities must contain only 0. Otherwise, this + * list contains only the exact filter density values available on + * this camera device.</p> + * + * @see CaptureRequest#LENS_FILTER_DENSITY */ public static final Key<float[]> LENS_INFO_AVAILABLE_FILTER_DENSITIES = new Key<float[]>("android.lens.info.availableFilterDensities", float[].class); /** - * <p> - * If fitted with optical zoom, what focal - * lengths are available. If not, the static focal - * length - * </p> - * <p> - * If optical zoom not supported, only one value - * should be reported - * </p> + * <p>The available focal lengths for this device for use with + * {@link CaptureRequest#LENS_FOCAL_LENGTH android.lens.focalLength}.</p> + * <p>If optical zoom is not supported, this will only report + * a single value corresponding to the static focal length of the + * device. Otherwise, this will report every focal length supported + * by the device.</p> + * + * @see CaptureRequest#LENS_FOCAL_LENGTH */ public static final Key<float[]> LENS_INFO_AVAILABLE_FOCAL_LENGTHS = new Key<float[]>("android.lens.info.availableFocalLengths", float[].class); /** - * <p> - * List of supported optical image - * stabilization modes - * </p> + * <p>List containing a subset of the optical image + * stabilization (OIS) modes specified in + * {@link CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE android.lens.opticalStabilizationMode}.</p> + * <p>If OIS is not implemented for a given camera device, this should + * contain only OFF.</p> + * + * @see CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE */ public static final Key<byte[]> LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION = new Key<byte[]>("android.lens.info.availableOpticalStabilization", byte[].class); /** - * <p> - * Hyperfocal distance for this lens; set to - * 0 if fixed focus - * </p> - * <p> - * The hyperfocal distance is used for the old - * API's 'fixed' setting - * </p> + * <p>Optional. Hyperfocal distance for this lens.</p> + * <p>If the lens is fixed focus, the camera device will report 0.</p> + * <p>If the lens is not fixed focus, the camera device will report this + * field when {@link CameraCharacteristics#LENS_INFO_FOCUS_DISTANCE_CALIBRATION android.lens.info.focusDistanceCalibration} is APPROXIMATE or CALIBRATED.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * + * @see CameraCharacteristics#LENS_INFO_FOCUS_DISTANCE_CALIBRATION */ public static final Key<Float> LENS_INFO_HYPERFOCAL_DISTANCE = new Key<Float>("android.lens.info.hyperfocalDistance", float.class); /** - * <p> - * Shortest distance from frontmost surface - * of the lens that can be focused correctly - * </p> - * <p> - * If the lens is fixed-focus, this should be - * 0 - * </p> + * <p>Shortest distance from frontmost surface + * of the lens that can be focused correctly.</p> + * <p>If the lens is fixed-focus, this should be + * 0.</p> */ public static final Key<Float> LENS_INFO_MINIMUM_FOCUS_DISTANCE = new Key<Float>("android.lens.info.minimumFocusDistance", float.class); /** - * <p> - * Dimensions of lens shading - * map - * </p> + * <p>Dimensions of lens shading map.</p> + * <p>The map should be on the order of 30-40 rows and columns, and + * must be smaller than 64x64.</p> */ public static final Key<android.hardware.camera2.Size> LENS_INFO_SHADING_MAP_SIZE = new Key<android.hardware.camera2.Size>("android.lens.info.shadingMapSize", android.hardware.camera2.Size.class); /** - * <p> - * Direction the camera faces relative to - * device screen - * </p> + * <p>The lens focus distance calibration quality.</p> + * <p>The lens focus distance calibration quality determines the reliability of + * focus related metadata entries, i.e. {@link CaptureRequest#LENS_FOCUS_DISTANCE android.lens.focusDistance}, + * {@link CaptureResult#LENS_FOCUS_RANGE android.lens.focusRange}, {@link CameraCharacteristics#LENS_INFO_HYPERFOCAL_DISTANCE android.lens.info.hyperfocalDistance}, and + * {@link CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE android.lens.info.minimumFocusDistance}.</p> + * + * @see CaptureRequest#LENS_FOCUS_DISTANCE + * @see CaptureResult#LENS_FOCUS_RANGE + * @see CameraCharacteristics#LENS_INFO_HYPERFOCAL_DISTANCE + * @see CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE + * @see #LENS_INFO_FOCUS_DISTANCE_CALIBRATION_UNCALIBRATED + * @see #LENS_INFO_FOCUS_DISTANCE_CALIBRATION_APPROXIMATE + * @see #LENS_INFO_FOCUS_DISTANCE_CALIBRATION_CALIBRATED + */ + public static final Key<Integer> LENS_INFO_FOCUS_DISTANCE_CALIBRATION = + new Key<Integer>("android.lens.info.focusDistanceCalibration", int.class); + + /** + * <p>Direction the camera faces relative to + * device screen</p> * @see #LENS_FACING_FRONT * @see #LENS_FACING_BACK */ @@ -333,292 +406,773 @@ public final class CameraCharacteristics extends CameraMetadata { new Key<Integer>("android.lens.facing", int.class); /** - * <p> - * If set to 1, the HAL will always split result + * <p>If set to 1, the HAL will always split result * metadata for a single capture into multiple buffers, - * returned using multiple process_capture_result calls. - * </p> - * <p> - * Does not need to be listed in static + * returned using multiple process_capture_result calls.</p> + * <p>Does not need to be listed in static * metadata. Support for partial results will be reworked in * future versions of camera service. This quirk will stop * working at that point; DO NOT USE without careful - * consideration of future support. - * </p> - * - * <b>Optional</b> - This value may be null on some devices. - * + * consideration of future support.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * @hide */ public static final Key<Byte> QUIRKS_USE_PARTIAL_RESULT = new Key<Byte>("android.quirks.usePartialResult", byte.class); /** - * <p> - * How many output streams can be allocated at - * the same time for each type of stream - * </p> - * <p> - * Video snapshot with preview callbacks requires 3 - * processed streams (preview, record, app callbacks) and - * one JPEG stream (snapshot) - * </p> + * <p>The maximum numbers of different types of output streams + * that can be configured and used simultaneously by a camera device.</p> + * <p>This is a 3 element tuple that contains the max number of output simultaneous + * streams for raw sensor, processed (and uncompressed), and JPEG formats respectively. + * For example, if max raw sensor format output stream number is 1, max YUV streams + * number is 3, and max JPEG stream number is 2, then this tuple should be <code>(1, 3, 2)</code>.</p> + * <p>This lists the upper bound of the number of output streams supported by + * the camera device. Using more streams simultaneously may require more hardware and + * CPU resources that will consume more power. The image format for a output stream can + * be any supported format provided by {@link CameraCharacteristics#SCALER_AVAILABLE_FORMATS android.scaler.availableFormats}. The formats + * defined in {@link CameraCharacteristics#SCALER_AVAILABLE_FORMATS android.scaler.availableFormats} can be catergorized into the 3 stream types + * as below:</p> + * <ul> + * <li>JPEG-compressed format: BLOB.</li> + * <li>Raw formats: RAW_SENSOR and RAW_OPAQUE.</li> + * <li>processed, uncompressed formats: YCbCr_420_888, YCrCb_420_SP, YV12.</li> + * </ul> + * + * @see CameraCharacteristics#SCALER_AVAILABLE_FORMATS */ public static final Key<int[]> REQUEST_MAX_NUM_OUTPUT_STREAMS = new Key<int[]>("android.request.maxNumOutputStreams", int[].class); /** - * <p> - * List of app-visible formats - * </p> + * <p>The maximum numbers of any type of input streams + * that can be configured and used simultaneously by a camera device.</p> + * <p>When set to 0, it means no input stream is supported.</p> + * <p>The image format for a input stream can be any supported + * format provided by + * {@link CameraCharacteristics#SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP android.scaler.availableInputOutputFormatsMap}. When using an + * input stream, there must be at least one output stream + * configured to to receive the reprocessed images.</p> + * <p>For example, for Zero Shutter Lag (ZSL) still capture use case, the input + * stream image format will be RAW_OPAQUE, the associated output stream image format + * should be JPEG.</p> + * + * @see CameraCharacteristics#SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP + */ + public static final Key<Integer> REQUEST_MAX_NUM_INPUT_STREAMS = + new Key<Integer>("android.request.maxNumInputStreams", int.class); + + /** + * <p>Specifies the number of maximum pipeline stages a frame + * has to go through from when it's exposed to when it's available + * to the framework.</p> + * <p>A typical minimum value for this is 2 (one stage to expose, + * one stage to readout) from the sensor. The ISP then usually adds + * its own stages to do custom HW processing. Further stages may be + * added by SW processing.</p> + * <p>Depending on what settings are used (e.g. YUV, JPEG) and what + * processing is enabled (e.g. face detection), the actual pipeline + * depth (specified by {@link CaptureResult#REQUEST_PIPELINE_DEPTH android.request.pipelineDepth}) may be less than + * the max pipeline depth.</p> + * <p>A pipeline depth of X stages is equivalent to a pipeline latency of + * X frame intervals.</p> + * <p>This value will be 8 or less.</p> + * + * @see CaptureResult#REQUEST_PIPELINE_DEPTH + */ + public static final Key<Byte> REQUEST_PIPELINE_MAX_DEPTH = + new Key<Byte>("android.request.pipelineMaxDepth", byte.class); + + /** + * <p>Optional. Defaults to 1. Defines how many sub-components + * a result will be composed of.</p> + * <p>In order to combat the pipeline latency, partial results + * may be delivered to the application layer from the camera device as + * soon as they are available.</p> + * <p>A value of 1 means that partial results are not supported.</p> + * <p>A typical use case for this might be: after requesting an AF lock the + * new AF state might be available 50% of the way through the pipeline. + * The camera device could then immediately dispatch this state via a + * partial result to the framework/application layer, and the rest of + * the metadata via later partial results.</p> + */ + public static final Key<Integer> REQUEST_PARTIAL_RESULT_COUNT = + new Key<Integer>("android.request.partialResultCount", int.class); + + /** + * <p>List of capabilities that the camera device + * advertises as fully supporting.</p> + * <p>A capability is a contract that the camera device makes in order + * to be able to satisfy one or more use cases.</p> + * <p>Listing a capability guarantees that the whole set of features + * required to support a common use will all be available.</p> + * <p>Using a subset of the functionality provided by an unsupported + * capability may be possible on a specific camera device implementation; + * to do this query each of android.request.availableRequestKeys, + * android.request.availableResultKeys, + * android.request.availableCharacteristicsKeys.</p> + * <p>XX: Maybe these should go into {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} + * as a table instead?</p> + * <p>The following capabilities are guaranteed to be available on + * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} <code>==</code> FULL devices:</p> + * <ul> + * <li>MANUAL_SENSOR</li> + * <li>ZSL</li> + * </ul> + * <p>Other capabilities may be available on either FULL or LIMITED + * devices, but the app. should query this field to be sure.</p> + * + * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + * @see #REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE + * @see #REQUEST_AVAILABLE_CAPABILITIES_OPTIONAL + * @see #REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR + * @see #REQUEST_AVAILABLE_CAPABILITIES_GCAM + * @see #REQUEST_AVAILABLE_CAPABILITIES_ZSL + * @see #REQUEST_AVAILABLE_CAPABILITIES_DNG + */ + public static final Key<Integer> REQUEST_AVAILABLE_CAPABILITIES = + new Key<Integer>("android.request.availableCapabilities", int.class); + + /** + * <p>A list of all keys that the camera device has available + * to use with CaptureRequest.</p> + * <p>Attempting to set a key into a CaptureRequest that is not + * listed here will result in an invalid request and will be rejected + * by the camera device.</p> + * <p>This field can be used to query the feature set of a camera device + * at a more granular level than capabilities. This is especially + * important for optional keys that are not listed under any capability + * in {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities}.</p> + * <p>TODO: This should be used by #getAvailableCaptureRequestKeys.</p> + * + * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + * @hide + */ + public static final Key<int[]> REQUEST_AVAILABLE_REQUEST_KEYS = + new Key<int[]>("android.request.availableRequestKeys", int[].class); + + /** + * <p>A list of all keys that the camera device has available + * to use with CaptureResult.</p> + * <p>Attempting to get a key from a CaptureResult that is not + * listed here will always return a <code>null</code> value. Getting a key from + * a CaptureResult that is listed here must never return a <code>null</code> + * value.</p> + * <p>The following keys may return <code>null</code> unless they are enabled:</p> + * <ul> + * <li>{@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap} (non-null iff {@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE android.statistics.lensShadingMapMode} == ON)</li> + * </ul> + * <p>(Those sometimes-null keys should nevertheless be listed here + * if they are available.)</p> + * <p>This field can be used to query the feature set of a camera device + * at a more granular level than capabilities. This is especially + * important for optional keys that are not listed under any capability + * in {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities}.</p> + * <p>TODO: This should be used by #getAvailableCaptureResultKeys.</p> + * + * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + * @see CaptureResult#STATISTICS_LENS_SHADING_MAP + * @see CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE + * @hide + */ + public static final Key<int[]> REQUEST_AVAILABLE_RESULT_KEYS = + new Key<int[]>("android.request.availableResultKeys", int[].class); + + /** + * <p>A list of all keys that the camera device has available + * to use with CameraCharacteristics.</p> + * <p>This entry follows the same rules as + * android.request.availableResultKeys (except that it applies for + * CameraCharacteristics instead of CaptureResult). See above for more + * details.</p> + * <p>TODO: This should be used by CameraCharacteristics#getKeys.</p> + * @hide + */ + public static final Key<int[]> REQUEST_AVAILABLE_CHARACTERISTICS_KEYS = + new Key<int[]>("android.request.availableCharacteristicsKeys", int[].class); + + /** + * <p>The list of image formats that are supported by this + * camera device for output streams.</p> + * <p>All camera devices will support JPEG and YUV_420_888 formats.</p> + * <p>When set to YUV_420_888, application can access the YUV420 data directly.</p> */ public static final Key<int[]> SCALER_AVAILABLE_FORMATS = new Key<int[]>("android.scaler.availableFormats", int[].class); /** - * <p> - * The minimum frame duration that is supported - * for each resolution in availableJpegSizes. Should - * correspond to the frame duration when only that JPEG - * stream is active and captured in a burst, with all - * processing set to FAST - * </p> - * <p> - * When multiple streams are configured, the minimum - * frame duration will be >= max(individual stream min - * durations) - * </p> + * <p>The minimum frame duration that is supported + * for each resolution in {@link CameraCharacteristics#SCALER_AVAILABLE_JPEG_SIZES android.scaler.availableJpegSizes}.</p> + * <p>This corresponds to the minimum steady-state frame duration when only + * that JPEG stream is active and captured in a burst, with all + * processing (typically in android.*.mode) set to FAST.</p> + * <p>When multiple streams are configured, the minimum + * frame duration will be >= max(individual stream min + * durations)</p> + * + * @see CameraCharacteristics#SCALER_AVAILABLE_JPEG_SIZES */ public static final Key<long[]> SCALER_AVAILABLE_JPEG_MIN_DURATIONS = new Key<long[]>("android.scaler.availableJpegMinDurations", long[].class); /** - * <p> - * The resolutions available for output from - * the JPEG block. Listed as width x height - * </p> + * <p>The JPEG resolutions that are supported by this camera device.</p> + * <p>The resolutions are listed as <code>(width, height)</code> pairs. All camera devices will support + * sensor maximum resolution (defined by {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}).</p> + * + * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE */ public static final Key<android.hardware.camera2.Size[]> SCALER_AVAILABLE_JPEG_SIZES = new Key<android.hardware.camera2.Size[]>("android.scaler.availableJpegSizes", android.hardware.camera2.Size[].class); /** - * <p> - * The maximum ratio between active area width + * <p>The maximum ratio between active area width * and crop region width, or between active area height and * crop region height, if the crop region height is larger - * than width - * </p> + * than width</p> */ public static final Key<Float> SCALER_AVAILABLE_MAX_DIGITAL_ZOOM = new Key<Float>("android.scaler.availableMaxDigitalZoom", float.class); /** - * <p> - * The minimum frame duration that is supported - * for each resolution in availableProcessedSizes. Should - * correspond to the frame duration when only that processed - * stream is active, with all processing set to - * FAST - * </p> - * <p> - * When multiple streams are configured, the minimum - * frame duration will be >= max(individual stream min - * durations) - * </p> + * <p>For each available processed output size (defined in + * {@link CameraCharacteristics#SCALER_AVAILABLE_PROCESSED_SIZES android.scaler.availableProcessedSizes}), this property lists the + * minimum supportable frame duration for that size.</p> + * <p>This should correspond to the frame duration when only that processed + * stream is active, with all processing (typically in android.*.mode) + * set to FAST.</p> + * <p>When multiple streams are configured, the minimum frame duration will + * be >= max(individual stream min durations).</p> + * + * @see CameraCharacteristics#SCALER_AVAILABLE_PROCESSED_SIZES */ public static final Key<long[]> SCALER_AVAILABLE_PROCESSED_MIN_DURATIONS = new Key<long[]>("android.scaler.availableProcessedMinDurations", long[].class); /** - * <p> - * The resolutions available for use with + * <p>The resolutions available for use with * processed output streams, such as YV12, NV12, and * platform opaque YUV/RGB streams to the GPU or video - * encoders. Listed as width, height - * </p> - * <p> - * The actual supported resolution list may be limited by - * consumer end points for different use cases. For example, for - * recording use case, the largest supported resolution may be - * limited by max supported size from encoder, for preview use - * case, the largest supported resolution may be limited by max - * resolution SurfaceTexture/SurfaceView can support. - * </p> + * encoders.</p> + * <p>The resolutions are listed as <code>(width, height)</code> pairs.</p> + * <p>For a given use case, the actual maximum supported resolution + * may be lower than what is listed here, depending on the destination + * Surface for the image data. For example, for recording video, + * the video encoder chosen may have a maximum size limit (e.g. 1080p) + * smaller than what the camera (e.g. maximum resolution is 3264x2448) + * can provide.</p> + * <p>Please reference the documentation for the image data destination to + * check if it limits the maximum size for image data.</p> */ public static final Key<android.hardware.camera2.Size[]> SCALER_AVAILABLE_PROCESSED_SIZES = new Key<android.hardware.camera2.Size[]>("android.scaler.availableProcessedSizes", android.hardware.camera2.Size[].class); /** - * <p> - * Area of raw data which corresponds to only - * active pixels; smaller or equal to - * pixelArraySize. - * </p> + * <p>The mapping of image formats that are supported by this + * camera device for input streams, to their corresponding output formats.</p> + * <p>All camera devices with at least 1 + * {@link CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS android.request.maxNumInputStreams} will have at least one + * available input format.</p> + * <p>The camera device will support the following map of formats, + * if its dependent capability is supported:</p> + * <table> + * <thead> + * <tr> + * <th align="left">Input Format</th> + * <th align="left">Output Format</th> + * <th align="left">Capability</th> + * </tr> + * </thead> + * <tbody> + * <tr> + * <td align="left">RAW_OPAQUE</td> + * <td align="left">JPEG</td> + * <td align="left">ZSL</td> + * </tr> + * <tr> + * <td align="left">RAW_OPAQUE</td> + * <td align="left">YUV_420_888</td> + * <td align="left">ZSL</td> + * </tr> + * <tr> + * <td align="left">RAW_OPAQUE</td> + * <td align="left">RAW16</td> + * <td align="left">DNG</td> + * </tr> + * <tr> + * <td align="left">RAW16</td> + * <td align="left">YUV_420_888</td> + * <td align="left">DNG</td> + * </tr> + * <tr> + * <td align="left">RAW16</td> + * <td align="left">JPEG</td> + * <td align="left">DNG</td> + * </tr> + * </tbody> + * </table> + * <p>For ZSL-capable camera devices, using the RAW_OPAQUE format + * as either input or output will never hurt maximum frame rate (i.e. + * {@link CameraCharacteristics#SCALER_AVAILABLE_STALL_DURATIONS android.scaler.availableStallDurations} will not have RAW_OPAQUE).</p> + * <p>Attempting to configure an input stream with output streams not + * listed as available in this map is not valid.</p> + * <p>TODO: Add java type mapping for this property.</p> + * + * @see CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS + * @see CameraCharacteristics#SCALER_AVAILABLE_STALL_DURATIONS + */ + public static final Key<int[]> SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP = + new Key<int[]>("android.scaler.availableInputOutputFormatsMap", int[].class); + + /** + * <p>The available stream configurations that this + * camera device supports + * (i.e. format, width, height, output/input stream).</p> + * <p>The configurations are listed as <code>(format, width, height, input?)</code> + * tuples.</p> + * <p>All camera devices will support sensor maximum resolution (defined by + * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}) for the JPEG format.</p> + * <p>For a given use case, the actual maximum supported resolution + * may be lower than what is listed here, depending on the destination + * Surface for the image data. For example, for recording video, + * the video encoder chosen may have a maximum size limit (e.g. 1080p) + * smaller than what the camera (e.g. maximum resolution is 3264x2448) + * can provide.</p> + * <p>Please reference the documentation for the image data destination to + * check if it limits the maximum size for image data.</p> + * <p>Not all output formats may be supported in a configuration with + * an input stream of a particular format. For more details, see + * {@link CameraCharacteristics#SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP android.scaler.availableInputOutputFormatsMap}.</p> + * <p>The following table describes the minimum required output stream + * configurations based on the hardware level + * ({@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel}):</p> + * <table> + * <thead> + * <tr> + * <th align="center">Format</th> + * <th align="center">Size</th> + * <th align="center">Hardware Level</th> + * <th align="center">Notes</th> + * </tr> + * </thead> + * <tbody> + * <tr> + * <td align="center">JPEG</td> + * <td align="center">{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</td> + * <td align="center">Any</td> + * <td align="center"></td> + * </tr> + * <tr> + * <td align="center">JPEG</td> + * <td align="center">1920x1080 (1080p)</td> + * <td align="center">Any</td> + * <td align="center">if 1080p <= activeArraySize</td> + * </tr> + * <tr> + * <td align="center">JPEG</td> + * <td align="center">1280x720 (720)</td> + * <td align="center">Any</td> + * <td align="center">if 720p <= activeArraySize</td> + * </tr> + * <tr> + * <td align="center">JPEG</td> + * <td align="center">640x480 (480p)</td> + * <td align="center">Any</td> + * <td align="center">if 480p <= activeArraySize</td> + * </tr> + * <tr> + * <td align="center">JPEG</td> + * <td align="center">320x240 (240p)</td> + * <td align="center">Any</td> + * <td align="center">if 240p <= activeArraySize</td> + * </tr> + * <tr> + * <td align="center">YUV_420_888</td> + * <td align="center">all output sizes available for JPEG</td> + * <td align="center">FULL</td> + * <td align="center"></td> + * </tr> + * <tr> + * <td align="center">YUV_420_888</td> + * <td align="center">all output sizes available for JPEG, up to the maximum video size</td> + * <td align="center">LIMITED</td> + * <td align="center"></td> + * </tr> + * <tr> + * <td align="center">IMPLEMENTATION_DEFINED</td> + * <td align="center">same as YUV_420_888</td> + * <td align="center">Any</td> + * <td align="center"></td> + * </tr> + * </tbody> + * </table> + * <p>Refer to {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} for additional + * mandatory stream configurations on a per-capability basis.</p> + * + * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + * @see CameraCharacteristics#SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP + * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE + * @see #SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT + * @see #SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT + */ + public static final Key<int[]> SCALER_AVAILABLE_STREAM_CONFIGURATIONS = + new Key<int[]>("android.scaler.availableStreamConfigurations", int[].class); + + /** + * <p>This lists the minimum frame duration for each + * format/size combination.</p> + * <p>This should correspond to the frame duration when only that + * stream is active, with all processing (typically in android.*.mode) + * set to either OFF or FAST.</p> + * <p>When multiple streams are used in a request, the minimum frame + * duration will be max(individual stream min durations).</p> + * <p>The minimum frame duration of a stream (of a particular format, size) + * is the same regardless of whether the stream is input or output.</p> + * <p>See {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} and + * {@link CameraCharacteristics#SCALER_AVAILABLE_STALL_DURATIONS android.scaler.availableStallDurations} for more details about + * calculating the max frame rate.</p> + * + * @see CameraCharacteristics#SCALER_AVAILABLE_STALL_DURATIONS + * @see CaptureRequest#SENSOR_FRAME_DURATION + */ + public static final Key<long[]> SCALER_AVAILABLE_MIN_FRAME_DURATIONS = + new Key<long[]>("android.scaler.availableMinFrameDurations", long[].class); + + /** + * <p>This lists the maximum stall duration for each + * format/size combination.</p> + * <p>A stall duration is how much extra time would get added + * to the normal minimum frame duration for a repeating request + * that has streams with non-zero stall.</p> + * <p>For example, consider JPEG captures which have the following + * characteristics:</p> + * <ul> + * <li>JPEG streams act like processed YUV streams in requests for which + * they are not included; in requests in which they are directly + * referenced, they act as JPEG streams. This is because supporting a + * JPEG stream requires the underlying YUV data to always be ready for + * use by a JPEG encoder, but the encoder will only be used (and impact + * frame duration) on requests that actually reference a JPEG stream.</li> + * <li>The JPEG processor can run concurrently to the rest of the camera + * pipeline, but cannot process more than 1 capture at a time.</li> + * </ul> + * <p>In other words, using a repeating YUV request would result + * in a steady frame rate (let's say it's 30 FPS). If a single + * JPEG request is submitted periodically, the frame rate will stay + * at 30 FPS (as long as we wait for the previous JPEG to return each + * time). If we try to submit a repeating YUV + JPEG request, then + * the frame rate will drop from 30 FPS.</p> + * <p>In general, submitting a new request with a non-0 stall time + * stream will <em>not</em> cause a frame rate drop unless there are still + * outstanding buffers for that stream from previous requests.</p> + * <p>Submitting a repeating request with streams (call this <code>S</code>) + * is the same as setting the minimum frame duration from + * the normal minimum frame duration corresponding to <code>S</code>, added with + * the maximum stall duration for <code>S</code>.</p> + * <p>If interleaving requests with and without a stall duration, + * a request will stall by the maximum of the remaining times + * for each can-stall stream with outstanding buffers.</p> + * <p>This means that a stalling request will not have an exposure start + * until the stall has completed.</p> + * <p>This should correspond to the stall duration when only that stream is + * active, with all processing (typically in android.*.mode) set to FAST + * or OFF. Setting any of the processing modes to HIGH_QUALITY + * effectively results in an indeterminate stall duration for all + * streams in a request (the regular stall calculation rules are + * ignored).</p> + * <p>The following formats may always have a stall duration:</p> + * <ul> + * <li>JPEG</li> + * <li>RAW16</li> + * </ul> + * <p>The following formats will never have a stall duration:</p> + * <ul> + * <li>YUV_420_888</li> + * <li>IMPLEMENTATION_DEFINED</li> + * </ul> + * <p>All other formats may or may not have an allowed stall duration on + * a per-capability basis; refer to {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} + * for more details.</p> + * <p>See {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} for more information about + * calculating the max frame rate (absent stalls).</p> + * + * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + * @see CaptureRequest#SENSOR_FRAME_DURATION + */ + public static final Key<long[]> SCALER_AVAILABLE_STALL_DURATIONS = + new Key<long[]>("android.scaler.availableStallDurations", long[].class); + + /** + * <p>Area of raw data which corresponds to only + * active pixels.</p> + * <p>It is smaller or equal to + * sensor full pixel array, which could include the black calibration pixels.</p> */ public static final Key<android.graphics.Rect> SENSOR_INFO_ACTIVE_ARRAY_SIZE = new Key<android.graphics.Rect>("android.sensor.info.activeArraySize", android.graphics.Rect.class); /** - * <p> - * Range of valid sensitivities - * </p> + * <p>Range of valid sensitivities</p> */ public static final Key<int[]> SENSOR_INFO_SENSITIVITY_RANGE = new Key<int[]>("android.sensor.info.sensitivityRange", int[].class); /** - * <p> - * Range of valid exposure - * times - * </p> + * <p>Range of valid exposure + * times used by {@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}.</p> + * + * @see CaptureRequest#SENSOR_EXPOSURE_TIME */ public static final Key<long[]> SENSOR_INFO_EXPOSURE_TIME_RANGE = new Key<long[]>("android.sensor.info.exposureTimeRange", long[].class); /** - * <p> - * Maximum possible frame duration (minimum frame - * rate) - * </p> - * <p> - * Minimum duration is a function of resolution, - * processing settings. See - * android.scaler.availableProcessedMinDurations - * android.scaler.availableJpegMinDurations - * android.scaler.availableRawMinDurations - * </p> + * <p>Maximum possible frame duration (minimum frame + * rate).</p> + * <p>The largest possible {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} + * that will be accepted by the camera device. Attempting to use + * frame durations beyond the maximum will result in the frame duration + * being clipped to the maximum. See that control + * for a full definition of frame durations.</p> + * <p>Refer to + * {@link CameraCharacteristics#SCALER_AVAILABLE_PROCESSED_MIN_DURATIONS android.scaler.availableProcessedMinDurations}, + * {@link CameraCharacteristics#SCALER_AVAILABLE_JPEG_MIN_DURATIONS android.scaler.availableJpegMinDurations}, and + * android.scaler.availableRawMinDurations for the minimum + * frame duration values.</p> + * + * @see CameraCharacteristics#SCALER_AVAILABLE_JPEG_MIN_DURATIONS + * @see CameraCharacteristics#SCALER_AVAILABLE_PROCESSED_MIN_DURATIONS + * @see CaptureRequest#SENSOR_FRAME_DURATION */ public static final Key<Long> SENSOR_INFO_MAX_FRAME_DURATION = new Key<Long>("android.sensor.info.maxFrameDuration", long.class); /** - * <p> - * The physical dimensions of the full pixel - * array - * </p> - * <p> - * Needed for FOV calculation for old API - * </p> + * <p>The physical dimensions of the full pixel + * array</p> + * <p>Needed for FOV calculation for old API</p> */ public static final Key<float[]> SENSOR_INFO_PHYSICAL_SIZE = new Key<float[]>("android.sensor.info.physicalSize", float[].class); /** - * <p> - * Gain factor from electrons to raw units when - * ISO=100 - * </p> + * <p>Dimensions of full pixel array, possibly + * including black calibration pixels.</p> + * <p>Maximum output resolution for raw format must + * match this in + * {@link CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS android.scaler.availableStreamConfigurations}.</p> * - * <b>Optional</b> - This value may be null on some devices. + * @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS + */ + public static final Key<android.hardware.camera2.Size> SENSOR_INFO_PIXEL_ARRAY_SIZE = + new Key<android.hardware.camera2.Size>("android.sensor.info.pixelArraySize", android.hardware.camera2.Size.class); + + /** + * <p>Maximum raw value output by sensor.</p> + * <p>This specifies the fully-saturated encoding level for the raw + * sample values from the sensor. This is typically caused by the + * sensor becoming highly non-linear or clipping. The minimum for + * each channel is specified by the offset in the + * {@link CameraCharacteristics#SENSOR_BLACK_LEVEL_PATTERN android.sensor.blackLevelPattern} tag.</p> + * <p>The white level is typically determined either by sensor bit depth + * (10-14 bits is expected), or by the point where the sensor response + * becomes too non-linear to be useful. The default value for this is + * maximum representable value for a 16-bit raw sample (2^16 - 1).</p> * - * <b>{@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL}</b> - - * Present on all devices that report being FULL level hardware devices in the - * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL HARDWARE_LEVEL} key. + * @see CameraCharacteristics#SENSOR_BLACK_LEVEL_PATTERN + */ + public static final Key<Integer> SENSOR_INFO_WHITE_LEVEL = + new Key<Integer>("android.sensor.info.whiteLevel", int.class); + + /** + * <p>Gain factor from electrons to raw units when + * ISO=100</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * <p><b>Full capability</b> - + * Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the + * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p> + * + * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL */ public static final Key<Rational> SENSOR_BASE_GAIN_FACTOR = new Key<Rational>("android.sensor.baseGainFactor", Rational.class); /** - * <p> - * Maximum sensitivity that is implemented - * purely through analog gain - * </p> - * <p> - * For android.sensor.sensitivity values less than or - * equal to this, all applied gain must be analog. For - * values above this, it can be a mix of analog and - * digital - * </p> + * <p>A fixed black level offset for each of the color filter arrangement + * (CFA) mosaic channels.</p> + * <p>This tag specifies the zero light value for each of the CFA mosaic + * channels in the camera sensor. The maximal value output by the + * sensor is represented by the value in {@link CameraCharacteristics#SENSOR_INFO_WHITE_LEVEL android.sensor.info.whiteLevel}.</p> + * <p>The values are given in row-column scan order, with the first value + * corresponding to the element of the CFA in row=0, column=0.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * - * <b>Optional</b> - This value may be null on some devices. + * @see CameraCharacteristics#SENSOR_INFO_WHITE_LEVEL + */ + public static final Key<int[]> SENSOR_BLACK_LEVEL_PATTERN = + new Key<int[]>("android.sensor.blackLevelPattern", int[].class); + + /** + * <p>Maximum sensitivity that is implemented + * purely through analog gain.</p> + * <p>For {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity} values less than or + * equal to this, all applied gain must be analog. For + * values above this, the gain applied can be a mix of analog and + * digital.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * <p><b>Full capability</b> - + * Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the + * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p> * - * <b>{@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL}</b> - - * Present on all devices that report being FULL level hardware devices in the - * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL HARDWARE_LEVEL} key. + * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + * @see CaptureRequest#SENSOR_SENSITIVITY */ public static final Key<Integer> SENSOR_MAX_ANALOG_SENSITIVITY = new Key<Integer>("android.sensor.maxAnalogSensitivity", int.class); /** - * <p> - * Clockwise angle through which the output + * <p>Clockwise angle through which the output * image needs to be rotated to be upright on the device * screen in its native orientation. Also defines the * direction of rolling shutter readout, which is from top - * to bottom in the sensor's coordinate system - * </p> + * to bottom in the sensor's coordinate system</p> */ public static final Key<Integer> SENSOR_ORIENTATION = new Key<Integer>("android.sensor.orientation", int.class); /** - * <p> - * Which face detection modes are available, - * if any - * </p> - * <p> - * OFF means face detection is disabled, it must - * be included in the list. - * </p><p> - * SIMPLE means the device supports the + * <p>The number of input samples for each dimension of + * {@link CaptureResult#SENSOR_PROFILE_HUE_SAT_MAP android.sensor.profileHueSatMap}.</p> + * <p>The number of input samples for the hue, saturation, and value + * dimension of {@link CaptureResult#SENSOR_PROFILE_HUE_SAT_MAP android.sensor.profileHueSatMap}. The order of the + * dimensions given is hue, saturation, value; where hue is the 0th + * element.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * + * @see CaptureResult#SENSOR_PROFILE_HUE_SAT_MAP + */ + public static final Key<int[]> SENSOR_PROFILE_HUE_SAT_MAP_DIMENSIONS = + new Key<int[]>("android.sensor.profileHueSatMapDimensions", int[].class); + + /** + * <p>Optional. Defaults to [OFF]. Lists the supported test + * pattern modes for {@link CaptureRequest#SENSOR_TEST_PATTERN_MODE android.sensor.testPatternMode}.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * + * @see CaptureRequest#SENSOR_TEST_PATTERN_MODE + */ + public static final Key<Byte> SENSOR_AVAILABLE_TEST_PATTERN_MODES = + new Key<Byte>("android.sensor.availableTestPatternModes", byte.class); + + /** + * <p>Which face detection modes are available, + * if any</p> + * <p>OFF means face detection is disabled, it must + * be included in the list.</p> + * <p>SIMPLE means the device supports the * android.statistics.faceRectangles and - * android.statistics.faceScores outputs. - * </p><p> - * FULL means the device additionally supports the + * android.statistics.faceScores outputs.</p> + * <p>FULL means the device additionally supports the * android.statistics.faceIds and - * android.statistics.faceLandmarks outputs. - * </p> + * android.statistics.faceLandmarks outputs.</p> */ public static final Key<byte[]> STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES = new Key<byte[]>("android.statistics.info.availableFaceDetectModes", byte[].class); /** - * <p> - * Maximum number of simultaneously detectable - * faces - * </p> + * <p>Maximum number of simultaneously detectable + * faces</p> */ public static final Key<Integer> STATISTICS_INFO_MAX_FACE_COUNT = new Key<Integer>("android.statistics.info.maxFaceCount", int.class); /** - * <p> - * Maximum number of supported points in the - * tonemap curve - * </p> + * <p>Maximum number of supported points in the + * tonemap curve that can be used for {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed}, or + * {@link CaptureRequest#TONEMAP_CURVE_GREEN android.tonemap.curveGreen}, or {@link CaptureRequest#TONEMAP_CURVE_BLUE android.tonemap.curveBlue}.</p> + * <p>If the actual number of points provided by the application (in + * android.tonemap.curve*) is less than max, the camera device will + * resample the curve to its internal representation, using linear + * interpolation.</p> + * <p>The output curves in the result metadata may have a different number + * of points than the input curves, and will represent the actual + * hardware curves used as closely as possible when linearly interpolated.</p> + * + * @see CaptureRequest#TONEMAP_CURVE_BLUE + * @see CaptureRequest#TONEMAP_CURVE_GREEN + * @see CaptureRequest#TONEMAP_CURVE_RED */ public static final Key<Integer> TONEMAP_MAX_CURVE_POINTS = new Key<Integer>("android.tonemap.maxCurvePoints", int.class); /** - * <p> - * A list of camera LEDs that are available on this system. - * </p> + * <p>A list of camera LEDs that are available on this system.</p> * @see #LED_AVAILABLE_LEDS_TRANSMIT - * * @hide */ public static final Key<int[]> LED_AVAILABLE_LEDS = new Key<int[]>("android.led.availableLeds", int[].class); /** - * <p> - * The camera 3 HAL device can implement one of two possible - * operational modes; limited and full. Full support is - * expected from new higher-end devices. Limited mode has - * hardware requirements roughly in line with those for a - * camera HAL device v1 implementation, and is expected from - * older or inexpensive devices. Full is a strict superset of - * limited, and they share the same essential operational flow. - * </p><p> - * For full details refer to "S3. Operational Modes" in camera3.h - * </p> + * <p>Generally classifies the overall set of the camera device functionality.</p> + * <p>Camera devices will come in two flavors: LIMITED and FULL.</p> + * <p>A FULL device has the most support possible and will enable the + * widest range of use cases such as:</p> + * <ul> + * <li>30 FPS at maximum resolution (== sensor resolution)</li> + * <li>Per frame control</li> + * <li>Manual sensor control</li> + * <li>Zero Shutter Lag (ZSL)</li> + * </ul> + * <p>A LIMITED device may have some or none of the above characteristics. + * To find out more refer to {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities}.</p> + * + * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES * @see #INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED * @see #INFO_SUPPORTED_HARDWARE_LEVEL_FULL */ public static final Key<Integer> INFO_SUPPORTED_HARDWARE_LEVEL = new Key<Integer>("android.info.supportedHardwareLevel", int.class); + /** + * <p>The maximum number of frames that can occur after a request + * (different than the previous) has been submitted, and before the + * result's state becomes synchronized (by setting + * android.sync.frameNumber to a non-negative value).</p> + * <p>This defines the maximum distance (in number of metadata results), + * between android.sync.frameNumber and the equivalent + * android.request.frameCount.</p> + * <p>In other words this acts as an upper boundary for how many frames + * must occur before the camera device knows for a fact that the new + * submitted camera settings have been applied in outgoing frames.</p> + * <p>For example if the distance was 2,</p> + * <pre><code>initial request = X (repeating) + * request1 = X + * request2 = Y + * request3 = Y + * request4 = Y + * + * where requestN has frameNumber N, and the first of the repeating + * initial request's has frameNumber F (and F < 1). + * + * initial result = X' + { android.sync.frameNumber == F } + * result1 = X' + { android.sync.frameNumber == F } + * result2 = X' + { android.sync.frameNumber == CONVERGING } + * result3 = X' + { android.sync.frameNumber == CONVERGING } + * result4 = X' + { android.sync.frameNumber == 2 } + * + * where resultN has frameNumber N. + * </code></pre> + * <p>Since <code>result4</code> has a <code>frameNumber == 4</code> and + * <code>android.sync.frameNumber == 2</code>, the distance is clearly + * <code>4 - 2 = 2</code>.</p> + * @see #SYNC_MAX_LATENCY_PER_FRAME_CONTROL + * @see #SYNC_MAX_LATENCY_UNKNOWN + */ + public static final Key<Integer> SYNC_MAX_LATENCY = + new Key<Integer>("android.sync.maxLatency", int.class); + /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ * End generated code *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/ diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index 9e8d7d1..2c53f03 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -92,7 +92,6 @@ public interface CameraDevice extends AutoCloseable { * AE/AWB/AF should be on auto mode. * * @see #createCaptureRequest - * @hide */ public static final int TEMPLATE_ZERO_SHUTTER_LAG = 5; @@ -105,7 +104,6 @@ public interface CameraDevice extends AutoCloseable { * application depending on the intended use case. * * @see #createCaptureRequest - * @hide */ public static final int TEMPLATE_MANUAL = 6; @@ -501,31 +499,6 @@ public interface CameraDevice extends AutoCloseable { public void stopRepeating() throws CameraAccessException; /** - * <p>Wait until all the submitted requests have finished processing</p> - * - * <p>This method blocks until all the requests that have been submitted to - * the camera device, either through {@link #capture capture}, - * {@link #captureBurst captureBurst}, - * {@link #setRepeatingRequest setRepeatingRequest}, or - * {@link #setRepeatingBurst setRepeatingBurst}, have completed their - * processing.</p> - * - * <p>Once this call returns successfully, the device is in an idle state, - * and can be reconfigured with {@link #configureOutputs configureOutputs}.</p> - * - * <p>This method cannot be used if there is an active repeating request or - * burst, set with {@link #setRepeatingRequest setRepeatingRequest} or - * {@link #setRepeatingBurst setRepeatingBurst}. Call - * {@link #stopRepeating stopRepeating} before calling this method.</p> - * - * @throws CameraAccessException if the camera device is no longer connected - * @throws IllegalStateException if the camera device has been closed, the - * device has encountered a fatal error, or if there is an active repeating - * request or burst. - */ - public void waitUntilIdle() throws CameraAccessException; - - /** * Flush all captures currently pending and in-progress as fast as * possible. * diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 65b6c7a..78e7037 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -19,7 +19,6 @@ package android.hardware.camera2; import android.content.Context; import android.hardware.ICameraService; import android.hardware.ICameraServiceListener; -import android.hardware.IProCameraUser; import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.utils.CameraBinderDecorator; import android.hardware.camera2.utils.CameraRuntimeException; @@ -49,6 +48,8 @@ import java.util.ArrayList; */ public final class CameraManager { + private static final String TAG = "CameraManager"; + /** * This should match the ICameraService definition */ @@ -80,6 +81,19 @@ public final class CameraManager { mCameraService = CameraBinderDecorator.newInstance(cameraServiceRaw); try { + int err = CameraMetadataNative.nativeSetupGlobalVendorTagDescriptor(); + if (err == CameraBinderDecorator.EOPNOTSUPP) { + Log.w(TAG, "HAL version doesn't vendor tags."); + } else { + CameraBinderDecorator.throwOnError(CameraMetadataNative. + nativeSetupGlobalVendorTagDescriptor()); + } + } catch(CameraRuntimeException e) { + throw new IllegalStateException("Failed to setup camera vendor tags", + e.asChecked()); + } + + try { mCameraService.addListener(new CameraServiceListener()); } catch(CameraRuntimeException e) { throw new IllegalStateException("Failed to register a camera service listener", diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index 1d6ff7d..a62df0f 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -168,7 +168,7 @@ public abstract class CameraMetadata { Key lhs = (Key) o; - return mName.equals(lhs.mName); + return mName.equals(lhs.mName) && mType.equals(lhs.mType); } /** @@ -206,6 +206,45 @@ public abstract class CameraMetadata { *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~*/ // + // Enumeration values for CameraCharacteristics#LENS_INFO_FOCUS_DISTANCE_CALIBRATION + // + + /** + * <p>The lens focus distance is not accurate, and the units used for + * {@link CaptureRequest#LENS_FOCUS_DISTANCE android.lens.focusDistance} do not correspond to any physical units. + * Setting the lens to the same focus distance on separate occasions may + * result in a different real focus distance, depending on factors such + * as the orientation of the device, the age of the focusing mechanism, + * and the device temperature. The focus distance value will still be + * in the range of <code>[0, {@link CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE android.lens.info.minimumFocusDistance}]</code>, where 0 + * represents the farthest focus.</p> + * + * @see CaptureRequest#LENS_FOCUS_DISTANCE + * @see CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE + * @see CameraCharacteristics#LENS_INFO_FOCUS_DISTANCE_CALIBRATION + */ + public static final int LENS_INFO_FOCUS_DISTANCE_CALIBRATION_UNCALIBRATED = 0; + + /** + * <p>The lens focus distance is measured in diopters. However, setting the lens + * to the same focus distance on separate occasions may result in a + * different real focus distance, depending on factors such as the + * orientation of the device, the age of the focusing mechanism, and + * the device temperature.</p> + * @see CameraCharacteristics#LENS_INFO_FOCUS_DISTANCE_CALIBRATION + */ + public static final int LENS_INFO_FOCUS_DISTANCE_CALIBRATION_APPROXIMATE = 1; + + /** + * <p>The lens focus distance is measured in diopters. The lens mechanism is + * calibrated so that setting the same focus distance is repeatable on + * multiple occasions with good accuracy, and the focus distance corresponds + * to the real physical distance to the plane of best focus.</p> + * @see CameraCharacteristics#LENS_INFO_FOCUS_DISTANCE_CALIBRATION + */ + public static final int LENS_INFO_FOCUS_DISTANCE_CALIBRATION_CALIBRATED = 2; + + // // Enumeration values for CameraCharacteristics#LENS_FACING // @@ -220,13 +259,177 @@ public abstract class CameraMetadata { public static final int LENS_FACING_BACK = 1; // + // Enumeration values for CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + // + + /** + * <p>The minimal set of capabilities that every camera + * device (regardless of {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel}) + * will support.</p> + * <p>The full set of features supported by this capability makes + * the camera2 api backwards compatible with the camera1 + * (android.hardware.Camera) API.</p> + * <p>TODO: @hide this. Doesn't really mean anything except + * act as a catch-all for all the 'base' functionality.</p> + * + * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + */ + public static final int REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE = 0; + + /** + * <p>This is a catch-all capability to include all other + * tags or functionality not encapsulated by one of the other + * capabilities.</p> + * <p>A typical example is all tags marked 'optional'.</p> + * <p>TODO: @hide. We may not need this if we @hide all the optional + * tags not belonging to a capability.</p> + * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + */ + public static final int REQUEST_AVAILABLE_CAPABILITIES_OPTIONAL = 1; + + /** + * <p>The camera device can be manually controlled (3A algorithms such + * as auto exposure, and auto focus can be + * bypassed), this includes but is not limited to:</p> + * <ul> + * <li>Manual exposure control<ul> + * <li>{@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}</li> + * <li>{@link CameraCharacteristics#SENSOR_INFO_EXPOSURE_TIME_RANGE android.sensor.info.exposureTimeRange}</li> + * </ul> + * </li> + * <li>Manual sensitivity control<ul> + * <li>{@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}</li> + * <li>{@link CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE android.sensor.info.sensitivityRange}</li> + * <li>{@link CameraCharacteristics#SENSOR_BASE_GAIN_FACTOR android.sensor.baseGainFactor}</li> + * </ul> + * </li> + * <li>Manual lens control<ul> + * <li>android.lens.*</li> + * </ul> + * </li> + * <li>Manual flash control<ul> + * <li>android.flash.*</li> + * </ul> + * </li> + * <li>Manual black level locking<ul> + * <li>{@link CaptureRequest#BLACK_LEVEL_LOCK android.blackLevel.lock}</li> + * </ul> + * </li> + * </ul> + * <p>If any of the above 3A algorithms are enabled, then the camera + * device will accurately report the values applied by 3A in the + * result.</p> + * + * @see CaptureRequest#BLACK_LEVEL_LOCK + * @see CameraCharacteristics#SENSOR_BASE_GAIN_FACTOR + * @see CaptureRequest#SENSOR_EXPOSURE_TIME + * @see CameraCharacteristics#SENSOR_INFO_EXPOSURE_TIME_RANGE + * @see CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE + * @see CaptureRequest#SENSOR_SENSITIVITY + * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + */ + public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR = 2; + + /** + * <p>TODO: This should be @hide</p> + * <ul> + * <li>Manual tonemap control<ul> + * <li>{@link CaptureRequest#TONEMAP_CURVE_BLUE android.tonemap.curveBlue}</li> + * <li>{@link CaptureRequest#TONEMAP_CURVE_GREEN android.tonemap.curveGreen}</li> + * <li>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed}</li> + * <li>{@link CaptureRequest#TONEMAP_MODE android.tonemap.mode}</li> + * <li>{@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}</li> + * </ul> + * </li> + * <li>Manual white balance control<ul> + * <li>{@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform}</li> + * <li>{@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains}</li> + * </ul> + * </li> + * <li>Lens shading map information<ul> + * <li>{@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap}</li> + * <li>{@link CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE android.lens.info.shadingMapSize}</li> + * </ul> + * </li> + * </ul> + * <p>If auto white balance is enabled, then the camera device + * will accurately report the values applied by AWB in the result.</p> + * <p>The camera device will also support everything in MANUAL_SENSOR + * except manual lens control and manual flash control.</p> + * + * @see CaptureRequest#COLOR_CORRECTION_GAINS + * @see CaptureRequest#COLOR_CORRECTION_TRANSFORM + * @see CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE + * @see CaptureResult#STATISTICS_LENS_SHADING_MAP + * @see CaptureRequest#TONEMAP_CURVE_BLUE + * @see CaptureRequest#TONEMAP_CURVE_GREEN + * @see CaptureRequest#TONEMAP_CURVE_RED + * @see CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS + * @see CaptureRequest#TONEMAP_MODE + * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + */ + public static final int REQUEST_AVAILABLE_CAPABILITIES_GCAM = 3; + + /** + * <p>The camera device supports the Zero Shutter Lag use case.</p> + * <ul> + * <li>At least one input stream can be used.</li> + * <li>RAW_OPAQUE is supported as an output/input format</li> + * <li>Using RAW_OPAQUE does not cause a frame rate drop + * relative to the sensor's maximum capture rate (at that + * resolution).</li> + * <li>RAW_OPAQUE will be reprocessable into both YUV_420_888 + * and JPEG formats.</li> + * <li>The maximum available resolution for RAW_OPAQUE streams + * (both input/output) will match the maximum available + * resolution of JPEG streams.</li> + * </ul> + * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + */ + public static final int REQUEST_AVAILABLE_CAPABILITIES_ZSL = 4; + + /** + * <p>The camera device supports outputting RAW buffers that can be + * saved offline into a DNG format. It can reprocess DNG + * files (produced from the same camera device) back into YUV.</p> + * <ul> + * <li>At least one input stream can be used.</li> + * <li>RAW16 is supported as output/input format.</li> + * <li>RAW16 is reprocessable into both YUV_420_888 and JPEG + * formats.</li> + * <li>The maximum available resolution for RAW16 streams (both + * input/output) will match the value in + * {@link CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE android.sensor.info.pixelArraySize}.</li> + * <li>All DNG-related optional metadata entries are provided + * by the camera device.</li> + * </ul> + * + * @see CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE + * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + */ + public static final int REQUEST_AVAILABLE_CAPABILITIES_DNG = 5; + + // + // Enumeration values for CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS + // + + /** + * @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS + */ + public static final int SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT = 0; + + /** + * @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS + */ + public static final int SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT = 1; + + // // Enumeration values for CameraCharacteristics#LED_AVAILABLE_LEDS // /** - * <p> - * android.led.transmit control is used - * </p> + * <p>android.led.transmit control is used</p> * @see CameraCharacteristics#LED_AVAILABLE_LEDS * @hide */ @@ -247,32 +450,75 @@ public abstract class CameraMetadata { public static final int INFO_SUPPORTED_HARDWARE_LEVEL_FULL = 1; // + // Enumeration values for CameraCharacteristics#SYNC_MAX_LATENCY + // + + /** + * <p>Every frame has the requests immediately applied. + * (and furthermore for all results, + * <code>android.sync.frameNumber == android.request.frameCount</code>)</p> + * <p>Changing controls over multiple requests one after another will + * produce results that have those controls applied atomically + * each frame.</p> + * <p>All FULL capability devices will have this as their maxLatency.</p> + * @see CameraCharacteristics#SYNC_MAX_LATENCY + */ + public static final int SYNC_MAX_LATENCY_PER_FRAME_CONTROL = 0; + + /** + * <p>Each new frame has some subset (potentially the entire set) + * of the past requests applied to the camera settings.</p> + * <p>By submitting a series of identical requests, the camera device + * will eventually have the camera settings applied, but it is + * unknown when that exact point will be.</p> + * @see CameraCharacteristics#SYNC_MAX_LATENCY + */ + public static final int SYNC_MAX_LATENCY_UNKNOWN = -1; + + // // Enumeration values for CaptureRequest#COLOR_CORRECTION_MODE // /** - * <p> - * Use the android.colorCorrection.transform matrix - * and android.colorCorrection.gains to do color conversion - * </p> + * <p>Use the {@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform} matrix + * and {@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains} to do color conversion.</p> + * <p>All advanced white balance adjustments (not specified + * by our white balance pipeline) must be disabled.</p> + * <p>If AWB is enabled with <code>{@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode} != OFF</code>, then + * TRANSFORM_MATRIX is ignored. The camera device will override + * this value to either FAST or HIGH_QUALITY.</p> + * + * @see CaptureRequest#COLOR_CORRECTION_GAINS + * @see CaptureRequest#COLOR_CORRECTION_TRANSFORM + * @see CaptureRequest#CONTROL_AWB_MODE * @see CaptureRequest#COLOR_CORRECTION_MODE */ public static final int COLOR_CORRECTION_MODE_TRANSFORM_MATRIX = 0; /** - * <p> - * Must not slow down frame rate relative to raw - * bayer output - * </p> + * <p>Must not slow down capture rate relative to sensor raw + * output.</p> + * <p>Advanced white balance adjustments above and beyond + * the specified white balance pipeline may be applied.</p> + * <p>If AWB is enabled with <code>{@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode} != OFF</code>, then + * the camera device uses the last frame's AWB values + * (or defaults if AWB has never been run).</p> + * + * @see CaptureRequest#CONTROL_AWB_MODE * @see CaptureRequest#COLOR_CORRECTION_MODE */ public static final int COLOR_CORRECTION_MODE_FAST = 1; /** - * <p> - * Frame rate may be reduced by high - * quality - * </p> + * <p>Capture rate (relative to sensor raw output) + * may be reduced by high quality.</p> + * <p>Advanced white balance adjustments above and beyond + * the specified white balance pipeline may be applied.</p> + * <p>If AWB is enabled with <code>{@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode} != OFF</code>, then + * the camera device uses the last frame's AWB values + * (or defaults if AWB has never been run).</p> + * + * @see CaptureRequest#CONTROL_AWB_MODE * @see CaptureRequest#COLOR_CORRECTION_MODE */ public static final int COLOR_CORRECTION_MODE_HIGH_QUALITY = 2; @@ -282,21 +528,31 @@ public abstract class CameraMetadata { // /** + * <p>The camera device will not adjust exposure duration to + * avoid banding problems.</p> * @see CaptureRequest#CONTROL_AE_ANTIBANDING_MODE */ public static final int CONTROL_AE_ANTIBANDING_MODE_OFF = 0; /** + * <p>The camera device will adjust exposure duration to + * avoid banding problems with 50Hz illumination sources.</p> * @see CaptureRequest#CONTROL_AE_ANTIBANDING_MODE */ public static final int CONTROL_AE_ANTIBANDING_MODE_50HZ = 1; /** + * <p>The camera device will adjust exposure duration to + * avoid banding problems with 60Hz illumination + * sources.</p> * @see CaptureRequest#CONTROL_AE_ANTIBANDING_MODE */ public static final int CONTROL_AE_ANTIBANDING_MODE_60HZ = 2; /** + * <p>The camera device will automatically adapt its + * antibanding routine to the current illumination + * conditions. This is the default.</p> * @see CaptureRequest#CONTROL_AE_ANTIBANDING_MODE */ public static final int CONTROL_AE_ANTIBANDING_MODE_AUTO = 3; @@ -306,52 +562,73 @@ public abstract class CameraMetadata { // /** - * <p> - * Autoexposure is disabled; sensor.exposureTime, - * sensor.sensitivity and sensor.frameDuration are used - * </p> + * <p>The camera device's autoexposure routine is disabled; + * the application-selected {@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}, + * {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity} and + * {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} are used by the camera + * device, along with android.flash.* fields, if there's + * a flash unit for this camera device.</p> + * + * @see CaptureRequest#SENSOR_EXPOSURE_TIME + * @see CaptureRequest#SENSOR_FRAME_DURATION + * @see CaptureRequest#SENSOR_SENSITIVITY * @see CaptureRequest#CONTROL_AE_MODE */ public static final int CONTROL_AE_MODE_OFF = 0; /** - * <p> - * Autoexposure is active, no flash - * control - * </p> + * <p>The camera device's autoexposure routine is active, + * with no flash control. The application's values for + * {@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}, + * {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}, and + * {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} are ignored. The + * application has control over the various + * android.flash.* fields.</p> + * + * @see CaptureRequest#SENSOR_EXPOSURE_TIME + * @see CaptureRequest#SENSOR_FRAME_DURATION + * @see CaptureRequest#SENSOR_SENSITIVITY * @see CaptureRequest#CONTROL_AE_MODE */ public static final int CONTROL_AE_MODE_ON = 1; /** - * <p> - * if flash exists Autoexposure is active, auto - * flash control; flash may be fired when precapture - * trigger is activated, and for captures for which - * captureIntent = STILL_CAPTURE - * </p> + * <p>Like ON, except that the camera device also controls + * the camera's flash unit, firing it in low-light + * conditions. The flash may be fired during a + * precapture sequence (triggered by + * {@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger}) and may be fired + * for captures for which the + * {@link CaptureRequest#CONTROL_CAPTURE_INTENT android.control.captureIntent} field is set to + * STILL_CAPTURE</p> + * + * @see CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER + * @see CaptureRequest#CONTROL_CAPTURE_INTENT * @see CaptureRequest#CONTROL_AE_MODE */ public static final int CONTROL_AE_MODE_ON_AUTO_FLASH = 2; /** - * <p> - * if flash exists Autoexposure is active, auto - * flash control for precapture trigger and always flash - * when captureIntent = STILL_CAPTURE - * </p> + * <p>Like ON, except that the camera device also controls + * the camera's flash unit, always firing it for still + * captures. The flash may be fired during a precapture + * sequence (triggered by + * {@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger}) and will always + * be fired for captures for which the + * {@link CaptureRequest#CONTROL_CAPTURE_INTENT android.control.captureIntent} field is set to + * STILL_CAPTURE</p> + * + * @see CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER + * @see CaptureRequest#CONTROL_CAPTURE_INTENT * @see CaptureRequest#CONTROL_AE_MODE */ public static final int CONTROL_AE_MODE_ON_ALWAYS_FLASH = 3; /** - * <p> - * optional Automatic red eye reduction with flash. - * If deemed necessary, red eye reduction sequence should - * fire when precapture trigger is activated, and final - * flash should fire when captureIntent = - * STILL_CAPTURE - * </p> + * <p>Like ON_AUTO_FLASH, but with automatic red eye + * reduction. If deemed necessary by the camera device, + * a red eye reduction flash will fire during the + * precapture sequence.</p> * @see CaptureRequest#CONTROL_AE_MODE */ public static final int CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE = 4; @@ -361,20 +638,15 @@ public abstract class CameraMetadata { // /** - * <p> - * The trigger is idle. - * </p> + * <p>The trigger is idle.</p> * @see CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER */ public static final int CONTROL_AE_PRECAPTURE_TRIGGER_IDLE = 0; /** - * <p> - * The precapture metering sequence - * must be started. The exact effect of the precapture - * trigger depends on the current AE mode and - * state. - * </p> + * <p>The precapture metering sequence will be started + * by the camera device. The exact effect of the precapture + * trigger depends on the current AE mode and state.</p> * @see CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER */ public static final int CONTROL_AE_PRECAPTURE_TRIGGER_START = 1; @@ -384,55 +656,47 @@ public abstract class CameraMetadata { // /** - * <p> - * The 3A routines do not control the lens; - * android.lens.focusDistance is controlled by the - * application - * </p> + * <p>The auto-focus routine does not control the lens; + * {@link CaptureRequest#LENS_FOCUS_DISTANCE android.lens.focusDistance} is controlled by the + * application</p> + * + * @see CaptureRequest#LENS_FOCUS_DISTANCE * @see CaptureRequest#CONTROL_AF_MODE */ public static final int CONTROL_AF_MODE_OFF = 0; /** - * <p> - * if lens is not fixed focus. - * </p><p> - * Use android.lens.minimumFocusDistance to determine if lens - * is fixed focus In this mode, the lens does not move unless + * <p>If lens is not fixed focus.</p> + * <p>Use {@link CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE android.lens.info.minimumFocusDistance} to determine if lens + * is fixed-focus. In this mode, the lens does not move unless * the autofocus trigger action is called. When that trigger * is activated, AF must transition to ACTIVE_SCAN, then to - * the outcome of the scan (FOCUSED or - * NOT_FOCUSED). - * </p><p> - * Triggering cancel AF resets the lens position to default, - * and sets the AF state to INACTIVE. - * </p> + * the outcome of the scan (FOCUSED or NOT_FOCUSED).</p> + * <p>Triggering AF_CANCEL resets the lens position to default, + * and sets the AF state to INACTIVE.</p> + * + * @see CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE * @see CaptureRequest#CONTROL_AF_MODE */ public static final int CONTROL_AF_MODE_AUTO = 1; /** - * <p> - * In this mode, the lens does not move unless the - * autofocus trigger action is called. - * </p><p> - * When that trigger is activated, AF must transition to + * <p>In this mode, the lens does not move unless the + * autofocus trigger action is called.</p> + * <p>When that trigger is activated, AF must transition to * ACTIVE_SCAN, then to the outcome of the scan (FOCUSED or * NOT_FOCUSED). Triggering cancel AF resets the lens * position to default, and sets the AF state to - * INACTIVE. - * </p> + * INACTIVE.</p> * @see CaptureRequest#CONTROL_AF_MODE */ public static final int CONTROL_AF_MODE_MACRO = 2; /** - * <p> - * In this mode, the AF algorithm modifies the lens + * <p>In this mode, the AF algorithm modifies the lens * position continually to attempt to provide a - * constantly-in-focus image stream. - * </p><p> - * The focusing behavior should be suitable for good quality + * constantly-in-focus image stream.</p> + * <p>The focusing behavior should be suitable for good quality * video recording; typically this means slower focus * movement and no overshoots. When the AF trigger is not * involved, the AF algorithm should start in INACTIVE state, @@ -440,25 +704,21 @@ public abstract class CameraMetadata { * states as appropriate. When the AF trigger is activated, * the algorithm should immediately transition into * AF_FOCUSED or AF_NOT_FOCUSED as appropriate, and lock the - * lens position until a cancel AF trigger is received. - * </p><p> - * Once cancel is received, the algorithm should transition + * lens position until a cancel AF trigger is received.</p> + * <p>Once cancel is received, the algorithm should transition * back to INACTIVE and resume passive scan. Note that this * behavior is not identical to CONTINUOUS_PICTURE, since an * ongoing PASSIVE_SCAN must immediately be - * canceled. - * </p> + * canceled.</p> * @see CaptureRequest#CONTROL_AF_MODE */ public static final int CONTROL_AF_MODE_CONTINUOUS_VIDEO = 3; /** - * <p> - * In this mode, the AF algorithm modifies the lens + * <p>In this mode, the AF algorithm modifies the lens * position continually to attempt to provide a - * constantly-in-focus image stream. - * </p><p> - * The focusing behavior should be suitable for still image + * constantly-in-focus image stream.</p> + * <p>The focusing behavior should be suitable for still image * capture; typically this means focusing as fast as * possible. When the AF trigger is not involved, the AF * algorithm should start in INACTIVE state, and then @@ -467,22 +727,18 @@ public abstract class CameraMetadata { * trigger is activated, the algorithm should finish its * PASSIVE_SCAN if active, and then transition into * AF_FOCUSED or AF_NOT_FOCUSED as appropriate, and lock the - * lens position until a cancel AF trigger is received. - * </p><p> - * When the AF cancel trigger is activated, the algorithm + * lens position until a cancel AF trigger is received.</p> + * <p>When the AF cancel trigger is activated, the algorithm * should transition back to INACTIVE and then act as if it - * has just been started. - * </p> + * has just been started.</p> * @see CaptureRequest#CONTROL_AF_MODE */ public static final int CONTROL_AF_MODE_CONTINUOUS_PICTURE = 4; /** - * <p> - * Extended depth of field (digital focus). AF + * <p>Extended depth of field (digital focus). AF * trigger is ignored, AF state should always be - * INACTIVE. - * </p> + * INACTIVE.</p> * @see CaptureRequest#CONTROL_AF_MODE */ public static final int CONTROL_AF_MODE_EDOF = 5; @@ -492,26 +748,20 @@ public abstract class CameraMetadata { // /** - * <p> - * The trigger is idle. - * </p> + * <p>The trigger is idle.</p> * @see CaptureRequest#CONTROL_AF_TRIGGER */ public static final int CONTROL_AF_TRIGGER_IDLE = 0; /** - * <p> - * Autofocus must trigger now. - * </p> + * <p>Autofocus will trigger now.</p> * @see CaptureRequest#CONTROL_AF_TRIGGER */ public static final int CONTROL_AF_TRIGGER_START = 1; /** - * <p> - * Autofocus must return to initial - * state, and cancel any active trigger. - * </p> + * <p>Autofocus will return to its initial + * state, and cancel any currently active trigger.</p> * @see CaptureRequest#CONTROL_AF_TRIGGER */ public static final int CONTROL_AF_TRIGGER_CANCEL = 2; @@ -521,46 +771,89 @@ public abstract class CameraMetadata { // /** + * <p>The camera device's auto white balance routine is disabled; + * the application-selected color transform matrix + * ({@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform}) and gains + * ({@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains}) are used by the camera + * device for manual white balance control.</p> + * + * @see CaptureRequest#COLOR_CORRECTION_GAINS + * @see CaptureRequest#COLOR_CORRECTION_TRANSFORM * @see CaptureRequest#CONTROL_AWB_MODE */ public static final int CONTROL_AWB_MODE_OFF = 0; /** + * <p>The camera device's auto white balance routine is active; + * the application's values for {@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform} + * and {@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains} are ignored.</p> + * + * @see CaptureRequest#COLOR_CORRECTION_GAINS + * @see CaptureRequest#COLOR_CORRECTION_TRANSFORM * @see CaptureRequest#CONTROL_AWB_MODE */ public static final int CONTROL_AWB_MODE_AUTO = 1; /** + * <p>The camera device's auto white balance routine is disabled; + * the camera device uses incandescent light as the assumed scene + * illumination for white balance. While the exact white balance + * transforms are up to the camera device, they will approximately + * match the CIE standard illuminant A.</p> * @see CaptureRequest#CONTROL_AWB_MODE */ public static final int CONTROL_AWB_MODE_INCANDESCENT = 2; /** + * <p>The camera device's auto white balance routine is disabled; + * the camera device uses fluorescent light as the assumed scene + * illumination for white balance. While the exact white balance + * transforms are up to the camera device, they will approximately + * match the CIE standard illuminant F2.</p> * @see CaptureRequest#CONTROL_AWB_MODE */ public static final int CONTROL_AWB_MODE_FLUORESCENT = 3; /** + * <p>The camera device's auto white balance routine is disabled; + * the camera device uses warm fluorescent light as the assumed scene + * illumination for white balance. While the exact white balance + * transforms are up to the camera device, they will approximately + * match the CIE standard illuminant F4.</p> * @see CaptureRequest#CONTROL_AWB_MODE */ public static final int CONTROL_AWB_MODE_WARM_FLUORESCENT = 4; /** + * <p>The camera device's auto white balance routine is disabled; + * the camera device uses daylight light as the assumed scene + * illumination for white balance. While the exact white balance + * transforms are up to the camera device, they will approximately + * match the CIE standard illuminant D65.</p> * @see CaptureRequest#CONTROL_AWB_MODE */ public static final int CONTROL_AWB_MODE_DAYLIGHT = 5; /** + * <p>The camera device's auto white balance routine is disabled; + * the camera device uses cloudy daylight light as the assumed scene + * illumination for white balance.</p> * @see CaptureRequest#CONTROL_AWB_MODE */ public static final int CONTROL_AWB_MODE_CLOUDY_DAYLIGHT = 6; /** + * <p>The camera device's auto white balance routine is disabled; + * the camera device uses twilight light as the assumed scene + * illumination for white balance.</p> * @see CaptureRequest#CONTROL_AWB_MODE */ public static final int CONTROL_AWB_MODE_TWILIGHT = 7; /** + * <p>The camera device's auto white balance routine is disabled; + * the camera device uses shade light as the assumed scene + * illumination for white balance.</p> * @see CaptureRequest#CONTROL_AWB_MODE */ public static final int CONTROL_AWB_MODE_SHADE = 8; @@ -570,59 +863,47 @@ public abstract class CameraMetadata { // /** - * <p> - * This request doesn't fall into the other + * <p>This request doesn't fall into the other * categories. Default to preview-like - * behavior. - * </p> + * behavior.</p> * @see CaptureRequest#CONTROL_CAPTURE_INTENT */ public static final int CONTROL_CAPTURE_INTENT_CUSTOM = 0; /** - * <p> - * This request is for a preview-like usecase. The + * <p>This request is for a preview-like usecase. The * precapture trigger may be used to start off a metering - * w/flash sequence - * </p> + * w/flash sequence</p> * @see CaptureRequest#CONTROL_CAPTURE_INTENT */ public static final int CONTROL_CAPTURE_INTENT_PREVIEW = 1; /** - * <p> - * This request is for a still capture-type - * usecase. - * </p> + * <p>This request is for a still capture-type + * usecase.</p> * @see CaptureRequest#CONTROL_CAPTURE_INTENT */ public static final int CONTROL_CAPTURE_INTENT_STILL_CAPTURE = 2; /** - * <p> - * This request is for a video recording - * usecase. - * </p> + * <p>This request is for a video recording + * usecase.</p> * @see CaptureRequest#CONTROL_CAPTURE_INTENT */ public static final int CONTROL_CAPTURE_INTENT_VIDEO_RECORD = 3; /** - * <p> - * This request is for a video snapshot (still - * image while recording video) usecase - * </p> + * <p>This request is for a video snapshot (still + * image while recording video) usecase</p> * @see CaptureRequest#CONTROL_CAPTURE_INTENT */ public static final int CONTROL_CAPTURE_INTENT_VIDEO_SNAPSHOT = 4; /** - * <p> - * This request is for a ZSL usecase; the + * <p>This request is for a ZSL usecase; the * application will stream full-resolution images and * reprocess one or several later for a final - * capture - * </p> + * capture</p> * @see CaptureRequest#CONTROL_CAPTURE_INTENT */ public static final int CONTROL_CAPTURE_INTENT_ZERO_SHUTTER_LAG = 5; @@ -632,46 +913,64 @@ public abstract class CameraMetadata { // /** + * <p>No color effect will be applied.</p> * @see CaptureRequest#CONTROL_EFFECT_MODE */ public static final int CONTROL_EFFECT_MODE_OFF = 0; /** + * <p>A "monocolor" effect where the image is mapped into + * a single color. This will typically be grayscale.</p> * @see CaptureRequest#CONTROL_EFFECT_MODE */ public static final int CONTROL_EFFECT_MODE_MONO = 1; /** + * <p>A "photo-negative" effect where the image's colors + * are inverted.</p> * @see CaptureRequest#CONTROL_EFFECT_MODE */ public static final int CONTROL_EFFECT_MODE_NEGATIVE = 2; /** + * <p>A "solarisation" effect (Sabattier effect) where the + * image is wholly or partially reversed in + * tone.</p> * @see CaptureRequest#CONTROL_EFFECT_MODE */ public static final int CONTROL_EFFECT_MODE_SOLARIZE = 3; /** + * <p>A "sepia" effect where the image is mapped into warm + * gray, red, and brown tones.</p> * @see CaptureRequest#CONTROL_EFFECT_MODE */ public static final int CONTROL_EFFECT_MODE_SEPIA = 4; /** + * <p>A "posterization" effect where the image uses + * discrete regions of tone rather than a continuous + * gradient of tones.</p> * @see CaptureRequest#CONTROL_EFFECT_MODE */ public static final int CONTROL_EFFECT_MODE_POSTERIZE = 5; /** + * <p>A "whiteboard" effect where the image is typically displayed + * as regions of white, with black or grey details.</p> * @see CaptureRequest#CONTROL_EFFECT_MODE */ public static final int CONTROL_EFFECT_MODE_WHITEBOARD = 6; /** + * <p>A "blackboard" effect where the image is typically displayed + * as regions of black, with white or grey details.</p> * @see CaptureRequest#CONTROL_EFFECT_MODE */ public static final int CONTROL_EFFECT_MODE_BLACKBOARD = 7; /** + * <p>An "aqua" effect where a blue hue is added to the image.</p> * @see CaptureRequest#CONTROL_EFFECT_MODE */ public static final int CONTROL_EFFECT_MODE_AQUA = 8; @@ -681,137 +980,166 @@ public abstract class CameraMetadata { // /** - * <p> - * Full application control of pipeline. All 3A + * <p>Full application control of pipeline. All 3A * routines are disabled, no other settings in - * android.control.* have any effect - * </p> + * android.control.* have any effect</p> * @see CaptureRequest#CONTROL_MODE */ public static final int CONTROL_MODE_OFF = 0; /** - * <p> - * Use settings for each individual 3A routine. + * <p>Use settings for each individual 3A routine. * Manual control of capture parameters is disabled. All * controls in android.control.* besides sceneMode take - * effect - * </p> + * effect</p> * @see CaptureRequest#CONTROL_MODE */ public static final int CONTROL_MODE_AUTO = 1; /** - * <p> - * Use specific scene mode. Enabling this disables + * <p>Use specific scene mode. Enabling this disables * control.aeMode, control.awbMode and control.afMode - * controls; the HAL must ignore those settings while + * controls; the camera device will ignore those settings while * USE_SCENE_MODE is active (except for FACE_PRIORITY * scene mode). Other control entries are still active. * This setting can only be used if availableSceneModes != - * UNSUPPORTED - * </p> + * UNSUPPORTED</p> * @see CaptureRequest#CONTROL_MODE */ public static final int CONTROL_MODE_USE_SCENE_MODE = 2; + /** + * <p>Same as OFF mode, except that this capture will not be + * used by camera device background auto-exposure, auto-white balance and + * auto-focus algorithms to update their statistics.</p> + * @see CaptureRequest#CONTROL_MODE + */ + public static final int CONTROL_MODE_OFF_KEEP_STATE = 3; + // // Enumeration values for CaptureRequest#CONTROL_SCENE_MODE // /** + * <p>Indicates that no scene modes are set for a given capture request.</p> * @see CaptureRequest#CONTROL_SCENE_MODE */ - public static final int CONTROL_SCENE_MODE_UNSUPPORTED = 0; + public static final int CONTROL_SCENE_MODE_DISABLED = 0; /** - * <p> - * if face detection support exists Use face - * detection data to drive 3A routines. If face detection - * statistics are disabled, should still operate correctly - * (but not return face detection statistics to the - * framework). - * </p><p> - * Unlike the other scene modes, aeMode, awbMode, and afMode - * remain active when FACE_PRIORITY is set. This is due to - * compatibility concerns with the old camera - * API - * </p> + * <p>If face detection support exists, use face + * detection data for auto-focus, auto-white balance, and + * auto-exposure routines. If face detection statistics are + * disabled (i.e. {@link CaptureRequest#STATISTICS_FACE_DETECT_MODE android.statistics.faceDetectMode} is set to OFF), + * this should still operate correctly (but will not return + * face detection statistics to the framework).</p> + * <p>Unlike the other scene modes, {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode}, + * {@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode}, and {@link CaptureRequest#CONTROL_AF_MODE android.control.afMode} + * remain active when FACE_PRIORITY is set.</p> + * + * @see CaptureRequest#CONTROL_AE_MODE + * @see CaptureRequest#CONTROL_AF_MODE + * @see CaptureRequest#CONTROL_AWB_MODE + * @see CaptureRequest#STATISTICS_FACE_DETECT_MODE * @see CaptureRequest#CONTROL_SCENE_MODE */ public static final int CONTROL_SCENE_MODE_FACE_PRIORITY = 1; /** + * <p>Optimized for photos of quickly moving objects. + * Similar to SPORTS.</p> * @see CaptureRequest#CONTROL_SCENE_MODE */ public static final int CONTROL_SCENE_MODE_ACTION = 2; /** + * <p>Optimized for still photos of people.</p> * @see CaptureRequest#CONTROL_SCENE_MODE */ public static final int CONTROL_SCENE_MODE_PORTRAIT = 3; /** + * <p>Optimized for photos of distant macroscopic objects.</p> * @see CaptureRequest#CONTROL_SCENE_MODE */ public static final int CONTROL_SCENE_MODE_LANDSCAPE = 4; /** + * <p>Optimized for low-light settings.</p> * @see CaptureRequest#CONTROL_SCENE_MODE */ public static final int CONTROL_SCENE_MODE_NIGHT = 5; /** + * <p>Optimized for still photos of people in low-light + * settings.</p> * @see CaptureRequest#CONTROL_SCENE_MODE */ public static final int CONTROL_SCENE_MODE_NIGHT_PORTRAIT = 6; /** + * <p>Optimized for dim, indoor settings where flash must + * remain off.</p> * @see CaptureRequest#CONTROL_SCENE_MODE */ public static final int CONTROL_SCENE_MODE_THEATRE = 7; /** + * <p>Optimized for bright, outdoor beach settings.</p> * @see CaptureRequest#CONTROL_SCENE_MODE */ public static final int CONTROL_SCENE_MODE_BEACH = 8; /** + * <p>Optimized for bright, outdoor settings containing snow.</p> * @see CaptureRequest#CONTROL_SCENE_MODE */ public static final int CONTROL_SCENE_MODE_SNOW = 9; /** + * <p>Optimized for scenes of the setting sun.</p> * @see CaptureRequest#CONTROL_SCENE_MODE */ public static final int CONTROL_SCENE_MODE_SUNSET = 10; /** + * <p>Optimized to avoid blurry photos due to small amounts of + * device motion (for example: due to hand shake).</p> * @see CaptureRequest#CONTROL_SCENE_MODE */ public static final int CONTROL_SCENE_MODE_STEADYPHOTO = 11; /** + * <p>Optimized for nighttime photos of fireworks.</p> * @see CaptureRequest#CONTROL_SCENE_MODE */ public static final int CONTROL_SCENE_MODE_FIREWORKS = 12; /** + * <p>Optimized for photos of quickly moving people. + * Similar to ACTION.</p> * @see CaptureRequest#CONTROL_SCENE_MODE */ public static final int CONTROL_SCENE_MODE_SPORTS = 13; /** + * <p>Optimized for dim, indoor settings with multiple moving + * people.</p> * @see CaptureRequest#CONTROL_SCENE_MODE */ public static final int CONTROL_SCENE_MODE_PARTY = 14; /** + * <p>Optimized for dim settings where the main light source + * is a flame.</p> * @see CaptureRequest#CONTROL_SCENE_MODE */ public static final int CONTROL_SCENE_MODE_CANDLELIGHT = 15; /** + * <p>Optimized for accurately capturing a photo of barcode + * for use by camera applications that wish to read the + * barcode value.</p> * @see CaptureRequest#CONTROL_SCENE_MODE */ public static final int CONTROL_SCENE_MODE_BARCODE = 16; @@ -821,27 +1149,21 @@ public abstract class CameraMetadata { // /** - * <p> - * No edge enhancement is applied - * </p> + * <p>No edge enhancement is applied</p> * @see CaptureRequest#EDGE_MODE */ public static final int EDGE_MODE_OFF = 0; /** - * <p> - * Must not slow down frame rate relative to raw - * bayer output - * </p> + * <p>Must not slow down frame rate relative to sensor + * output</p> * @see CaptureRequest#EDGE_MODE */ public static final int EDGE_MODE_FAST = 1; /** - * <p> - * Frame rate may be reduced by high - * quality - * </p> + * <p>Frame rate may be reduced by high + * quality</p> * @see CaptureRequest#EDGE_MODE */ public static final int EDGE_MODE_HIGH_QUALITY = 2; @@ -851,44 +1173,65 @@ public abstract class CameraMetadata { // /** - * <p> - * Do not fire the flash for this - * capture - * </p> + * <p>Do not fire the flash for this capture.</p> * @see CaptureRequest#FLASH_MODE */ public static final int FLASH_MODE_OFF = 0; /** - * <p> - * if android.flash.available is true Fire flash - * for this capture based on firingPower, - * firingTime. - * </p> + * <p>If the flash is available and charged, fire flash + * for this capture based on android.flash.firingPower and + * android.flash.firingTime.</p> * @see CaptureRequest#FLASH_MODE */ public static final int FLASH_MODE_SINGLE = 1; /** - * <p> - * if android.flash.available is true Flash - * continuously on, power set by - * firingPower - * </p> + * <p>Transition flash to continuously on.</p> * @see CaptureRequest#FLASH_MODE */ public static final int FLASH_MODE_TORCH = 2; // + // Enumeration values for CaptureRequest#HOT_PIXEL_MODE + // + + /** + * <p>The frame rate must not be reduced relative to sensor raw output + * for this option.</p> + * <p>No hot pixel correction is applied.</p> + * @see CaptureRequest#HOT_PIXEL_MODE + */ + public static final int HOT_PIXEL_MODE_OFF = 0; + + /** + * <p>The frame rate must not be reduced relative to sensor raw output + * for this option.</p> + * <p>Hot pixel correction is applied.</p> + * @see CaptureRequest#HOT_PIXEL_MODE + */ + public static final int HOT_PIXEL_MODE_FAST = 1; + + /** + * <p>The frame rate may be reduced relative to sensor raw output + * for this option.</p> + * <p>A high-quality hot pixel correction is applied.</p> + * @see CaptureRequest#HOT_PIXEL_MODE + */ + public static final int HOT_PIXEL_MODE_HIGH_QUALITY = 2; + + // // Enumeration values for CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE // /** + * <p>Optical stabilization is unavailable.</p> * @see CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE */ public static final int LENS_OPTICAL_STABILIZATION_MODE_OFF = 0; /** + * <p>Optical stabilization is enabled.</p> * @see CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE */ public static final int LENS_OPTICAL_STABILIZATION_MODE_ON = 1; @@ -898,32 +1241,153 @@ public abstract class CameraMetadata { // /** - * <p> - * No noise reduction is applied - * </p> + * <p>No noise reduction is applied</p> * @see CaptureRequest#NOISE_REDUCTION_MODE */ public static final int NOISE_REDUCTION_MODE_OFF = 0; /** - * <p> - * Must not slow down frame rate relative to raw - * bayer output - * </p> + * <p>Must not slow down frame rate relative to sensor + * output</p> * @see CaptureRequest#NOISE_REDUCTION_MODE */ public static final int NOISE_REDUCTION_MODE_FAST = 1; /** - * <p> - * May slow down frame rate to provide highest - * quality - * </p> + * <p>May slow down frame rate to provide highest + * quality</p> * @see CaptureRequest#NOISE_REDUCTION_MODE */ public static final int NOISE_REDUCTION_MODE_HIGH_QUALITY = 2; // + // Enumeration values for CaptureRequest#SENSOR_TEST_PATTERN_MODE + // + + /** + * <p>Default. No test pattern mode is used, and the camera + * device returns captures from the image sensor.</p> + * @see CaptureRequest#SENSOR_TEST_PATTERN_MODE + */ + public static final int SENSOR_TEST_PATTERN_MODE_OFF = 0; + + /** + * <p>Each pixel in <code>[R, G_even, G_odd, B]</code> is replaced by its + * respective color channel provided in + * {@link CaptureRequest#SENSOR_TEST_PATTERN_DATA android.sensor.testPatternData}.</p> + * <p>For example:</p> + * <pre><code>android.testPatternData = [0, 0xFFFFFFFF, 0xFFFFFFFF, 0] + * </code></pre> + * <p>All green pixels are 100% green. All red/blue pixels are black.</p> + * <pre><code>android.testPatternData = [0xFFFFFFFF, 0, 0xFFFFFFFF, 0] + * </code></pre> + * <p>All red pixels are 100% red. Only the odd green pixels + * are 100% green. All blue pixels are 100% black.</p> + * + * @see CaptureRequest#SENSOR_TEST_PATTERN_DATA + * @see CaptureRequest#SENSOR_TEST_PATTERN_MODE + */ + public static final int SENSOR_TEST_PATTERN_MODE_SOLID_COLOR = 1; + + /** + * <p>All pixel data is replaced with an 8-bar color pattern.</p> + * <p>The vertical bars (left-to-right) are as follows:</p> + * <ul> + * <li>100% white</li> + * <li>yellow</li> + * <li>cyan</li> + * <li>green</li> + * <li>magenta</li> + * <li>red</li> + * <li>blue</li> + * <li>black</li> + * </ul> + * <p>In general the image would look like the following:</p> + * <pre><code>W Y C G M R B K + * W Y C G M R B K + * W Y C G M R B K + * W Y C G M R B K + * W Y C G M R B K + * . . . . . . . . + * . . . . . . . . + * . . . . . . . . + * + * (B = Blue, K = Black) + * </code></pre> + * <p>Each bar should take up 1/8 of the sensor pixel array width. + * When this is not possible, the bar size should be rounded + * down to the nearest integer and the pattern can repeat + * on the right side.</p> + * <p>Each bar's height must always take up the full sensor + * pixel array height.</p> + * <p>Each pixel in this test pattern must be set to either + * 0% intensity or 100% intensity.</p> + * @see CaptureRequest#SENSOR_TEST_PATTERN_MODE + */ + public static final int SENSOR_TEST_PATTERN_MODE_COLOR_BARS = 2; + + /** + * <p>The test pattern is similar to COLOR_BARS, except that + * each bar should start at its specified color at the top, + * and fade to gray at the bottom.</p> + * <p>Furthermore each bar is further subdivided into a left and + * right half. The left half should have a smooth gradient, + * and the right half should have a quantized gradient.</p> + * <p>In particular, the right half's should consist of blocks of the + * same color for 1/16th active sensor pixel array width.</p> + * <p>The least significant bits in the quantized gradient should + * be copied from the most significant bits of the smooth gradient.</p> + * <p>The height of each bar should always be a multiple of 128. + * When this is not the case, the pattern should repeat at the bottom + * of the image.</p> + * @see CaptureRequest#SENSOR_TEST_PATTERN_MODE + */ + public static final int SENSOR_TEST_PATTERN_MODE_COLOR_BARS_FADE_TO_GRAY = 3; + + /** + * <p>All pixel data is replaced by a pseudo-random sequence + * generated from a PN9 512-bit sequence (typically implemented + * in hardware with a linear feedback shift register).</p> + * <p>The generator should be reset at the beginning of each frame, + * and thus each subsequent raw frame with this test pattern should + * be exactly the same as the last.</p> + * @see CaptureRequest#SENSOR_TEST_PATTERN_MODE + */ + public static final int SENSOR_TEST_PATTERN_MODE_PN9 = 4; + + /** + * <p>The first custom test pattern. All custom patterns that are + * available only on this camera device are at least this numeric + * value.</p> + * <p>All of the custom test patterns will be static + * (that is the raw image must not vary from frame to frame).</p> + * @see CaptureRequest#SENSOR_TEST_PATTERN_MODE + */ + public static final int SENSOR_TEST_PATTERN_MODE_CUSTOM1 = 256; + + // + // Enumeration values for CaptureRequest#SHADING_MODE + // + + /** + * <p>No lens shading correction is applied</p> + * @see CaptureRequest#SHADING_MODE + */ + public static final int SHADING_MODE_OFF = 0; + + /** + * <p>Must not slow down frame rate relative to sensor raw output</p> + * @see CaptureRequest#SHADING_MODE + */ + public static final int SHADING_MODE_FAST = 1; + + /** + * <p>Frame rate may be reduced by high quality</p> + * @see CaptureRequest#SHADING_MODE + */ + public static final int SHADING_MODE_HIGH_QUALITY = 2; + + // // Enumeration values for CaptureRequest#STATISTICS_FACE_DETECT_MODE // @@ -933,19 +1397,15 @@ public abstract class CameraMetadata { public static final int STATISTICS_FACE_DETECT_MODE_OFF = 0; /** - * <p> - * Optional Return rectangle and confidence - * only - * </p> + * <p>Optional Return rectangle and confidence + * only</p> * @see CaptureRequest#STATISTICS_FACE_DETECT_MODE */ public static final int STATISTICS_FACE_DETECT_MODE_SIMPLE = 1; /** - * <p> - * Optional Return all face - * metadata - * </p> + * <p>Optional Return all face + * metadata</p> * @see CaptureRequest#STATISTICS_FACE_DETECT_MODE */ public static final int STATISTICS_FACE_DETECT_MODE_FULL = 2; @@ -969,28 +1429,32 @@ public abstract class CameraMetadata { // /** - * <p> - * Use the tone mapping curve specified in - * android.tonemap.curve - * </p> + * <p>Use the tone mapping curve specified in + * the android.tonemap.curve* entries.</p> + * <p>All color enhancement and tonemapping must be disabled, except + * for applying the tonemapping curve specified by + * {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed}, {@link CaptureRequest#TONEMAP_CURVE_BLUE android.tonemap.curveBlue}, or + * {@link CaptureRequest#TONEMAP_CURVE_GREEN android.tonemap.curveGreen}.</p> + * <p>Must not slow down frame rate relative to raw + * sensor output.</p> + * + * @see CaptureRequest#TONEMAP_CURVE_BLUE + * @see CaptureRequest#TONEMAP_CURVE_GREEN + * @see CaptureRequest#TONEMAP_CURVE_RED * @see CaptureRequest#TONEMAP_MODE */ public static final int TONEMAP_MODE_CONTRAST_CURVE = 0; /** - * <p> - * Must not slow down frame rate relative to raw - * bayer output - * </p> + * <p>Advanced gamma mapping and color enhancement may be applied.</p> + * <p>Should not slow down frame rate relative to raw sensor output.</p> * @see CaptureRequest#TONEMAP_MODE */ public static final int TONEMAP_MODE_FAST = 1; /** - * <p> - * Frame rate may be reduced by high - * quality - * </p> + * <p>Advanced gamma mapping and color enhancement may be applied.</p> + * <p>May slow down frame rate relative to raw sensor output.</p> * @see CaptureRequest#TONEMAP_MODE */ public static final int TONEMAP_MODE_HIGH_QUALITY = 2; @@ -1000,60 +1464,51 @@ public abstract class CameraMetadata { // /** - * <p> - * AE is off. When a camera device is opened, it starts in - * this state. - * </p> + * <p>AE is off or recently reset. When a camera device is opened, it starts in + * this state. This is a transient state, the camera device may skip reporting + * this state in capture result.</p> * @see CaptureResult#CONTROL_AE_STATE */ public static final int CONTROL_AE_STATE_INACTIVE = 0; /** - * <p> - * AE doesn't yet have a good set of control values - * for the current scene - * </p> + * <p>AE doesn't yet have a good set of control values + * for the current scene. This is a transient state, the camera device may skip + * reporting this state in capture result.</p> * @see CaptureResult#CONTROL_AE_STATE */ public static final int CONTROL_AE_STATE_SEARCHING = 1; /** - * <p> - * AE has a good set of control values for the - * current scene - * </p> + * <p>AE has a good set of control values for the + * current scene.</p> * @see CaptureResult#CONTROL_AE_STATE */ public static final int CONTROL_AE_STATE_CONVERGED = 2; /** - * <p> - * AE has been locked (aeMode = - * LOCKED) - * </p> + * <p>AE has been locked.</p> * @see CaptureResult#CONTROL_AE_STATE */ public static final int CONTROL_AE_STATE_LOCKED = 3; /** - * <p> - * AE has a good set of control values, but flash + * <p>AE has a good set of control values, but flash * needs to be fired for good quality still - * capture - * </p> + * capture.</p> * @see CaptureResult#CONTROL_AE_STATE */ public static final int CONTROL_AE_STATE_FLASH_REQUIRED = 4; /** - * <p> - * AE has been asked to do a precapture sequence - * (through the - * trigger_action(CAMERA2_TRIGGER_PRECAPTURE_METERING) - * call), and is currently executing it. Once PRECAPTURE + * <p>AE has been asked to do a precapture sequence + * (through the {@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} START), + * and is currently executing it. Once PRECAPTURE * completes, AE will transition to CONVERGED or - * FLASH_REQUIRED as appropriate - * </p> + * FLASH_REQUIRED as appropriate. This is a transient state, the + * camera device may skip reporting this state in capture result.</p> + * + * @see CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER * @see CaptureResult#CONTROL_AE_STATE */ public static final int CONTROL_AE_STATE_PRECAPTURE = 5; @@ -1063,71 +1518,62 @@ public abstract class CameraMetadata { // /** - * <p> - * AF off or has not yet tried to scan/been asked + * <p>AF off or has not yet tried to scan/been asked * to scan. When a camera device is opened, it starts in - * this state. - * </p> + * this state. This is a transient state, the camera device may + * skip reporting this state in capture result.</p> * @see CaptureResult#CONTROL_AF_STATE */ public static final int CONTROL_AF_STATE_INACTIVE = 0; /** - * <p> - * if CONTINUOUS_* modes are supported. AF is + * <p>if CONTINUOUS_* modes are supported. AF is * currently doing an AF scan initiated by a continuous - * autofocus mode - * </p> + * autofocus mode. This is a transient state, the camera device may + * skip reporting this state in capture result.</p> * @see CaptureResult#CONTROL_AF_STATE */ public static final int CONTROL_AF_STATE_PASSIVE_SCAN = 1; /** - * <p> - * if CONTINUOUS_* modes are supported. AF currently + * <p>if CONTINUOUS_* modes are supported. AF currently * believes it is in focus, but may restart scanning at - * any time. - * </p> + * any time. This is a transient state, the camera device may skip + * reporting this state in capture result.</p> * @see CaptureResult#CONTROL_AF_STATE */ public static final int CONTROL_AF_STATE_PASSIVE_FOCUSED = 2; /** - * <p> - * if AUTO or MACRO modes are supported. AF is doing - * an AF scan because it was triggered by AF - * trigger - * </p> + * <p>if AUTO or MACRO modes are supported. AF is doing + * an AF scan because it was triggered by AF trigger. This is a + * transient state, the camera device may skip reporting + * this state in capture result.</p> * @see CaptureResult#CONTROL_AF_STATE */ public static final int CONTROL_AF_STATE_ACTIVE_SCAN = 3; /** - * <p> - * if any AF mode besides OFF is supported. AF + * <p>if any AF mode besides OFF is supported. AF * believes it is focused correctly and is - * locked - * </p> + * locked.</p> * @see CaptureResult#CONTROL_AF_STATE */ public static final int CONTROL_AF_STATE_FOCUSED_LOCKED = 4; /** - * <p> - * if any AF mode besides OFF is supported. AF has + * <p>if any AF mode besides OFF is supported. AF has * failed to focus successfully and is - * locked - * </p> + * locked.</p> * @see CaptureResult#CONTROL_AF_STATE */ public static final int CONTROL_AF_STATE_NOT_FOCUSED_LOCKED = 5; /** - * <p> - * if CONTINUOUS_* modes are supported. AF finished a + * <p>if CONTINUOUS_* modes are supported. AF finished a * passive scan without finding focus, and may restart - * scanning at any time. - * </p> + * scanning at any time. This is a transient state, the camera + * device may skip reporting this state in capture result.</p> * @see CaptureResult#CONTROL_AF_STATE */ public static final int CONTROL_AF_STATE_PASSIVE_UNFOCUSED = 6; @@ -1137,37 +1583,30 @@ public abstract class CameraMetadata { // /** - * <p> - * AWB is not in auto mode. When a camera device is opened, it - * starts in this state. - * </p> + * <p>AWB is not in auto mode. When a camera device is opened, it + * starts in this state. This is a transient state, the camera device may + * skip reporting this state in capture result.</p> * @see CaptureResult#CONTROL_AWB_STATE */ public static final int CONTROL_AWB_STATE_INACTIVE = 0; /** - * <p> - * AWB doesn't yet have a good set of control - * values for the current scene - * </p> + * <p>AWB doesn't yet have a good set of control + * values for the current scene. This is a transient state, the camera device + * may skip reporting this state in capture result.</p> * @see CaptureResult#CONTROL_AWB_STATE */ public static final int CONTROL_AWB_STATE_SEARCHING = 1; /** - * <p> - * AWB has a good set of control values for the - * current scene - * </p> + * <p>AWB has a good set of control values for the + * current scene.</p> * @see CaptureResult#CONTROL_AWB_STATE */ public static final int CONTROL_AWB_STATE_CONVERGED = 2; /** - * <p> - * AE has been locked (aeMode = - * LOCKED) - * </p> + * <p>AWB has been locked.</p> * @see CaptureResult#CONTROL_AWB_STATE */ public static final int CONTROL_AWB_STATE_LOCKED = 3; @@ -1177,36 +1616,34 @@ public abstract class CameraMetadata { // /** - * <p> - * No flash on camera - * </p> + * <p>No flash on camera</p> * @see CaptureResult#FLASH_STATE */ public static final int FLASH_STATE_UNAVAILABLE = 0; /** - * <p> - * if android.flash.available is true Flash is - * charging and cannot be fired - * </p> + * <p>if {@link CameraCharacteristics#FLASH_INFO_AVAILABLE android.flash.info.available} is true Flash is + * charging and cannot be fired</p> + * + * @see CameraCharacteristics#FLASH_INFO_AVAILABLE * @see CaptureResult#FLASH_STATE */ public static final int FLASH_STATE_CHARGING = 1; /** - * <p> - * if android.flash.available is true Flash is - * ready to fire - * </p> + * <p>if {@link CameraCharacteristics#FLASH_INFO_AVAILABLE android.flash.info.available} is true Flash is + * ready to fire</p> + * + * @see CameraCharacteristics#FLASH_INFO_AVAILABLE * @see CaptureResult#FLASH_STATE */ public static final int FLASH_STATE_READY = 2; /** - * <p> - * if android.flash.available is true Flash fired - * for this capture - * </p> + * <p>if {@link CameraCharacteristics#FLASH_INFO_AVAILABLE android.flash.info.available} is true Flash fired + * for this capture</p> + * + * @see CameraCharacteristics#FLASH_INFO_AVAILABLE * @see CaptureResult#FLASH_STATE */ public static final int FLASH_STATE_FIRED = 3; @@ -1216,16 +1653,134 @@ public abstract class CameraMetadata { // /** + * <p>The lens parameters ({@link CaptureRequest#LENS_FOCAL_LENGTH android.lens.focalLength}, {@link CaptureRequest#LENS_FOCUS_DISTANCE android.lens.focusDistance}, + * {@link CaptureRequest#LENS_FILTER_DENSITY android.lens.filterDensity} and {@link CaptureRequest#LENS_APERTURE android.lens.aperture}) are not changing.</p> + * + * @see CaptureRequest#LENS_APERTURE + * @see CaptureRequest#LENS_FILTER_DENSITY + * @see CaptureRequest#LENS_FOCAL_LENGTH + * @see CaptureRequest#LENS_FOCUS_DISTANCE * @see CaptureResult#LENS_STATE */ public static final int LENS_STATE_STATIONARY = 0; /** + * <p>Any of the lens parameters ({@link CaptureRequest#LENS_FOCAL_LENGTH android.lens.focalLength}, {@link CaptureRequest#LENS_FOCUS_DISTANCE android.lens.focusDistance}, + * {@link CaptureRequest#LENS_FILTER_DENSITY android.lens.filterDensity} or {@link CaptureRequest#LENS_APERTURE android.lens.aperture}) is changing.</p> + * + * @see CaptureRequest#LENS_APERTURE + * @see CaptureRequest#LENS_FILTER_DENSITY + * @see CaptureRequest#LENS_FOCAL_LENGTH + * @see CaptureRequest#LENS_FOCUS_DISTANCE * @see CaptureResult#LENS_STATE */ public static final int LENS_STATE_MOVING = 1; // + // Enumeration values for CaptureResult#SENSOR_REFERENCE_ILLUMINANT + // + + /** + * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT + */ + public static final int SENSOR_REFERENCE_ILLUMINANT_DAYLIGHT = 1; + + /** + * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT + */ + public static final int SENSOR_REFERENCE_ILLUMINANT_FLUORESCENT = 2; + + /** + * <p>Incandescent light</p> + * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT + */ + public static final int SENSOR_REFERENCE_ILLUMINANT_TUNGSTEN = 3; + + /** + * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT + */ + public static final int SENSOR_REFERENCE_ILLUMINANT_FLASH = 4; + + /** + * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT + */ + public static final int SENSOR_REFERENCE_ILLUMINANT_FINE_WEATHER = 9; + + /** + * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT + */ + public static final int SENSOR_REFERENCE_ILLUMINANT_CLOUDY_WEATHER = 10; + + /** + * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT + */ + public static final int SENSOR_REFERENCE_ILLUMINANT_SHADE = 11; + + /** + * <p>D 5700 - 7100K</p> + * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT + */ + public static final int SENSOR_REFERENCE_ILLUMINANT_DAYLIGHT_FLUORESCENT = 12; + + /** + * <p>N 4600 - 5400K</p> + * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT + */ + public static final int SENSOR_REFERENCE_ILLUMINANT_DAY_WHITE_FLUORESCENT = 13; + + /** + * <p>W 3900 - 4500K</p> + * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT + */ + public static final int SENSOR_REFERENCE_ILLUMINANT_COOL_WHITE_FLUORESCENT = 14; + + /** + * <p>WW 3200 - 3700K</p> + * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT + */ + public static final int SENSOR_REFERENCE_ILLUMINANT_WHITE_FLUORESCENT = 15; + + /** + * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT + */ + public static final int SENSOR_REFERENCE_ILLUMINANT_STANDARD_A = 17; + + /** + * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT + */ + public static final int SENSOR_REFERENCE_ILLUMINANT_STANDARD_B = 18; + + /** + * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT + */ + public static final int SENSOR_REFERENCE_ILLUMINANT_STANDARD_C = 19; + + /** + * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT + */ + public static final int SENSOR_REFERENCE_ILLUMINANT_D55 = 20; + + /** + * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT + */ + public static final int SENSOR_REFERENCE_ILLUMINANT_D65 = 21; + + /** + * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT + */ + public static final int SENSOR_REFERENCE_ILLUMINANT_D75 = 22; + + /** + * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT + */ + public static final int SENSOR_REFERENCE_ILLUMINANT_D50 = 23; + + /** + * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT + */ + public static final int SENSOR_REFERENCE_ILLUMINANT_ISO_STUDIO_TUNGSTEN = 24; + + // // Enumeration values for CaptureResult#STATISTICS_SCENE_FLICKER // @@ -1244,6 +1799,43 @@ public abstract class CameraMetadata { */ public static final int STATISTICS_SCENE_FLICKER_60HZ = 2; + // + // Enumeration values for CaptureResult#SYNC_FRAME_NUMBER + // + + /** + * <p>The current result is not yet fully synchronized to any request. + * Synchronization is in progress, and reading metadata from this + * result may include a mix of data that have taken effect since the + * last synchronization time.</p> + * <p>In some future result, within {@link CameraCharacteristics#SYNC_MAX_LATENCY android.sync.maxLatency} frames, + * this value will update to the actual frame number frame number + * the result is guaranteed to be synchronized to (as long as the + * request settings remain constant).</p> + * + * @see CameraCharacteristics#SYNC_MAX_LATENCY + * @see CaptureResult#SYNC_FRAME_NUMBER + * @hide + */ + public static final int SYNC_FRAME_NUMBER_CONVERGING = -1; + + /** + * <p>The current result's synchronization status is unknown. The + * result may have already converged, or it may be in progress. + * Reading from this result may include some mix of settings from + * past requests.</p> + * <p>After a settings change, the new settings will eventually all + * take effect for the output buffers and results. However, this + * value will not change when that happens. Altering settings + * rapidly may provide outcomes using mixes of settings from recent + * requests.</p> + * <p>This value is intended primarily for backwards compatibility with + * the older camera implementations (for android.hardware.Camera).</p> + * @see CaptureResult#SYNC_FRAME_NUMBER + * @hide + */ + public static final int SYNC_FRAME_NUMBER_UNKNOWN = -2; + /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ * End generated code *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/ diff --git a/core/java/android/hardware/camera2/CaptureFailure.java b/core/java/android/hardware/camera2/CaptureFailure.java index 3b408cf..35f9af1 100644 --- a/core/java/android/hardware/camera2/CaptureFailure.java +++ b/core/java/android/hardware/camera2/CaptureFailure.java @@ -15,8 +15,6 @@ */ package android.hardware.camera2; -import android.hardware.camera2.CameraDevice.CaptureListener; - /** * A report of failed capture for a single image capture from the image sensor. * diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index 898f123..a8caba0 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -17,7 +17,6 @@ package android.hardware.camera2; import android.hardware.camera2.impl.CameraMetadataNative; -import android.hardware.camera2.CameraDevice.CaptureListener; import android.os.Parcel; import android.os.Parcelable; import android.view.Surface; @@ -318,11 +317,52 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { * modify the comment blocks at the start or end. *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~*/ + /** - * <p> - * When android.control.awbMode is not OFF, TRANSFORM_MATRIX - * should be ignored. - * </p> + * <p>The mode control selects how the image data is converted from the + * sensor's native color into linear sRGB color.</p> + * <p>When auto-white balance is enabled with {@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode}, this + * control is overridden by the AWB routine. When AWB is disabled, the + * application controls how the color mapping is performed.</p> + * <p>We define the expected processing pipeline below. For consistency + * across devices, this is always the case with TRANSFORM_MATRIX.</p> + * <p>When either FULL or HIGH_QUALITY is used, the camera device may + * do additional processing but {@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains} and + * {@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform} will still be provided by the + * camera device (in the results) and be roughly correct.</p> + * <p>Switching to TRANSFORM_MATRIX and using the data provided from + * FAST or HIGH_QUALITY will yield a picture with the same white point + * as what was produced by the camera device in the earlier frame.</p> + * <p>The expected processing pipeline is as follows:</p> + * <p><img alt="White balance processing pipeline" src="../../../../images/camera2/metadata/android.colorCorrection.mode/processing_pipeline.png" /></p> + * <p>The white balance is encoded by two values, a 4-channel white-balance + * gain vector (applied in the Bayer domain), and a 3x3 color transform + * matrix (applied after demosaic).</p> + * <p>The 4-channel white-balance gains are defined as:</p> + * <pre><code>{@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains} = [ R G_even G_odd B ] + * </code></pre> + * <p>where <code>G_even</code> is the gain for green pixels on even rows of the + * output, and <code>G_odd</code> is the gain for green pixels on the odd rows. + * These may be identical for a given camera device implementation; if + * the camera device does not support a separate gain for even/odd green + * channels, it will use the <code>G_even</code> value, and write <code>G_odd</code> equal to + * <code>G_even</code> in the output result metadata.</p> + * <p>The matrices for color transforms are defined as a 9-entry vector:</p> + * <pre><code>{@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform} = [ I0 I1 I2 I3 I4 I5 I6 I7 I8 ] + * </code></pre> + * <p>which define a transform from input sensor colors, <code>P_in = [ r g b ]</code>, + * to output linear sRGB, <code>P_out = [ r' g' b' ]</code>,</p> + * <p>with colors as follows:</p> + * <pre><code>r' = I0r + I1g + I2b + * g' = I3r + I4g + I5b + * b' = I6r + I7g + I8b + * </code></pre> + * <p>Both the input and output value ranges must match. Overflow/underflow + * values are clipped to fit within the range.</p> + * + * @see CaptureRequest#COLOR_CORRECTION_GAINS + * @see CaptureRequest#COLOR_CORRECTION_TRANSFORM + * @see CaptureRequest#CONTROL_AWB_MODE * @see #COLOR_CORRECTION_MODE_TRANSFORM_MATRIX * @see #COLOR_CORRECTION_MODE_FAST * @see #COLOR_CORRECTION_MODE_HIGH_QUALITY @@ -331,55 +371,80 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { new Key<Integer>("android.colorCorrection.mode", int.class); /** - * <p> - * A color transform matrix to use to transform - * from sensor RGB color space to output linear sRGB color space - * </p> - * <p> - * This matrix is either set by HAL when the request - * android.colorCorrection.mode is not TRANSFORM_MATRIX, or + * <p>A color transform matrix to use to transform + * from sensor RGB color space to output linear sRGB color space</p> + * <p>This matrix is either set by the camera device when the request + * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is not TRANSFORM_MATRIX, or * directly by the application in the request when the - * android.colorCorrection.mode is TRANSFORM_MATRIX. - * </p><p> - * In the latter case, the HAL may round the matrix to account - * for precision issues; the final rounded matrix should be - * reported back in this matrix result metadata. - * </p> + * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is TRANSFORM_MATRIX.</p> + * <p>In the latter case, the camera device may round the matrix to account + * for precision issues; the final rounded matrix should be reported back + * in this matrix result metadata. The transform should keep the magnitude + * of the output color values within <code>[0, 1.0]</code> (assuming input color + * values is within the normalized range <code>[0, 1.0]</code>), or clipping may occur.</p> + * + * @see CaptureRequest#COLOR_CORRECTION_MODE */ public static final Key<Rational[]> COLOR_CORRECTION_TRANSFORM = new Key<Rational[]>("android.colorCorrection.transform", Rational[].class); /** - * <p> - * Gains applying to Bayer color channels for - * white-balance - * </p> - * <p> - * The 4-channel white-balance gains are defined in - * the order of [R G_even G_odd B], where G_even is the gain - * for green pixels on even rows of the output, and G_odd - * is the gain for greenpixels on the odd rows. if a HAL + * <p>Gains applying to Bayer raw color channels for + * white-balance.</p> + * <p>The 4-channel white-balance gains are defined in + * the order of <code>[R G_even G_odd B]</code>, where <code>G_even</code> is the gain + * for green pixels on even rows of the output, and <code>G_odd</code> + * is the gain for green pixels on the odd rows. if a HAL * does not support a separate gain for even/odd green channels, - * it should use the G_even value,and write G_odd equal to - * G_even in the output result metadata. - * </p><p> - * This array is either set by HAL when the request - * android.colorCorrection.mode is not TRANSFORM_MATRIX, or + * it should use the <code>G_even</code> value, and write <code>G_odd</code> equal to + * <code>G_even</code> in the output result metadata.</p> + * <p>This array is either set by the camera device when the request + * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is not TRANSFORM_MATRIX, or * directly by the application in the request when the - * android.colorCorrection.mode is TRANSFORM_MATRIX. - * </p><p> - * The ouput should be the gains actually applied by the HAL to - * the current frame. - * </p> + * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is TRANSFORM_MATRIX.</p> + * <p>The output should be the gains actually applied by the camera device to + * the current frame.</p> + * + * @see CaptureRequest#COLOR_CORRECTION_MODE */ public static final Key<float[]> COLOR_CORRECTION_GAINS = new Key<float[]>("android.colorCorrection.gains", float[].class); /** - * <p> - * Enum for controlling - * antibanding - * </p> + * <p>The desired setting for the camera device's auto-exposure + * algorithm's antibanding compensation.</p> + * <p>Some kinds of lighting fixtures, such as some fluorescent + * lights, flicker at the rate of the power supply frequency + * (60Hz or 50Hz, depending on country). While this is + * typically not noticeable to a person, it can be visible to + * a camera device. If a camera sets its exposure time to the + * wrong value, the flicker may become visible in the + * viewfinder as flicker or in a final captured image, as a + * set of variable-brightness bands across the image.</p> + * <p>Therefore, the auto-exposure routines of camera devices + * include antibanding routines that ensure that the chosen + * exposure value will not cause such banding. The choice of + * exposure time depends on the rate of flicker, which the + * camera device can detect automatically, or the expected + * rate can be selected by the application using this + * control.</p> + * <p>A given camera device may not support all of the possible + * options for the antibanding mode. The + * {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_ANTIBANDING_MODES android.control.aeAvailableAntibandingModes} key contains + * the available modes for a given camera device.</p> + * <p>The default mode is AUTO, which must be supported by all + * camera devices.</p> + * <p>If manual exposure control is enabled (by setting + * {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} or {@link CaptureRequest#CONTROL_MODE android.control.mode} to OFF), + * then this setting has no effect, and the application must + * ensure it selects exposure times that do not cause banding + * issues. The {@link CaptureResult#STATISTICS_SCENE_FLICKER android.statistics.sceneFlicker} key can assist + * the application in this.</p> + * + * @see CameraCharacteristics#CONTROL_AE_AVAILABLE_ANTIBANDING_MODES + * @see CaptureRequest#CONTROL_AE_MODE + * @see CaptureRequest#CONTROL_MODE + * @see CaptureResult#STATISTICS_SCENE_FLICKER * @see #CONTROL_AE_ANTIBANDING_MODE_OFF * @see #CONTROL_AE_ANTIBANDING_MODE_50HZ * @see #CONTROL_AE_ANTIBANDING_MODE_60HZ @@ -389,42 +454,66 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { new Key<Integer>("android.control.aeAntibandingMode", int.class); /** - * <p> - * Adjustment to AE target image - * brightness - * </p> - * <p> - * For example, if EV step is 0.333, '6' will mean an + * <p>Adjustment to AE target image + * brightness</p> + * <p>For example, if EV step is 0.333, '6' will mean an * exposure compensation of +2 EV; -3 will mean an exposure - * compensation of -1 - * </p> + * compensation of -1</p> */ public static final Key<Integer> CONTROL_AE_EXPOSURE_COMPENSATION = new Key<Integer>("android.control.aeExposureCompensation", int.class); /** - * <p> - * Whether AE is currently locked to its latest - * calculated values - * </p> - * <p> - * Note that even when AE is locked, the flash may be - * fired if the AE mode is ON_AUTO_FLASH / ON_ALWAYS_FLASH / - * ON_AUTO_FLASH_REDEYE. - * </p> + * <p>Whether AE is currently locked to its latest + * calculated values.</p> + * <p>Note that even when AE is locked, the flash may be + * fired if the {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is ON_AUTO_FLASH / ON_ALWAYS_FLASH / + * ON_AUTO_FLASH_REDEYE.</p> + * <p>If AE precapture is triggered (see {@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger}) + * when AE is already locked, the camera device will not change the exposure time + * ({@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}) and sensitivity ({@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}) + * parameters. The flash may be fired if the {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} + * is ON_AUTO_FLASH/ON_AUTO_FLASH_REDEYE and the scene is too dark. If the + * {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is ON_ALWAYS_FLASH, the scene may become overexposed.</p> + * <p>See {@link CaptureResult#CONTROL_AE_STATE android.control.aeState} for AE lock related state transition details.</p> + * + * @see CaptureRequest#CONTROL_AE_MODE + * @see CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER + * @see CaptureResult#CONTROL_AE_STATE + * @see CaptureRequest#SENSOR_EXPOSURE_TIME + * @see CaptureRequest#SENSOR_SENSITIVITY */ public static final Key<Boolean> CONTROL_AE_LOCK = new Key<Boolean>("android.control.aeLock", boolean.class); /** - * <p> - * Whether AE is currently updating the sensor - * exposure and sensitivity fields - * </p> - * <p> - * Only effective if android.control.mode = - * AUTO - * </p> + * <p>The desired mode for the camera device's + * auto-exposure routine.</p> + * <p>This control is only effective if {@link CaptureRequest#CONTROL_MODE android.control.mode} is + * AUTO.</p> + * <p>When set to any of the ON modes, the camera device's + * auto-exposure routine is enabled, overriding the + * application's selected exposure time, sensor sensitivity, + * and frame duration ({@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}, + * {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}, and + * {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}). If one of the FLASH modes + * is selected, the camera device's flash unit controls are + * also overridden.</p> + * <p>The FLASH modes are only available if the camera device + * has a flash unit ({@link CameraCharacteristics#FLASH_INFO_AVAILABLE android.flash.info.available} is <code>true</code>).</p> + * <p>If flash TORCH mode is desired, this field must be set to + * ON or OFF, and {@link CaptureRequest#FLASH_MODE android.flash.mode} set to TORCH.</p> + * <p>When set to any of the ON modes, the values chosen by the + * camera device auto-exposure routine for the overridden + * fields for a given capture will be available in its + * CaptureResult.</p> + * + * @see CaptureRequest#CONTROL_MODE + * @see CameraCharacteristics#FLASH_INFO_AVAILABLE + * @see CaptureRequest#FLASH_MODE + * @see CaptureRequest#SENSOR_EXPOSURE_TIME + * @see CaptureRequest#SENSOR_FRAME_DURATION + * @see CaptureRequest#SENSOR_SENSITIVITY * @see #CONTROL_AE_MODE_OFF * @see #CONTROL_AE_MODE_ON * @see #CONTROL_AE_MODE_ON_AUTO_FLASH @@ -435,60 +524,52 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { new Key<Integer>("android.control.aeMode", int.class); /** - * <p> - * List of areas to use for - * metering - * </p> - * <p> - * Each area is a rectangle plus weight: xmin, ymin, - * xmax, ymax, weight. The rectangle is defined inclusive of the - * specified coordinates. - * </p><p> - * The coordinate system is based on the active pixel array, + * <p>List of areas to use for + * metering.</p> + * <p>Each area is a rectangle plus weight: xmin, ymin, + * xmax, ymax, weight. The rectangle is defined to be inclusive of the + * specified coordinates.</p> + * <p>The coordinate system is based on the active pixel array, * with (0,0) being the top-left pixel in the active pixel array, and - * (android.sensor.info.activeArraySize.width - 1, - * android.sensor.info.activeArraySize.height - 1) being the + * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1, + * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the * bottom-right pixel in the active pixel array. The weight - * should be nonnegative. - * </p><p> - * If all regions have 0 weight, then no specific metering area - * needs to be used by the HAL. If the metering region is - * outside the current android.scaler.cropRegion, the HAL - * should ignore the sections outside the region and output the - * used sections in the frame metadata - * </p> + * should be nonnegative.</p> + * <p>If all regions have 0 weight, then no specific metering area + * needs to be used by the camera device. If the metering region is + * outside the current {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}, the camera device + * will ignore the sections outside the region and output the + * used sections in the frame metadata.</p> + * + * @see CaptureRequest#SCALER_CROP_REGION + * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE */ public static final Key<int[]> CONTROL_AE_REGIONS = new Key<int[]>("android.control.aeRegions", int[].class); /** - * <p> - * Range over which fps can be adjusted to - * maintain exposure - * </p> - * <p> - * Only constrains AE algorithm, not manual control - * of android.sensor.exposureTime - * </p> + * <p>Range over which fps can be adjusted to + * maintain exposure</p> + * <p>Only constrains AE algorithm, not manual control + * of {@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}</p> + * + * @see CaptureRequest#SENSOR_EXPOSURE_TIME */ public static final Key<int[]> CONTROL_AE_TARGET_FPS_RANGE = new Key<int[]>("android.control.aeTargetFpsRange", int[].class); /** - * <p> - * Whether the HAL must trigger precapture - * metering. - * </p> - * <p> - * This entry is normally set to IDLE, or is not + * <p>Whether the camera device will trigger a precapture + * metering sequence when it processes this request.</p> + * <p>This entry is normally set to IDLE, or is not * included at all in the request settings. When included and - * set to START, the HAL must trigger the autoexposure - * precapture metering sequence. - * </p><p> - * The effect of AE precapture trigger depends on the current - * AE mode and state; see the camera HAL device v3 header for - * details. - * </p> + * set to START, the camera device will trigger the autoexposure + * precapture metering sequence.</p> + * <p>The effect of AE precapture trigger depends on the current + * AE mode and state; see {@link CaptureResult#CONTROL_AE_STATE android.control.aeState} for AE precapture + * state transition details.</p> + * + * @see CaptureResult#CONTROL_AE_STATE * @see #CONTROL_AE_PRECAPTURE_TRIGGER_IDLE * @see #CONTROL_AE_PRECAPTURE_TRIGGER_START */ @@ -496,10 +577,17 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { new Key<Integer>("android.control.aePrecaptureTrigger", int.class); /** - * <p> - * Whether AF is currently enabled, and what - * mode it is set to - * </p> + * <p>Whether AF is currently enabled, and what + * mode it is set to</p> + * <p>Only effective if {@link CaptureRequest#CONTROL_MODE android.control.mode} = AUTO and the lens is not fixed focus + * (i.e. <code>{@link CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE android.lens.info.minimumFocusDistance} > 0</code>).</p> + * <p>If the lens is controlled by the camera device auto-focus algorithm, + * the camera device will report the current AF status in {@link CaptureResult#CONTROL_AF_STATE android.control.afState} + * in result metadata.</p> + * + * @see CaptureResult#CONTROL_AF_STATE + * @see CaptureRequest#CONTROL_MODE + * @see CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE * @see #CONTROL_AF_MODE_OFF * @see #CONTROL_AF_MODE_AUTO * @see #CONTROL_AF_MODE_MACRO @@ -511,46 +599,40 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { new Key<Integer>("android.control.afMode", int.class); /** - * <p> - * List of areas to use for focus - * estimation - * </p> - * <p> - * Each area is a rectangle plus weight: xmin, ymin, - * xmax, ymax, weight. The rectangle is defined inclusive of the - * specified coordinates. - * </p><p> - * The coordinate system is based on the active pixel array, + * <p>List of areas to use for focus + * estimation.</p> + * <p>Each area is a rectangle plus weight: xmin, ymin, + * xmax, ymax, weight. The rectangle is defined to be inclusive of the + * specified coordinates.</p> + * <p>The coordinate system is based on the active pixel array, * with (0,0) being the top-left pixel in the active pixel array, and - * (android.sensor.info.activeArraySize.width - 1, - * android.sensor.info.activeArraySize.height - 1) being the + * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1, + * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the * bottom-right pixel in the active pixel array. The weight - * should be nonnegative. - * </p><p> - * If all regions have 0 weight, then no specific focus area - * needs to be used by the HAL. If the focusing region is - * outside the current android.scaler.cropRegion, the HAL - * should ignore the sections outside the region and output the - * used sections in the frame metadata - * </p> + * should be nonnegative.</p> + * <p>If all regions have 0 weight, then no specific focus area + * needs to be used by the camera device. If the focusing region is + * outside the current {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}, the camera device + * will ignore the sections outside the region and output the + * used sections in the frame metadata.</p> + * + * @see CaptureRequest#SCALER_CROP_REGION + * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE */ public static final Key<int[]> CONTROL_AF_REGIONS = new Key<int[]>("android.control.afRegions", int[].class); /** - * <p> - * Whether the HAL must trigger autofocus. - * </p> - * <p> - * This entry is normally set to IDLE, or is not - * included at all in the request settings. - * </p><p> - * When included and set to START, the HAL must trigger the - * autofocus algorithm. The effect of AF trigger depends on the - * current AF mode and state; see the camera HAL device v3 - * header for details. When set to CANCEL, the HAL must cancel - * any active trigger, and return to initial AF state. - * </p> + * <p>Whether the camera device will trigger autofocus for this request.</p> + * <p>This entry is normally set to IDLE, or is not + * included at all in the request settings.</p> + * <p>When included and set to START, the camera device will trigger the + * autofocus algorithm. If autofocus is disabled, this trigger has no effect.</p> + * <p>When set to CANCEL, the camera device will cancel any active trigger, + * and return to its initial AF state.</p> + * <p>See {@link CaptureResult#CONTROL_AF_STATE android.control.afState} for what that means for each AF mode.</p> + * + * @see CaptureResult#CONTROL_AF_STATE * @see #CONTROL_AF_TRIGGER_IDLE * @see #CONTROL_AF_TRIGGER_START * @see #CONTROL_AF_TRIGGER_CANCEL @@ -559,28 +641,36 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { new Key<Integer>("android.control.afTrigger", int.class); /** - * <p> - * Whether AWB is currently locked to its - * latest calculated values - * </p> - * <p> - * Note that AWB lock is only meaningful for AUTO + * <p>Whether AWB is currently locked to its + * latest calculated values.</p> + * <p>Note that AWB lock is only meaningful for AUTO * mode; in other modes, AWB is already fixed to a specific - * setting - * </p> + * setting.</p> */ public static final Key<Boolean> CONTROL_AWB_LOCK = new Key<Boolean>("android.control.awbLock", boolean.class); /** - * <p> - * Whether AWB is currently setting the color + * <p>Whether AWB is currently setting the color * transform fields, and what its illumination target - * is - * </p> - * <p> - * [BC - AWB lock,AWB modes] - * </p> + * is.</p> + * <p>This control is only effective if {@link CaptureRequest#CONTROL_MODE android.control.mode} is AUTO.</p> + * <p>When set to the ON mode, the camera device's auto white balance + * routine is enabled, overriding the application's selected + * {@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform}, {@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains} and + * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode}.</p> + * <p>When set to the OFF mode, the camera device's auto white balance + * routine is disabled. The application manually controls the white + * balance by {@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform}, {@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains} + * and {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode}.</p> + * <p>When set to any other modes, the camera device's auto white balance + * routine is disabled. The camera device uses each particular illumination + * target for white balance adjustment.</p> + * + * @see CaptureRequest#COLOR_CORRECTION_GAINS + * @see CaptureRequest#COLOR_CORRECTION_MODE + * @see CaptureRequest#COLOR_CORRECTION_TRANSFORM + * @see CaptureRequest#CONTROL_MODE * @see #CONTROL_AWB_MODE_OFF * @see #CONTROL_AWB_MODE_AUTO * @see #CONTROL_AWB_MODE_INCANDESCENT @@ -595,43 +685,39 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { new Key<Integer>("android.control.awbMode", int.class); /** - * <p> - * List of areas to use for illuminant - * estimation - * </p> - * <p> - * Only used in AUTO mode. - * </p><p> - * Each area is a rectangle plus weight: xmin, ymin, - * xmax, ymax, weight. The rectangle is defined inclusive of the - * specified coordinates. - * </p><p> - * The coordinate system is based on the active pixel array, + * <p>List of areas to use for illuminant + * estimation.</p> + * <p>Only used in AUTO mode.</p> + * <p>Each area is a rectangle plus weight: xmin, ymin, + * xmax, ymax, weight. The rectangle is defined to be inclusive of the + * specified coordinates.</p> + * <p>The coordinate system is based on the active pixel array, * with (0,0) being the top-left pixel in the active pixel array, and - * (android.sensor.info.activeArraySize.width - 1, - * android.sensor.info.activeArraySize.height - 1) being the + * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1, + * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the * bottom-right pixel in the active pixel array. The weight - * should be nonnegative. - * </p><p> - * If all regions have 0 weight, then no specific metering area - * needs to be used by the HAL. If the metering region is - * outside the current android.scaler.cropRegion, the HAL - * should ignore the sections outside the region and output the - * used sections in the frame metadata - * </p> + * should be nonnegative.</p> + * <p>If all regions have 0 weight, then no specific auto-white balance (AWB) area + * needs to be used by the camera device. If the AWB region is + * outside the current {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}, the camera device + * will ignore the sections outside the region and output the + * used sections in the frame metadata.</p> + * + * @see CaptureRequest#SCALER_CROP_REGION + * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE */ public static final Key<int[]> CONTROL_AWB_REGIONS = new Key<int[]>("android.control.awbRegions", int[].class); /** - * <p> - * Information to 3A routines about the purpose - * of this capture, to help decide optimal 3A - * strategy - * </p> - * <p> - * Only used if android.control.mode != OFF. - * </p> + * <p>Information to the camera device 3A (auto-exposure, + * auto-focus, auto-white balance) routines about the purpose + * of this capture, to help the camera device to decide optimal 3A + * strategy.</p> + * <p>This control is only effective if <code>{@link CaptureRequest#CONTROL_MODE android.control.mode} != OFF</code> + * and any 3A routine is active.</p> + * + * @see CaptureRequest#CONTROL_MODE * @see #CONTROL_CAPTURE_INTENT_CUSTOM * @see #CONTROL_CAPTURE_INTENT_PREVIEW * @see #CONTROL_CAPTURE_INTENT_STILL_CAPTURE @@ -643,10 +729,17 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { new Key<Integer>("android.control.captureIntent", int.class); /** - * <p> - * Whether any special color effect is in use. - * Only used if android.control.mode != OFF - * </p> + * <p>A special color effect to apply.</p> + * <p>When this mode is set, a color effect will be applied + * to images produced by the camera device. The interpretation + * and implementation of these color effects is left to the + * implementor of the camera device, and should not be + * depended on to be consistent (or present) across all + * devices.</p> + * <p>A color effect will only be applied if + * {@link CaptureRequest#CONTROL_MODE android.control.mode} != OFF.</p> + * + * @see CaptureRequest#CONTROL_MODE * @see #CONTROL_EFFECT_MODE_OFF * @see #CONTROL_EFFECT_MODE_MONO * @see #CONTROL_EFFECT_MODE_NEGATIVE @@ -661,23 +754,50 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { new Key<Integer>("android.control.effectMode", int.class); /** - * <p> - * Overall mode of 3A control - * routines - * </p> + * <p>Overall mode of 3A control + * routines.</p> + * <p>High-level 3A control. When set to OFF, all 3A control + * by the camera device is disabled. The application must set the fields for + * capture parameters itself.</p> + * <p>When set to AUTO, the individual algorithm controls in + * android.control.* are in effect, such as {@link CaptureRequest#CONTROL_AF_MODE android.control.afMode}.</p> + * <p>When set to USE_SCENE_MODE, the individual controls in + * android.control.* are mostly disabled, and the camera device implements + * one of the scene mode settings (such as ACTION, SUNSET, or PARTY) + * as it wishes. The camera device scene mode 3A settings are provided by + * android.control.sceneModeOverrides.</p> + * <p>When set to OFF_KEEP_STATE, it is similar to OFF mode, the only difference + * is that this frame will not be used by camera device background 3A statistics + * update, as if this frame is never captured. This mode can be used in the scenario + * where the application doesn't want a 3A manual control capture to affect + * the subsequent auto 3A capture results.</p> + * + * @see CaptureRequest#CONTROL_AF_MODE * @see #CONTROL_MODE_OFF * @see #CONTROL_MODE_AUTO * @see #CONTROL_MODE_USE_SCENE_MODE + * @see #CONTROL_MODE_OFF_KEEP_STATE */ public static final Key<Integer> CONTROL_MODE = new Key<Integer>("android.control.mode", int.class); /** - * <p> - * Which scene mode is active when - * android.control.mode = SCENE_MODE - * </p> - * @see #CONTROL_SCENE_MODE_UNSUPPORTED + * <p>A camera mode optimized for conditions typical in a particular + * capture setting.</p> + * <p>This is the mode that that is active when + * <code>{@link CaptureRequest#CONTROL_MODE android.control.mode} == USE_SCENE_MODE</code>. Aside from FACE_PRIORITY, + * these modes will disable {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode}, + * {@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode}, and {@link CaptureRequest#CONTROL_AF_MODE android.control.afMode} while in use.</p> + * <p>The interpretation and implementation of these scene modes is left + * to the implementor of the camera device. Their behavior will not be + * consistent across all devices, and any given device may only implement + * a subset of these modes.</p> + * + * @see CaptureRequest#CONTROL_AE_MODE + * @see CaptureRequest#CONTROL_AF_MODE + * @see CaptureRequest#CONTROL_AWB_MODE + * @see CaptureRequest#CONTROL_MODE + * @see #CONTROL_SCENE_MODE_DISABLED * @see #CONTROL_SCENE_MODE_FACE_PRIORITY * @see #CONTROL_SCENE_MODE_ACTION * @see #CONTROL_SCENE_MODE_PORTRAIT @@ -699,24 +819,27 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { new Key<Integer>("android.control.sceneMode", int.class); /** - * <p> - * Whether video stabilization is - * active - * </p> - * <p> - * If enabled, video stabilization can modify the - * android.scaler.cropRegion to keep the video stream - * stabilized - * </p> + * <p>Whether video stabilization is + * active</p> + * <p>If enabled, video stabilization can modify the + * {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} to keep the video stream + * stabilized</p> + * + * @see CaptureRequest#SCALER_CROP_REGION */ public static final Key<Boolean> CONTROL_VIDEO_STABILIZATION_MODE = new Key<Boolean>("android.control.videoStabilizationMode", boolean.class); /** - * <p> - * Operation mode for edge - * enhancement - * </p> + * <p>Operation mode for edge + * enhancement.</p> + * <p>Edge/sharpness/detail enhancement. OFF means no + * enhancement will be applied by the camera device.</p> + * <p>FAST/HIGH_QUALITY both mean camera device determined enhancement + * will be applied. HIGH_QUALITY mode indicates that the + * camera device will use the highest-quality enhancement algorithms, + * even if it slows down capture rate. FAST means the camera device will + * not slow down capture rate when applying edge enhancement.</p> * @see #EDGE_MODE_OFF * @see #EDGE_MODE_FAST * @see #EDGE_MODE_HIGH_QUALITY @@ -725,9 +848,25 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { new Key<Integer>("android.edge.mode", int.class); /** - * <p> - * Select flash operation mode - * </p> + * <p>The desired mode for for the camera device's flash control.</p> + * <p>This control is only effective when flash unit is available + * (<code>{@link CameraCharacteristics#FLASH_INFO_AVAILABLE android.flash.info.available} == true</code>).</p> + * <p>When this control is used, the {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} must be set to ON or OFF. + * Otherwise, the camera device auto-exposure related flash control (ON_AUTO_FLASH, + * ON_ALWAYS_FLASH, or ON_AUTO_FLASH_REDEYE) will override this control.</p> + * <p>When set to OFF, the camera device will not fire flash for this capture.</p> + * <p>When set to SINGLE, the camera device will fire flash regardless of the camera + * device's auto-exposure routine's result. When used in still capture case, this + * control should be used along with AE precapture metering sequence + * ({@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger}), otherwise, the image may be incorrectly exposed.</p> + * <p>When set to TORCH, the flash will be on continuously. This mode can be used + * for use cases such as preview, auto-focus assist, still capture, or video recording.</p> + * <p>The flash status will be reported by {@link CaptureResult#FLASH_STATE android.flash.state} in the capture result metadata.</p> + * + * @see CaptureRequest#CONTROL_AE_MODE + * @see CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER + * @see CameraCharacteristics#FLASH_INFO_AVAILABLE + * @see CaptureResult#FLASH_STATE * @see #FLASH_MODE_OFF * @see #FLASH_MODE_SINGLE * @see #FLASH_MODE_TORCH @@ -736,128 +875,167 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { new Key<Integer>("android.flash.mode", int.class); /** - * <p> - * GPS coordinates to include in output JPEG - * EXIF - * </p> + * <p>Set operational mode for hot pixel correction.</p> + * <p>Hotpixel correction interpolates out, or otherwise removes, pixels + * that do not accurately encode the incoming light (i.e. pixels that + * are stuck at an arbitrary value).</p> + * @see #HOT_PIXEL_MODE_OFF + * @see #HOT_PIXEL_MODE_FAST + * @see #HOT_PIXEL_MODE_HIGH_QUALITY + */ + public static final Key<Integer> HOT_PIXEL_MODE = + new Key<Integer>("android.hotPixel.mode", int.class); + + /** + * <p>GPS coordinates to include in output JPEG + * EXIF</p> */ public static final Key<double[]> JPEG_GPS_COORDINATES = new Key<double[]>("android.jpeg.gpsCoordinates", double[].class); /** - * <p> - * 32 characters describing GPS algorithm to - * include in EXIF - * </p> + * <p>32 characters describing GPS algorithm to + * include in EXIF</p> */ public static final Key<String> JPEG_GPS_PROCESSING_METHOD = new Key<String>("android.jpeg.gpsProcessingMethod", String.class); /** - * <p> - * Time GPS fix was made to include in - * EXIF - * </p> + * <p>Time GPS fix was made to include in + * EXIF</p> */ public static final Key<Long> JPEG_GPS_TIMESTAMP = new Key<Long>("android.jpeg.gpsTimestamp", long.class); /** - * <p> - * Orientation of JPEG image to - * write - * </p> + * <p>Orientation of JPEG image to + * write</p> */ public static final Key<Integer> JPEG_ORIENTATION = new Key<Integer>("android.jpeg.orientation", int.class); /** - * <p> - * Compression quality of the final JPEG - * image - * </p> - * <p> - * 85-95 is typical usage range - * </p> + * <p>Compression quality of the final JPEG + * image</p> + * <p>85-95 is typical usage range</p> */ public static final Key<Byte> JPEG_QUALITY = new Key<Byte>("android.jpeg.quality", byte.class); /** - * <p> - * Compression quality of JPEG - * thumbnail - * </p> + * <p>Compression quality of JPEG + * thumbnail</p> */ public static final Key<Byte> JPEG_THUMBNAIL_QUALITY = new Key<Byte>("android.jpeg.thumbnailQuality", byte.class); /** - * <p> - * Resolution of embedded JPEG - * thumbnail - * </p> + * <p>Resolution of embedded JPEG thumbnail</p> + * <p>When set to (0, 0) value, the JPEG EXIF will not contain thumbnail, + * but the captured JPEG will still be a valid image.</p> + * <p>When a jpeg image capture is issued, the thumbnail size selected should have + * the same aspect ratio as the jpeg image.</p> */ public static final Key<android.hardware.camera2.Size> JPEG_THUMBNAIL_SIZE = new Key<android.hardware.camera2.Size>("android.jpeg.thumbnailSize", android.hardware.camera2.Size.class); /** - * <p> - * Size of the lens aperture - * </p> - * <p> - * Will not be supported on most devices. Can only - * pick from supported list - * </p> + * <p>The ratio of lens focal length to the effective + * aperture diameter.</p> + * <p>This will only be supported on the camera devices that + * have variable aperture lens. The aperture value can only be + * one of the values listed in {@link CameraCharacteristics#LENS_INFO_AVAILABLE_APERTURES android.lens.info.availableApertures}.</p> + * <p>When this is supported and {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is OFF, + * this can be set along with {@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}, + * {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}, and {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} + * to achieve manual exposure control.</p> + * <p>The requested aperture value may take several frames to reach the + * requested value; the camera device will report the current (intermediate) + * aperture size in capture result metadata while the aperture is changing. + * While the aperture is still changing, {@link CaptureResult#LENS_STATE android.lens.state} will be set to MOVING.</p> + * <p>When this is supported and {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is one of + * the ON modes, this will be overridden by the camera device + * auto-exposure algorithm, the overridden values are then provided + * back to the user in the corresponding result.</p> + * + * @see CaptureRequest#CONTROL_AE_MODE + * @see CameraCharacteristics#LENS_INFO_AVAILABLE_APERTURES + * @see CaptureResult#LENS_STATE + * @see CaptureRequest#SENSOR_EXPOSURE_TIME + * @see CaptureRequest#SENSOR_FRAME_DURATION + * @see CaptureRequest#SENSOR_SENSITIVITY */ public static final Key<Float> LENS_APERTURE = new Key<Float>("android.lens.aperture", float.class); /** - * <p> - * State of lens neutral density - * filter(s) - * </p> - * <p> - * Will not be supported on most devices. Can only - * pick from supported list - * </p> + * <p>State of lens neutral density filter(s).</p> + * <p>This will not be supported on most camera devices. On devices + * where this is supported, this may only be set to one of the + * values included in {@link CameraCharacteristics#LENS_INFO_AVAILABLE_FILTER_DENSITIES android.lens.info.availableFilterDensities}.</p> + * <p>Lens filters are typically used to lower the amount of light the + * sensor is exposed to (measured in steps of EV). As used here, an EV + * step is the standard logarithmic representation, which are + * non-negative, and inversely proportional to the amount of light + * hitting the sensor. For example, setting this to 0 would result + * in no reduction of the incoming light, and setting this to 2 would + * mean that the filter is set to reduce incoming light by two stops + * (allowing 1/4 of the prior amount of light to the sensor).</p> + * <p>It may take several frames before the lens filter density changes + * to the requested value. While the filter density is still changing, + * {@link CaptureResult#LENS_STATE android.lens.state} will be set to MOVING.</p> + * + * @see CameraCharacteristics#LENS_INFO_AVAILABLE_FILTER_DENSITIES + * @see CaptureResult#LENS_STATE */ public static final Key<Float> LENS_FILTER_DENSITY = new Key<Float>("android.lens.filterDensity", float.class); /** - * <p> - * Lens optical zoom setting - * </p> - * <p> - * Will not be supported on most devices. - * </p> + * <p>The current lens focal length; used for optical zoom.</p> + * <p>This setting controls the physical focal length of the camera + * device's lens. Changing the focal length changes the field of + * view of the camera device, and is usually used for optical zoom.</p> + * <p>Like {@link CaptureRequest#LENS_FOCUS_DISTANCE android.lens.focusDistance} and {@link CaptureRequest#LENS_APERTURE android.lens.aperture}, this + * setting won't be applied instantaneously, and it may take several + * frames before the lens can change to the requested focal length. + * While the focal length is still changing, {@link CaptureResult#LENS_STATE android.lens.state} will + * be set to MOVING.</p> + * <p>This is expected not to be supported on most devices.</p> + * + * @see CaptureRequest#LENS_APERTURE + * @see CaptureRequest#LENS_FOCUS_DISTANCE + * @see CaptureResult#LENS_STATE */ public static final Key<Float> LENS_FOCAL_LENGTH = new Key<Float>("android.lens.focalLength", float.class); /** - * <p> - * Distance to plane of sharpest focus, - * measured from frontmost surface of the lens - * </p> - * <p> - * 0 = infinity focus. Used value should be clamped - * to (0,minimum focus distance) - * </p> + * <p>Distance to plane of sharpest focus, + * measured from frontmost surface of the lens</p> + * <p>0 means infinity focus. Used value will be clamped + * to [0, {@link CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE android.lens.info.minimumFocusDistance}].</p> + * <p>Like {@link CaptureRequest#LENS_FOCAL_LENGTH android.lens.focalLength}, this setting won't be applied + * instantaneously, and it may take several frames before the lens + * can move to the requested focus distance. While the lens is still moving, + * {@link CaptureResult#LENS_STATE android.lens.state} will be set to MOVING.</p> + * + * @see CaptureRequest#LENS_FOCAL_LENGTH + * @see CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE + * @see CaptureResult#LENS_STATE */ public static final Key<Float> LENS_FOCUS_DISTANCE = new Key<Float>("android.lens.focusDistance", float.class); /** - * <p> - * Whether optical image stabilization is - * enabled. - * </p> - * <p> - * Will not be supported on most devices. - * </p> + * <p>Sets whether the camera device uses optical image stabilization (OIS) + * when capturing images.</p> + * <p>OIS is used to compensate for motion blur due to small movements of + * the camera during capture. Unlike digital image stabilization, OIS makes + * use of mechanical elements to stabilize the camera sensor, and thus + * allows for longer exposure times before camera shake becomes + * apparent.</p> + * <p>This is not expected to be supported on most devices.</p> * @see #LENS_OPTICAL_STABILIZATION_MODE_OFF * @see #LENS_OPTICAL_STABILIZATION_MODE_ON */ @@ -865,10 +1043,15 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { new Key<Integer>("android.lens.opticalStabilizationMode", int.class); /** - * <p> - * Mode of operation for the noise reduction - * algorithm - * </p> + * <p>Mode of operation for the noise reduction + * algorithm</p> + * <p>Noise filtering control. OFF means no noise reduction + * will be applied by the camera device.</p> + * <p>FAST/HIGH_QUALITY both mean camera device determined noise filtering + * will be applied. HIGH_QUALITY mode indicates that the camera device + * will use the highest-quality noise filtering algorithms, + * even if it slows down capture rate. FAST means the camera device should not + * slow down capture rate when applying noise filtering.</p> * @see #NOISE_REDUCTION_MODE_OFF * @see #NOISE_REDUCTION_MODE_FAST * @see #NOISE_REDUCTION_MODE_HIGH_QUALITY @@ -877,44 +1060,33 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { new Key<Integer>("android.noiseReduction.mode", int.class); /** - * <p> - * An application-specified ID for the current + * <p>An application-specified ID for the current * request. Must be maintained unchanged in output - * frame - * </p> - * + * frame</p> * @hide */ public static final Key<Integer> REQUEST_ID = new Key<Integer>("android.request.id", int.class); /** - * <p> - * (x, y, width, height). - * </p><p> - * A rectangle with the top-level corner of (x,y) and size + * <p>(x, y, width, height).</p> + * <p>A rectangle with the top-level corner of (x,y) and size * (width, height). The region of the sensor that is used for * output. Each stream must use this rectangle to produce its * output, cropping to a smaller region if necessary to - * maintain the stream's aspect ratio. - * </p><p> - * HAL2.x uses only (x, y, width) - * </p> - * <p> - * Any additional per-stream cropping must be done to - * maximize the final pixel area of the stream. - * </p><p> - * For example, if the crop region is set to a 4:3 aspect + * maintain the stream's aspect ratio.</p> + * <p>HAL2.x uses only (x, y, width)</p> + * <p>Any additional per-stream cropping must be done to + * maximize the final pixel area of the stream.</p> + * <p>For example, if the crop region is set to a 4:3 aspect * ratio, then 4:3 streams should use the exact crop * region. 16:9 streams should further crop vertically - * (letterbox). - * </p><p> - * Conversely, if the crop region is set to a 16:9, then 4:3 + * (letterbox).</p> + * <p>Conversely, if the crop region is set to a 16:9, then 4:3 * outputs should crop horizontally (pillarbox), and 16:9 * streams should match exactly. These additional crops must - * be centered within the crop region. - * </p><p> - * The output streams must maintain square pixels at all + * be centered within the crop region.</p> + * <p>The output streams must maintain square pixels at all * times, no matter what the relative aspect ratios of the * crop region and the stream are. Negative values for * corner are allowed for raw output if full pixel array is @@ -923,69 +1095,189 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { * for raw output, where only a few fixed scales may be * possible. The width and height of the crop region cannot * be set to be smaller than floor( activeArraySize.width / - * android.scaler.maxDigitalZoom ) and floor( - * activeArraySize.height / android.scaler.maxDigitalZoom), - * respectively. - * </p> + * {@link CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM android.scaler.availableMaxDigitalZoom} ) and floor( + * activeArraySize.height / + * {@link CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM android.scaler.availableMaxDigitalZoom}), respectively.</p> + * + * @see CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM */ public static final Key<android.graphics.Rect> SCALER_CROP_REGION = new Key<android.graphics.Rect>("android.scaler.cropRegion", android.graphics.Rect.class); /** - * <p> - * Duration each pixel is exposed to - * light. - * </p><p> - * If the sensor can't expose this exact duration, it should shorten the - * duration exposed to the nearest possible value (rather than expose longer). - * </p> - * <p> - * 1/10000 - 30 sec range. No bulb mode - * </p> + * <p>Duration each pixel is exposed to + * light.</p> + * <p>If the sensor can't expose this exact duration, it should shorten the + * duration exposed to the nearest possible value (rather than expose longer).</p> + * <p>1/10000 - 30 sec range. No bulb mode</p> */ public static final Key<Long> SENSOR_EXPOSURE_TIME = new Key<Long>("android.sensor.exposureTime", long.class); /** - * <p> - * Duration from start of frame exposure to - * start of next frame exposure - * </p> - * <p> - * Exposure time has priority, so duration is set to - * max(duration, exposure time + overhead) - * </p> + * <p>Duration from start of frame exposure to + * start of next frame exposure.</p> + * <p>The maximum frame rate that can be supported by a camera subsystem is + * a function of many factors:</p> + * <ul> + * <li>Requested resolutions of output image streams</li> + * <li>Availability of binning / skipping modes on the imager</li> + * <li>The bandwidth of the imager interface</li> + * <li>The bandwidth of the various ISP processing blocks</li> + * </ul> + * <p>Since these factors can vary greatly between different ISPs and + * sensors, the camera abstraction tries to represent the bandwidth + * restrictions with as simple a model as possible.</p> + * <p>The model presented has the following characteristics:</p> + * <ul> + * <li>The image sensor is always configured to output the smallest + * resolution possible given the application's requested output stream + * sizes. The smallest resolution is defined as being at least as large + * as the largest requested output stream size; the camera pipeline must + * never digitally upsample sensor data when the crop region covers the + * whole sensor. In general, this means that if only small output stream + * resolutions are configured, the sensor can provide a higher frame + * rate.</li> + * <li>Since any request may use any or all the currently configured + * output streams, the sensor and ISP must be configured to support + * scaling a single capture to all the streams at the same time. This + * means the camera pipeline must be ready to produce the largest + * requested output size without any delay. Therefore, the overall + * frame rate of a given configured stream set is governed only by the + * largest requested stream resolution.</li> + * <li>Using more than one output stream in a request does not affect the + * frame duration.</li> + * <li>Certain format-streams may need to do additional background processing + * before data is consumed/produced by that stream. These processors + * can run concurrently to the rest of the camera pipeline, but + * cannot process more than 1 capture at a time.</li> + * </ul> + * <p>The necessary information for the application, given the model above, + * is provided via the {@link CameraCharacteristics#SCALER_AVAILABLE_MIN_FRAME_DURATIONS android.scaler.availableMinFrameDurations} field. + * These are used to determine the maximum frame rate / minimum frame + * duration that is possible for a given stream configuration.</p> + * <p>Specifically, the application can use the following rules to + * determine the minimum frame duration it can request from the camera + * device:</p> + * <ol> + * <li>Let the set of currently configured input/output streams + * be called <code>S</code>.</li> + * <li>Find the minimum frame durations for each stream in <code>S</code>, by + * looking it up in {@link CameraCharacteristics#SCALER_AVAILABLE_MIN_FRAME_DURATIONS android.scaler.availableMinFrameDurations} (with + * its respective size/format). Let this set of frame durations be called + * <code>F</code>.</li> + * <li>For any given request <code>R</code>, the minimum frame duration allowed + * for <code>R</code> is the maximum out of all values in <code>F</code>. Let the streams + * used in <code>R</code> be called <code>S_r</code>.</li> + * </ol> + * <p>If none of the streams in <code>S_r</code> have a stall time (listed in + * {@link CameraCharacteristics#SCALER_AVAILABLE_STALL_DURATIONS android.scaler.availableStallDurations}), then the frame duration in + * <code>F</code> determines the steady state frame rate that the application will + * get if it uses <code>R</code> as a repeating request. Let this special kind + * of request be called <code>Rsimple</code>.</p> + * <p>A repeating request <code>Rsimple</code> can be <em>occasionally</em> interleaved + * by a single capture of a new request <code>Rstall</code> (which has at least + * one in-use stream with a non-0 stall time) and if <code>Rstall</code> has the + * same minimum frame duration this will not cause a frame rate loss + * if all buffers from the previous <code>Rstall</code> have already been + * delivered.</p> + * <p>For more details about stalling, see + * {@link CameraCharacteristics#SCALER_AVAILABLE_STALL_DURATIONS android.scaler.availableStallDurations}.</p> + * + * @see CameraCharacteristics#SCALER_AVAILABLE_MIN_FRAME_DURATIONS + * @see CameraCharacteristics#SCALER_AVAILABLE_STALL_DURATIONS */ public static final Key<Long> SENSOR_FRAME_DURATION = new Key<Long>("android.sensor.frameDuration", long.class); /** - * <p> - * Gain applied to image data. Must be + * <p>Gain applied to image data. Must be * implemented through analog gain only if set to values - * below 'maximum analog sensitivity'. - * </p><p> - * If the sensor can't apply this exact gain, it should lessen the - * gain to the nearest possible value (rather than gain more). - * </p> - * <p> - * ISO 12232:2006 REI method - * </p> + * below 'maximum analog sensitivity'.</p> + * <p>If the sensor can't apply this exact gain, it should lessen the + * gain to the nearest possible value (rather than gain more).</p> + * <p>ISO 12232:2006 REI method</p> */ public static final Key<Integer> SENSOR_SENSITIVITY = new Key<Integer>("android.sensor.sensitivity", int.class); /** - * <p> - * State of the face detector - * unit - * </p> - * <p> - * Whether face detection is enabled, and whether it + * <p>A pixel <code>[R, G_even, G_odd, B]</code> that supplies the test pattern + * when {@link CaptureRequest#SENSOR_TEST_PATTERN_MODE android.sensor.testPatternMode} is SOLID_COLOR.</p> + * <p>Each color channel is treated as an unsigned 32-bit integer. + * The camera device then uses the most significant X bits + * that correspond to how many bits are in its Bayer raw sensor + * output.</p> + * <p>For example, a sensor with RAW10 Bayer output would use the + * 10 most significant bits from each color channel.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * + * @see CaptureRequest#SENSOR_TEST_PATTERN_MODE + */ + public static final Key<int[]> SENSOR_TEST_PATTERN_DATA = + new Key<int[]>("android.sensor.testPatternData", int[].class); + + /** + * <p>When enabled, the sensor sends a test pattern instead of + * doing a real exposure from the camera.</p> + * <p>When a test pattern is enabled, all manual sensor controls specified + * by android.sensor.* should be ignored. All other controls should + * work as normal.</p> + * <p>For example, if manual flash is enabled, flash firing should still + * occur (and that the test pattern remain unmodified, since the flash + * would not actually affect it).</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * @see #SENSOR_TEST_PATTERN_MODE_OFF + * @see #SENSOR_TEST_PATTERN_MODE_SOLID_COLOR + * @see #SENSOR_TEST_PATTERN_MODE_COLOR_BARS + * @see #SENSOR_TEST_PATTERN_MODE_COLOR_BARS_FADE_TO_GRAY + * @see #SENSOR_TEST_PATTERN_MODE_PN9 + * @see #SENSOR_TEST_PATTERN_MODE_CUSTOM1 + */ + public static final Key<Integer> SENSOR_TEST_PATTERN_MODE = + new Key<Integer>("android.sensor.testPatternMode", int.class); + + /** + * <p>Quality of lens shading correction applied + * to the image data.</p> + * <p>When set to OFF mode, no lens shading correction will be applied by the + * camera device, and an identity lens shading map data will be provided + * if <code>{@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE android.statistics.lensShadingMapMode} == ON</code>. For example, for lens + * shading map with size specified as <code>{@link CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE android.lens.info.shadingMapSize} = [ 4, 3 ]</code>, + * the output {@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap} for this case will be an identity map + * shown below:</p> + * <pre><code>[ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + * 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + * 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + * 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + * 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + * 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 ] + * </code></pre> + * <p>When set to other modes, lens shading correction will be applied by the + * camera device. Applications can request lens shading map data by setting + * {@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE android.statistics.lensShadingMapMode} to ON, and then the camera device will provide + * lens shading map data in {@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap}, with size specified + * by {@link CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE android.lens.info.shadingMapSize}.</p> + * + * @see CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE + * @see CaptureResult#STATISTICS_LENS_SHADING_MAP + * @see CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE + * @see #SHADING_MODE_OFF + * @see #SHADING_MODE_FAST + * @see #SHADING_MODE_HIGH_QUALITY + */ + public static final Key<Integer> SHADING_MODE = + new Key<Integer>("android.shading.mode", int.class); + + /** + * <p>State of the face detector + * unit</p> + * <p>Whether face detection is enabled, and whether it * should output just the basic fields or the full set of * fields. Value must be one of the - * android.statistics.info.availableFaceDetectModes. - * </p> + * {@link CameraCharacteristics#STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES android.statistics.info.availableFaceDetectModes}.</p> + * + * @see CameraCharacteristics#STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES * @see #STATISTICS_FACE_DETECT_MODE_OFF * @see #STATISTICS_FACE_DETECT_MODE_SIMPLE * @see #STATISTICS_FACE_DETECT_MODE_FULL @@ -994,15 +1286,13 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { new Key<Integer>("android.statistics.faceDetectMode", int.class); /** - * <p> - * Whether the HAL needs to output the lens - * shading map in output result metadata - * </p> - * <p> - * When set to ON, - * android.statistics.lensShadingMap must be provided in - * the output result metdata. - * </p> + * <p>Whether the camera device will output the lens + * shading map in output result metadata.</p> + * <p>When set to ON, + * {@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap} must be provided in + * the output result metadata.</p> + * + * @see CaptureResult#STATISTICS_LENS_SHADING_MAP * @see #STATISTICS_LENS_SHADING_MAP_MODE_OFF * @see #STATISTICS_LENS_SHADING_MAP_MODE_ON */ @@ -1010,61 +1300,107 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { new Key<Integer>("android.statistics.lensShadingMapMode", int.class); /** - * <p> - * Table mapping blue input values to output - * values - * </p> - * <p> - * Tonemapping / contrast / gamma curve for the blue - * channel, to use when android.tonemap.mode is CONTRAST_CURVE. - * </p><p> - * See android.tonemap.curveRed for more details. - * </p> + * <p>Tonemapping / contrast / gamma curve for the blue + * channel, to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is + * CONTRAST_CURVE.</p> + * <p>See {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} for more details.</p> + * + * @see CaptureRequest#TONEMAP_CURVE_RED + * @see CaptureRequest#TONEMAP_MODE */ public static final Key<float[]> TONEMAP_CURVE_BLUE = new Key<float[]>("android.tonemap.curveBlue", float[].class); /** - * <p> - * Table mapping green input values to output - * values - * </p> - * <p> - * Tonemapping / contrast / gamma curve for the green - * channel, to use when android.tonemap.mode is CONTRAST_CURVE. - * </p><p> - * See android.tonemap.curveRed for more details. - * </p> + * <p>Tonemapping / contrast / gamma curve for the green + * channel, to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is + * CONTRAST_CURVE.</p> + * <p>See {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} for more details.</p> + * + * @see CaptureRequest#TONEMAP_CURVE_RED + * @see CaptureRequest#TONEMAP_MODE */ public static final Key<float[]> TONEMAP_CURVE_GREEN = new Key<float[]>("android.tonemap.curveGreen", float[].class); /** - * <p> - * Table mapping red input values to output - * values - * </p> - * <p> - * Tonemapping / contrast / gamma curve for the red - * channel, to use when android.tonemap.mode is CONTRAST_CURVE. - * </p><p> - * Since the input and output ranges may vary depending on - * the camera pipeline, the input and output pixel values - * are represented by normalized floating-point values - * between 0 and 1, with 0 == black and 1 == white. - * </p><p> - * The curve should be linearly interpolated between the - * defined points. The points will be listed in increasing - * order of P_IN. For example, if the array is: [0.0, 0.0, - * 0.3, 0.5, 1.0, 1.0], then the input->output mapping - * for a few sample points would be: 0 -> 0, 0.15 -> - * 0.25, 0.3 -> 0.5, 0.5 -> 0.64 - * </p> + * <p>Tonemapping / contrast / gamma curve for the red + * channel, to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is + * CONTRAST_CURVE.</p> + * <p>Each channel's curve is defined by an array of control points:</p> + * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = + * [ P0in, P0out, P1in, P1out, P2in, P2out, P3in, P3out, ..., PNin, PNout ] + * 2 <= N <= {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}</code></pre> + * <p>These are sorted in order of increasing <code>Pin</code>; it is always + * guaranteed that input values 0.0 and 1.0 are included in the list to + * define a complete mapping. For input values between control points, + * the camera device must linearly interpolate between the control + * points.</p> + * <p>Each curve can have an independent number of points, and the number + * of points can be less than max (that is, the request doesn't have to + * always provide a curve with number of points equivalent to + * {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}).</p> + * <p>A few examples, and their corresponding graphical mappings; these + * only specify the red channel and the precision is limited to 4 + * digits, for conciseness.</p> + * <p>Linear mapping:</p> + * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [ 0, 0, 1.0, 1.0 ] + * </code></pre> + * <p><img alt="Linear mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/linear_tonemap.png" /></p> + * <p>Invert mapping:</p> + * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [ 0, 1.0, 1.0, 0 ] + * </code></pre> + * <p><img alt="Inverting mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/inverse_tonemap.png" /></p> + * <p>Gamma 1/2.2 mapping, with 16 control points:</p> + * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [ + * 0.0000, 0.0000, 0.0667, 0.2920, 0.1333, 0.4002, 0.2000, 0.4812, + * 0.2667, 0.5484, 0.3333, 0.6069, 0.4000, 0.6594, 0.4667, 0.7072, + * 0.5333, 0.7515, 0.6000, 0.7928, 0.6667, 0.8317, 0.7333, 0.8685, + * 0.8000, 0.9035, 0.8667, 0.9370, 0.9333, 0.9691, 1.0000, 1.0000 ] + * </code></pre> + * <p><img alt="Gamma = 1/2.2 tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/gamma_tonemap.png" /></p> + * <p>Standard sRGB gamma mapping, per IEC 61966-2-1:1999, with 16 control points:</p> + * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [ + * 0.0000, 0.0000, 0.0667, 0.2864, 0.1333, 0.4007, 0.2000, 0.4845, + * 0.2667, 0.5532, 0.3333, 0.6125, 0.4000, 0.6652, 0.4667, 0.7130, + * 0.5333, 0.7569, 0.6000, 0.7977, 0.6667, 0.8360, 0.7333, 0.8721, + * 0.8000, 0.9063, 0.8667, 0.9389, 0.9333, 0.9701, 1.0000, 1.0000 ] + * </code></pre> + * <p><img alt="sRGB tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p> + * + * @see CaptureRequest#TONEMAP_CURVE_RED + * @see CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS + * @see CaptureRequest#TONEMAP_MODE */ public static final Key<float[]> TONEMAP_CURVE_RED = new Key<float[]>("android.tonemap.curveRed", float[].class); /** + * <p>High-level global contrast/gamma/tonemapping control.</p> + * <p>When switching to an application-defined contrast curve by setting + * {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} to CONTRAST_CURVE, the curve is defined + * per-channel with a set of <code>(in, out)</code> points that specify the + * mapping from input high-bit-depth pixel value to the output + * low-bit-depth value. Since the actual pixel ranges of both input + * and output may change depending on the camera pipeline, the values + * are specified by normalized floating-point numbers.</p> + * <p>More-complex color mapping operations such as 3D color look-up + * tables, selective chroma enhancement, or other non-linear color + * transforms will be disabled when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is + * CONTRAST_CURVE.</p> + * <p>When using either FAST or HIGH_QUALITY, the camera device will + * emit its own tonemap curve in {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed}, + * {@link CaptureRequest#TONEMAP_CURVE_GREEN android.tonemap.curveGreen}, and {@link CaptureRequest#TONEMAP_CURVE_BLUE android.tonemap.curveBlue}. + * These values are always available, and as close as possible to the + * actually used nonlinear/nonglobal transforms.</p> + * <p>If a request is sent with TRANSFORM_MATRIX with the camera device's + * provided curve in FAST or HIGH_QUALITY, the image's tonemap will be + * roughly the same.</p> + * + * @see CaptureRequest#TONEMAP_CURVE_BLUE + * @see CaptureRequest#TONEMAP_CURVE_GREEN + * @see CaptureRequest#TONEMAP_CURVE_RED + * @see CaptureRequest#TONEMAP_MODE * @see #TONEMAP_MODE_CONTRAST_CURVE * @see #TONEMAP_MODE_FAST * @see #TONEMAP_MODE_HIGH_QUALITY @@ -1073,49 +1409,59 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { new Key<Integer>("android.tonemap.mode", int.class); /** - * <p> - * This LED is nominally used to indicate to the user + * <p>This LED is nominally used to indicate to the user * that the camera is powered on and may be streaming images back to the * Application Processor. In certain rare circumstances, the OS may * disable this when video is processed locally and not transmitted to - * any untrusted applications. - * </p><p> - * In particular, the LED *must* always be on when the data could be - * transmitted off the device. The LED *should* always be on whenever - * data is stored locally on the device. - * </p><p> - * The LED *may* be off if a trusted application is using the data that - * doesn't violate the above rules. - * </p> - * + * any untrusted applications.</p> + * <p>In particular, the LED <em>must</em> always be on when the data could be + * transmitted off the device. The LED <em>should</em> always be on whenever + * data is stored locally on the device.</p> + * <p>The LED <em>may</em> be off if a trusted application is using the data that + * doesn't violate the above rules.</p> * @hide */ public static final Key<Boolean> LED_TRANSMIT = new Key<Boolean>("android.led.transmit", boolean.class); /** - * <p> - * Whether black-level compensation is locked - * to its current values, or is free to vary - * </p> - * <p> - * When set to ON, the values used for black-level - * compensation must not change until the lock is set to - * OFF - * </p><p> - * Since changes to certain capture parameters (such as + * <p>Whether black-level compensation is locked + * to its current values, or is free to vary.</p> + * <p>When set to ON, the values used for black-level + * compensation will not change until the lock is set to + * OFF.</p> + * <p>Since changes to certain capture parameters (such as * exposure time) may require resetting of black level - * compensation, the HAL must report whether setting the - * black level lock was successful in the output result - * metadata. - * </p><p> - * The black level locking must happen at the sensor, and not at the ISP. - * If for some reason black level locking is no longer legal (for example, - * the analog gain has changed, which forces black levels to be - * recalculated), then the HAL is free to override this request (and it - * must report 'OFF' when this does happen) until the next time locking - * is legal again. - * </p> + * compensation, the camera device must report whether setting + * the black level lock was successful in the output result + * metadata.</p> + * <p>For example, if a sequence of requests is as follows:</p> + * <ul> + * <li>Request 1: Exposure = 10ms, Black level lock = OFF</li> + * <li>Request 2: Exposure = 10ms, Black level lock = ON</li> + * <li>Request 3: Exposure = 10ms, Black level lock = ON</li> + * <li>Request 4: Exposure = 20ms, Black level lock = ON</li> + * <li>Request 5: Exposure = 20ms, Black level lock = ON</li> + * <li>Request 6: Exposure = 20ms, Black level lock = ON</li> + * </ul> + * <p>And the exposure change in Request 4 requires the camera + * device to reset the black level offsets, then the output + * result metadata is expected to be:</p> + * <ul> + * <li>Result 1: Exposure = 10ms, Black level lock = OFF</li> + * <li>Result 2: Exposure = 10ms, Black level lock = ON</li> + * <li>Result 3: Exposure = 10ms, Black level lock = ON</li> + * <li>Result 4: Exposure = 20ms, Black level lock = OFF</li> + * <li>Result 5: Exposure = 20ms, Black level lock = ON</li> + * <li>Result 6: Exposure = 20ms, Black level lock = ON</li> + * </ul> + * <p>This indicates to the application that on frame 4, black + * levels were reset due to exposure value changes, and pixel + * values may not be consistent across captures.</p> + * <p>The camera device will maintain the lock to the extent + * possible, only overriding the lock to OFF when changes to + * other request parameters require a black level recalculation + * or reset.</p> */ public static final Key<Boolean> BLACK_LEVEL_LOCK = new Key<Boolean>("android.blackLevel.lock", boolean.class); diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index 535b963..0f2c7f7 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -16,8 +16,6 @@ package android.hardware.camera2; -import android.graphics.Point; -import android.graphics.Rect; import android.hardware.camera2.impl.CameraMetadataNative; /** @@ -124,104 +122,308 @@ public final class CaptureResult extends CameraMetadata { * modify the comment blocks at the start or end. *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~*/ + /** - * <p> - * A color transform matrix to use to transform - * from sensor RGB color space to output linear sRGB color space - * </p> - * <p> - * This matrix is either set by HAL when the request - * android.colorCorrection.mode is not TRANSFORM_MATRIX, or + * <p>A color transform matrix to use to transform + * from sensor RGB color space to output linear sRGB color space</p> + * <p>This matrix is either set by the camera device when the request + * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is not TRANSFORM_MATRIX, or * directly by the application in the request when the - * android.colorCorrection.mode is TRANSFORM_MATRIX. - * </p><p> - * In the latter case, the HAL may round the matrix to account - * for precision issues; the final rounded matrix should be - * reported back in this matrix result metadata. - * </p> + * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is TRANSFORM_MATRIX.</p> + * <p>In the latter case, the camera device may round the matrix to account + * for precision issues; the final rounded matrix should be reported back + * in this matrix result metadata. The transform should keep the magnitude + * of the output color values within <code>[0, 1.0]</code> (assuming input color + * values is within the normalized range <code>[0, 1.0]</code>), or clipping may occur.</p> + * + * @see CaptureRequest#COLOR_CORRECTION_MODE */ public static final Key<Rational[]> COLOR_CORRECTION_TRANSFORM = new Key<Rational[]>("android.colorCorrection.transform", Rational[].class); /** - * <p> - * Gains applying to Bayer color channels for - * white-balance - * </p> - * <p> - * The 4-channel white-balance gains are defined in - * the order of [R G_even G_odd B], where G_even is the gain - * for green pixels on even rows of the output, and G_odd - * is the gain for greenpixels on the odd rows. if a HAL + * <p>Gains applying to Bayer raw color channels for + * white-balance.</p> + * <p>The 4-channel white-balance gains are defined in + * the order of <code>[R G_even G_odd B]</code>, where <code>G_even</code> is the gain + * for green pixels on even rows of the output, and <code>G_odd</code> + * is the gain for green pixels on the odd rows. if a HAL * does not support a separate gain for even/odd green channels, - * it should use the G_even value,and write G_odd equal to - * G_even in the output result metadata. - * </p><p> - * This array is either set by HAL when the request - * android.colorCorrection.mode is not TRANSFORM_MATRIX, or + * it should use the <code>G_even</code> value, and write <code>G_odd</code> equal to + * <code>G_even</code> in the output result metadata.</p> + * <p>This array is either set by the camera device when the request + * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is not TRANSFORM_MATRIX, or * directly by the application in the request when the - * android.colorCorrection.mode is TRANSFORM_MATRIX. - * </p><p> - * The ouput should be the gains actually applied by the HAL to - * the current frame. - * </p> + * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is TRANSFORM_MATRIX.</p> + * <p>The output should be the gains actually applied by the camera device to + * the current frame.</p> + * + * @see CaptureRequest#COLOR_CORRECTION_MODE */ public static final Key<float[]> COLOR_CORRECTION_GAINS = new Key<float[]>("android.colorCorrection.gains", float[].class); /** - * <p> - * The ID sent with the latest - * CAMERA2_TRIGGER_PRECAPTURE_METERING call - * </p> - * <p> - * Must be 0 if no + * <p>The ID sent with the latest + * CAMERA2_TRIGGER_PRECAPTURE_METERING call</p> + * <p>Must be 0 if no * CAMERA2_TRIGGER_PRECAPTURE_METERING trigger received yet * by HAL. Always updated even if AE algorithm ignores the - * trigger - * </p> - * + * trigger</p> * @hide */ public static final Key<Integer> CONTROL_AE_PRECAPTURE_ID = new Key<Integer>("android.control.aePrecaptureId", int.class); /** - * <p> - * List of areas to use for - * metering - * </p> - * <p> - * Each area is a rectangle plus weight: xmin, ymin, - * xmax, ymax, weight. The rectangle is defined inclusive of the - * specified coordinates. - * </p><p> - * The coordinate system is based on the active pixel array, + * <p>The desired mode for the camera device's + * auto-exposure routine.</p> + * <p>This control is only effective if {@link CaptureRequest#CONTROL_MODE android.control.mode} is + * AUTO.</p> + * <p>When set to any of the ON modes, the camera device's + * auto-exposure routine is enabled, overriding the + * application's selected exposure time, sensor sensitivity, + * and frame duration ({@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}, + * {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}, and + * {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}). If one of the FLASH modes + * is selected, the camera device's flash unit controls are + * also overridden.</p> + * <p>The FLASH modes are only available if the camera device + * has a flash unit ({@link CameraCharacteristics#FLASH_INFO_AVAILABLE android.flash.info.available} is <code>true</code>).</p> + * <p>If flash TORCH mode is desired, this field must be set to + * ON or OFF, and {@link CaptureRequest#FLASH_MODE android.flash.mode} set to TORCH.</p> + * <p>When set to any of the ON modes, the values chosen by the + * camera device auto-exposure routine for the overridden + * fields for a given capture will be available in its + * CaptureResult.</p> + * + * @see CaptureRequest#CONTROL_MODE + * @see CameraCharacteristics#FLASH_INFO_AVAILABLE + * @see CaptureRequest#FLASH_MODE + * @see CaptureRequest#SENSOR_EXPOSURE_TIME + * @see CaptureRequest#SENSOR_FRAME_DURATION + * @see CaptureRequest#SENSOR_SENSITIVITY + * @see #CONTROL_AE_MODE_OFF + * @see #CONTROL_AE_MODE_ON + * @see #CONTROL_AE_MODE_ON_AUTO_FLASH + * @see #CONTROL_AE_MODE_ON_ALWAYS_FLASH + * @see #CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE + */ + public static final Key<Integer> CONTROL_AE_MODE = + new Key<Integer>("android.control.aeMode", int.class); + + /** + * <p>List of areas to use for + * metering.</p> + * <p>Each area is a rectangle plus weight: xmin, ymin, + * xmax, ymax, weight. The rectangle is defined to be inclusive of the + * specified coordinates.</p> + * <p>The coordinate system is based on the active pixel array, * with (0,0) being the top-left pixel in the active pixel array, and - * (android.sensor.info.activeArraySize.width - 1, - * android.sensor.info.activeArraySize.height - 1) being the + * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1, + * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the * bottom-right pixel in the active pixel array. The weight - * should be nonnegative. - * </p><p> - * If all regions have 0 weight, then no specific metering area - * needs to be used by the HAL. If the metering region is - * outside the current android.scaler.cropRegion, the HAL - * should ignore the sections outside the region and output the - * used sections in the frame metadata - * </p> + * should be nonnegative.</p> + * <p>If all regions have 0 weight, then no specific metering area + * needs to be used by the camera device. If the metering region is + * outside the current {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}, the camera device + * will ignore the sections outside the region and output the + * used sections in the frame metadata.</p> + * + * @see CaptureRequest#SCALER_CROP_REGION + * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE */ public static final Key<int[]> CONTROL_AE_REGIONS = new Key<int[]>("android.control.aeRegions", int[].class); /** - * <p> - * Current state of AE algorithm - * </p> - * <p> - * Whenever the AE algorithm state changes, a - * MSG_AUTOEXPOSURE notification must be send if a - * notification callback is registered. - * </p> + * <p>Current state of AE algorithm</p> + * <p>Switching between or enabling AE modes ({@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode}) always + * resets the AE state to INACTIVE. Similarly, switching between {@link CaptureRequest#CONTROL_MODE android.control.mode}, + * or {@link CaptureRequest#CONTROL_SCENE_MODE android.control.sceneMode} if <code>{@link CaptureRequest#CONTROL_MODE android.control.mode} == USE_SCENE_MODE</code> resets all + * the algorithm states to INACTIVE.</p> + * <p>The camera device can do several state transitions between two results, if it is + * allowed by the state transition table. For example: INACTIVE may never actually be + * seen in a result.</p> + * <p>The state in the result is the state for this image (in sync with this image): if + * AE state becomes CONVERGED, then the image data associated with this result should + * be good to use.</p> + * <p>Below are state transition tables for different AE modes.</p> + * <table> + * <thead> + * <tr> + * <th align="center">State</th> + * <th align="center">Transition Cause</th> + * <th align="center">New State</th> + * <th align="center">Notes</th> + * </tr> + * </thead> + * <tbody> + * <tr> + * <td align="center">INACTIVE</td> + * <td align="center"></td> + * <td align="center">INACTIVE</td> + * <td align="center">Camera device auto exposure algorithm is disabled</td> + * </tr> + * </tbody> + * </table> + * <p>When {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is AE_MODE_ON_*:</p> + * <table> + * <thead> + * <tr> + * <th align="center">State</th> + * <th align="center">Transition Cause</th> + * <th align="center">New State</th> + * <th align="center">Notes</th> + * </tr> + * </thead> + * <tbody> + * <tr> + * <td align="center">INACTIVE</td> + * <td align="center">Camera device initiates AE scan</td> + * <td align="center">SEARCHING</td> + * <td align="center">Values changing</td> + * </tr> + * <tr> + * <td align="center">INACTIVE</td> + * <td align="center">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td> + * <td align="center">LOCKED</td> + * <td align="center">Values locked</td> + * </tr> + * <tr> + * <td align="center">SEARCHING</td> + * <td align="center">Camera device finishes AE scan</td> + * <td align="center">CONVERGED</td> + * <td align="center">Good values, not changing</td> + * </tr> + * <tr> + * <td align="center">SEARCHING</td> + * <td align="center">Camera device finishes AE scan</td> + * <td align="center">FLASH_REQUIRED</td> + * <td align="center">Converged but too dark w/o flash</td> + * </tr> + * <tr> + * <td align="center">SEARCHING</td> + * <td align="center">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td> + * <td align="center">LOCKED</td> + * <td align="center">Values locked</td> + * </tr> + * <tr> + * <td align="center">CONVERGED</td> + * <td align="center">Camera device initiates AE scan</td> + * <td align="center">SEARCHING</td> + * <td align="center">Values changing</td> + * </tr> + * <tr> + * <td align="center">CONVERGED</td> + * <td align="center">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td> + * <td align="center">LOCKED</td> + * <td align="center">Values locked</td> + * </tr> + * <tr> + * <td align="center">FLASH_REQUIRED</td> + * <td align="center">Camera device initiates AE scan</td> + * <td align="center">SEARCHING</td> + * <td align="center">Values changing</td> + * </tr> + * <tr> + * <td align="center">FLASH_REQUIRED</td> + * <td align="center">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td> + * <td align="center">LOCKED</td> + * <td align="center">Values locked</td> + * </tr> + * <tr> + * <td align="center">LOCKED</td> + * <td align="center">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is OFF</td> + * <td align="center">SEARCHING</td> + * <td align="center">Values not good after unlock</td> + * </tr> + * <tr> + * <td align="center">LOCKED</td> + * <td align="center">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is OFF</td> + * <td align="center">CONVERGED</td> + * <td align="center">Values good after unlock</td> + * </tr> + * <tr> + * <td align="center">LOCKED</td> + * <td align="center">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is OFF</td> + * <td align="center">FLASH_REQUIRED</td> + * <td align="center">Exposure good, but too dark</td> + * </tr> + * <tr> + * <td align="center">PRECAPTURE</td> + * <td align="center">Sequence done. {@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is OFF</td> + * <td align="center">CONVERGED</td> + * <td align="center">Ready for high-quality capture</td> + * </tr> + * <tr> + * <td align="center">PRECAPTURE</td> + * <td align="center">Sequence done. {@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td> + * <td align="center">LOCKED</td> + * <td align="center">Ready for high-quality capture</td> + * </tr> + * <tr> + * <td align="center">Any state</td> + * <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is START</td> + * <td align="center">PRECAPTURE</td> + * <td align="center">Start AE precapture metering sequence</td> + * </tr> + * </tbody> + * </table> + * <p>For the above table, the camera device may skip reporting any state changes that happen + * without application intervention (i.e. mode switch, trigger, locking). Any state that + * can be skipped in that manner is called a transient state.</p> + * <p>For example, for above AE modes (AE_MODE_ON_*), in addition to the state transitions + * listed in above table, it is also legal for the camera device to skip one or more + * transient states between two results. See below table for examples:</p> + * <table> + * <thead> + * <tr> + * <th align="center">State</th> + * <th align="center">Transition Cause</th> + * <th align="center">New State</th> + * <th align="center">Notes</th> + * </tr> + * </thead> + * <tbody> + * <tr> + * <td align="center">INACTIVE</td> + * <td align="center">Camera device finished AE scan</td> + * <td align="center">CONVERGED</td> + * <td align="center">Values are already good, transient states are skipped by camera device.</td> + * </tr> + * <tr> + * <td align="center">Any state</td> + * <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is START, sequence done</td> + * <td align="center">FLASH_REQUIRED</td> + * <td align="center">Converged but too dark w/o flash after a precapture sequence, transient states are skipped by camera device.</td> + * </tr> + * <tr> + * <td align="center">Any state</td> + * <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is START, sequence done</td> + * <td align="center">CONVERGED</td> + * <td align="center">Converged after a precapture sequence, transient states are skipped by camera device.</td> + * </tr> + * <tr> + * <td align="center">CONVERGED</td> + * <td align="center">Camera device finished AE scan</td> + * <td align="center">FLASH_REQUIRED</td> + * <td align="center">Converged but too dark w/o flash after a new scan, transient states are skipped by camera device.</td> + * </tr> + * <tr> + * <td align="center">FLASH_REQUIRED</td> + * <td align="center">Camera device finished AE scan</td> + * <td align="center">CONVERGED</td> + * <td align="center">Converged after a new scan, transient states are skipped by camera device.</td> + * </tr> + * </tbody> + * </table> + * + * @see CaptureRequest#CONTROL_AE_LOCK + * @see CaptureRequest#CONTROL_AE_MODE + * @see CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER + * @see CaptureRequest#CONTROL_MODE + * @see CaptureRequest#CONTROL_SCENE_MODE * @see #CONTROL_AE_STATE_INACTIVE * @see #CONTROL_AE_STATE_SEARCHING * @see #CONTROL_AE_STATE_CONVERGED @@ -233,10 +435,17 @@ public final class CaptureResult extends CameraMetadata { new Key<Integer>("android.control.aeState", int.class); /** - * <p> - * Whether AF is currently enabled, and what - * mode it is set to - * </p> + * <p>Whether AF is currently enabled, and what + * mode it is set to</p> + * <p>Only effective if {@link CaptureRequest#CONTROL_MODE android.control.mode} = AUTO and the lens is not fixed focus + * (i.e. <code>{@link CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE android.lens.info.minimumFocusDistance} > 0</code>).</p> + * <p>If the lens is controlled by the camera device auto-focus algorithm, + * the camera device will report the current AF status in {@link CaptureResult#CONTROL_AF_STATE android.control.afState} + * in result metadata.</p> + * + * @see CaptureResult#CONTROL_AF_STATE + * @see CaptureRequest#CONTROL_MODE + * @see CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE * @see #CONTROL_AF_MODE_OFF * @see #CONTROL_AF_MODE_AUTO * @see #CONTROL_AF_MODE_MACRO @@ -248,41 +457,415 @@ public final class CaptureResult extends CameraMetadata { new Key<Integer>("android.control.afMode", int.class); /** - * <p> - * List of areas to use for focus - * estimation - * </p> - * <p> - * Each area is a rectangle plus weight: xmin, ymin, - * xmax, ymax, weight. The rectangle is defined inclusive of the - * specified coordinates. - * </p><p> - * The coordinate system is based on the active pixel array, + * <p>List of areas to use for focus + * estimation.</p> + * <p>Each area is a rectangle plus weight: xmin, ymin, + * xmax, ymax, weight. The rectangle is defined to be inclusive of the + * specified coordinates.</p> + * <p>The coordinate system is based on the active pixel array, * with (0,0) being the top-left pixel in the active pixel array, and - * (android.sensor.info.activeArraySize.width - 1, - * android.sensor.info.activeArraySize.height - 1) being the + * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1, + * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the * bottom-right pixel in the active pixel array. The weight - * should be nonnegative. - * </p><p> - * If all regions have 0 weight, then no specific focus area - * needs to be used by the HAL. If the focusing region is - * outside the current android.scaler.cropRegion, the HAL - * should ignore the sections outside the region and output the - * used sections in the frame metadata - * </p> + * should be nonnegative.</p> + * <p>If all regions have 0 weight, then no specific focus area + * needs to be used by the camera device. If the focusing region is + * outside the current {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}, the camera device + * will ignore the sections outside the region and output the + * used sections in the frame metadata.</p> + * + * @see CaptureRequest#SCALER_CROP_REGION + * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE */ public static final Key<int[]> CONTROL_AF_REGIONS = new Key<int[]>("android.control.afRegions", int[].class); /** - * <p> - * Current state of AF algorithm - * </p> - * <p> - * Whenever the AF algorithm state changes, a - * MSG_AUTOFOCUS notification must be send if a notification - * callback is registered. - * </p> + * <p>Current state of AF algorithm.</p> + * <p>Switching between or enabling AF modes ({@link CaptureRequest#CONTROL_AF_MODE android.control.afMode}) always + * resets the AF state to INACTIVE. Similarly, switching between {@link CaptureRequest#CONTROL_MODE android.control.mode}, + * or {@link CaptureRequest#CONTROL_SCENE_MODE android.control.sceneMode} if <code>{@link CaptureRequest#CONTROL_MODE android.control.mode} == USE_SCENE_MODE</code> resets all + * the algorithm states to INACTIVE.</p> + * <p>The camera device can do several state transitions between two results, if it is + * allowed by the state transition table. For example: INACTIVE may never actually be + * seen in a result.</p> + * <p>The state in the result is the state for this image (in sync with this image): if + * AF state becomes FOCUSED, then the image data associated with this result should + * be sharp.</p> + * <p>Below are state transition tables for different AF modes.</p> + * <p>When {@link CaptureRequest#CONTROL_AF_MODE android.control.afMode} is AF_MODE_OFF or AF_MODE_EDOF:</p> + * <table> + * <thead> + * <tr> + * <th align="center">State</th> + * <th align="center">Transition Cause</th> + * <th align="center">New State</th> + * <th align="center">Notes</th> + * </tr> + * </thead> + * <tbody> + * <tr> + * <td align="center">INACTIVE</td> + * <td align="center"></td> + * <td align="center">INACTIVE</td> + * <td align="center">Never changes</td> + * </tr> + * </tbody> + * </table> + * <p>When {@link CaptureRequest#CONTROL_AF_MODE android.control.afMode} is AF_MODE_AUTO or AF_MODE_MACRO:</p> + * <table> + * <thead> + * <tr> + * <th align="center">State</th> + * <th align="center">Transition Cause</th> + * <th align="center">New State</th> + * <th align="center">Notes</th> + * </tr> + * </thead> + * <tbody> + * <tr> + * <td align="center">INACTIVE</td> + * <td align="center">AF_TRIGGER</td> + * <td align="center">ACTIVE_SCAN</td> + * <td align="center">Start AF sweep, Lens now moving</td> + * </tr> + * <tr> + * <td align="center">ACTIVE_SCAN</td> + * <td align="center">AF sweep done</td> + * <td align="center">FOCUSED_LOCKED</td> + * <td align="center">Focused, Lens now locked</td> + * </tr> + * <tr> + * <td align="center">ACTIVE_SCAN</td> + * <td align="center">AF sweep done</td> + * <td align="center">NOT_FOCUSED_LOCKED</td> + * <td align="center">Not focused, Lens now locked</td> + * </tr> + * <tr> + * <td align="center">ACTIVE_SCAN</td> + * <td align="center">AF_CANCEL</td> + * <td align="center">INACTIVE</td> + * <td align="center">Cancel/reset AF, Lens now locked</td> + * </tr> + * <tr> + * <td align="center">FOCUSED_LOCKED</td> + * <td align="center">AF_CANCEL</td> + * <td align="center">INACTIVE</td> + * <td align="center">Cancel/reset AF</td> + * </tr> + * <tr> + * <td align="center">FOCUSED_LOCKED</td> + * <td align="center">AF_TRIGGER</td> + * <td align="center">ACTIVE_SCAN</td> + * <td align="center">Start new sweep, Lens now moving</td> + * </tr> + * <tr> + * <td align="center">NOT_FOCUSED_LOCKED</td> + * <td align="center">AF_CANCEL</td> + * <td align="center">INACTIVE</td> + * <td align="center">Cancel/reset AF</td> + * </tr> + * <tr> + * <td align="center">NOT_FOCUSED_LOCKED</td> + * <td align="center">AF_TRIGGER</td> + * <td align="center">ACTIVE_SCAN</td> + * <td align="center">Start new sweep, Lens now moving</td> + * </tr> + * <tr> + * <td align="center">Any state</td> + * <td align="center">Mode change</td> + * <td align="center">INACTIVE</td> + * <td align="center"></td> + * </tr> + * </tbody> + * </table> + * <p>For the above table, the camera device may skip reporting any state changes that happen + * without application intervention (i.e. mode switch, trigger, locking). Any state that + * can be skipped in that manner is called a transient state.</p> + * <p>For example, for these AF modes (AF_MODE_AUTO and AF_MODE_MACRO), in addition to the + * state transitions listed in above table, it is also legal for the camera device to skip + * one or more transient states between two results. See below table for examples:</p> + * <table> + * <thead> + * <tr> + * <th align="center">State</th> + * <th align="center">Transition Cause</th> + * <th align="center">New State</th> + * <th align="center">Notes</th> + * </tr> + * </thead> + * <tbody> + * <tr> + * <td align="center">INACTIVE</td> + * <td align="center">AF_TRIGGER</td> + * <td align="center">FOCUSED_LOCKED</td> + * <td align="center">Focus is already good or good after a scan, lens is now locked.</td> + * </tr> + * <tr> + * <td align="center">INACTIVE</td> + * <td align="center">AF_TRIGGER</td> + * <td align="center">NOT_FOCUSED_LOCKED</td> + * <td align="center">Focus failed after a scan, lens is now locked.</td> + * </tr> + * <tr> + * <td align="center">FOCUSED_LOCKED</td> + * <td align="center">AF_TRIGGER</td> + * <td align="center">FOCUSED_LOCKED</td> + * <td align="center">Focus is already good or good after a scan, lens is now locked.</td> + * </tr> + * <tr> + * <td align="center">NOT_FOCUSED_LOCKED</td> + * <td align="center">AF_TRIGGER</td> + * <td align="center">FOCUSED_LOCKED</td> + * <td align="center">Focus is good after a scan, lens is not locked.</td> + * </tr> + * </tbody> + * </table> + * <p>When {@link CaptureRequest#CONTROL_AF_MODE android.control.afMode} is AF_MODE_CONTINUOUS_VIDEO:</p> + * <table> + * <thead> + * <tr> + * <th align="center">State</th> + * <th align="center">Transition Cause</th> + * <th align="center">New State</th> + * <th align="center">Notes</th> + * </tr> + * </thead> + * <tbody> + * <tr> + * <td align="center">INACTIVE</td> + * <td align="center">Camera device initiates new scan</td> + * <td align="center">PASSIVE_SCAN</td> + * <td align="center">Start AF scan, Lens now moving</td> + * </tr> + * <tr> + * <td align="center">INACTIVE</td> + * <td align="center">AF_TRIGGER</td> + * <td align="center">NOT_FOCUSED_LOCKED</td> + * <td align="center">AF state query, Lens now locked</td> + * </tr> + * <tr> + * <td align="center">PASSIVE_SCAN</td> + * <td align="center">Camera device completes current scan</td> + * <td align="center">PASSIVE_FOCUSED</td> + * <td align="center">End AF scan, Lens now locked</td> + * </tr> + * <tr> + * <td align="center">PASSIVE_SCAN</td> + * <td align="center">Camera device fails current scan</td> + * <td align="center">PASSIVE_UNFOCUSED</td> + * <td align="center">End AF scan, Lens now locked</td> + * </tr> + * <tr> + * <td align="center">PASSIVE_SCAN</td> + * <td align="center">AF_TRIGGER</td> + * <td align="center">FOCUSED_LOCKED</td> + * <td align="center">Immediate trans. If focus is good, Lens now locked</td> + * </tr> + * <tr> + * <td align="center">PASSIVE_SCAN</td> + * <td align="center">AF_TRIGGER</td> + * <td align="center">NOT_FOCUSED_LOCKED</td> + * <td align="center">Immediate trans. if focus is bad, Lens now locked</td> + * </tr> + * <tr> + * <td align="center">PASSIVE_SCAN</td> + * <td align="center">AF_CANCEL</td> + * <td align="center">INACTIVE</td> + * <td align="center">Reset lens position, Lens now locked</td> + * </tr> + * <tr> + * <td align="center">PASSIVE_FOCUSED</td> + * <td align="center">Camera device initiates new scan</td> + * <td align="center">PASSIVE_SCAN</td> + * <td align="center">Start AF scan, Lens now moving</td> + * </tr> + * <tr> + * <td align="center">PASSIVE_UNFOCUSED</td> + * <td align="center">Camera device initiates new scan</td> + * <td align="center">PASSIVE_SCAN</td> + * <td align="center">Start AF scan, Lens now moving</td> + * </tr> + * <tr> + * <td align="center">PASSIVE_FOCUSED</td> + * <td align="center">AF_TRIGGER</td> + * <td align="center">FOCUSED_LOCKED</td> + * <td align="center">Immediate trans. Lens now locked</td> + * </tr> + * <tr> + * <td align="center">PASSIVE_UNFOCUSED</td> + * <td align="center">AF_TRIGGER</td> + * <td align="center">NOT_FOCUSED_LOCKED</td> + * <td align="center">Immediate trans. Lens now locked</td> + * </tr> + * <tr> + * <td align="center">FOCUSED_LOCKED</td> + * <td align="center">AF_TRIGGER</td> + * <td align="center">FOCUSED_LOCKED</td> + * <td align="center">No effect</td> + * </tr> + * <tr> + * <td align="center">FOCUSED_LOCKED</td> + * <td align="center">AF_CANCEL</td> + * <td align="center">INACTIVE</td> + * <td align="center">Restart AF scan</td> + * </tr> + * <tr> + * <td align="center">NOT_FOCUSED_LOCKED</td> + * <td align="center">AF_TRIGGER</td> + * <td align="center">NOT_FOCUSED_LOCKED</td> + * <td align="center">No effect</td> + * </tr> + * <tr> + * <td align="center">NOT_FOCUSED_LOCKED</td> + * <td align="center">AF_CANCEL</td> + * <td align="center">INACTIVE</td> + * <td align="center">Restart AF scan</td> + * </tr> + * </tbody> + * </table> + * <p>When {@link CaptureRequest#CONTROL_AF_MODE android.control.afMode} is AF_MODE_CONTINUOUS_PICTURE:</p> + * <table> + * <thead> + * <tr> + * <th align="center">State</th> + * <th align="center">Transition Cause</th> + * <th align="center">New State</th> + * <th align="center">Notes</th> + * </tr> + * </thead> + * <tbody> + * <tr> + * <td align="center">INACTIVE</td> + * <td align="center">Camera device initiates new scan</td> + * <td align="center">PASSIVE_SCAN</td> + * <td align="center">Start AF scan, Lens now moving</td> + * </tr> + * <tr> + * <td align="center">INACTIVE</td> + * <td align="center">AF_TRIGGER</td> + * <td align="center">NOT_FOCUSED_LOCKED</td> + * <td align="center">AF state query, Lens now locked</td> + * </tr> + * <tr> + * <td align="center">PASSIVE_SCAN</td> + * <td align="center">Camera device completes current scan</td> + * <td align="center">PASSIVE_FOCUSED</td> + * <td align="center">End AF scan, Lens now locked</td> + * </tr> + * <tr> + * <td align="center">PASSIVE_SCAN</td> + * <td align="center">Camera device fails current scan</td> + * <td align="center">PASSIVE_UNFOCUSED</td> + * <td align="center">End AF scan, Lens now locked</td> + * </tr> + * <tr> + * <td align="center">PASSIVE_SCAN</td> + * <td align="center">AF_TRIGGER</td> + * <td align="center">FOCUSED_LOCKED</td> + * <td align="center">Eventual trans. once focus good, Lens now locked</td> + * </tr> + * <tr> + * <td align="center">PASSIVE_SCAN</td> + * <td align="center">AF_TRIGGER</td> + * <td align="center">NOT_FOCUSED_LOCKED</td> + * <td align="center">Eventual trans. if cannot focus, Lens now locked</td> + * </tr> + * <tr> + * <td align="center">PASSIVE_SCAN</td> + * <td align="center">AF_CANCEL</td> + * <td align="center">INACTIVE</td> + * <td align="center">Reset lens position, Lens now locked</td> + * </tr> + * <tr> + * <td align="center">PASSIVE_FOCUSED</td> + * <td align="center">Camera device initiates new scan</td> + * <td align="center">PASSIVE_SCAN</td> + * <td align="center">Start AF scan, Lens now moving</td> + * </tr> + * <tr> + * <td align="center">PASSIVE_UNFOCUSED</td> + * <td align="center">Camera device initiates new scan</td> + * <td align="center">PASSIVE_SCAN</td> + * <td align="center">Start AF scan, Lens now moving</td> + * </tr> + * <tr> + * <td align="center">PASSIVE_FOCUSED</td> + * <td align="center">AF_TRIGGER</td> + * <td align="center">FOCUSED_LOCKED</td> + * <td align="center">Immediate trans. Lens now locked</td> + * </tr> + * <tr> + * <td align="center">PASSIVE_UNFOCUSED</td> + * <td align="center">AF_TRIGGER</td> + * <td align="center">NOT_FOCUSED_LOCKED</td> + * <td align="center">Immediate trans. Lens now locked</td> + * </tr> + * <tr> + * <td align="center">FOCUSED_LOCKED</td> + * <td align="center">AF_TRIGGER</td> + * <td align="center">FOCUSED_LOCKED</td> + * <td align="center">No effect</td> + * </tr> + * <tr> + * <td align="center">FOCUSED_LOCKED</td> + * <td align="center">AF_CANCEL</td> + * <td align="center">INACTIVE</td> + * <td align="center">Restart AF scan</td> + * </tr> + * <tr> + * <td align="center">NOT_FOCUSED_LOCKED</td> + * <td align="center">AF_TRIGGER</td> + * <td align="center">NOT_FOCUSED_LOCKED</td> + * <td align="center">No effect</td> + * </tr> + * <tr> + * <td align="center">NOT_FOCUSED_LOCKED</td> + * <td align="center">AF_CANCEL</td> + * <td align="center">INACTIVE</td> + * <td align="center">Restart AF scan</td> + * </tr> + * </tbody> + * </table> + * <p>When switch between AF_MODE_CONTINUOUS_* (CAF modes) and AF_MODE_AUTO/AF_MODE_MACRO + * (AUTO modes), the initial INACTIVE or PASSIVE_SCAN states may be skipped by the + * camera device. When a trigger is included in a mode switch request, the trigger + * will be evaluated in the context of the new mode in the request. + * See below table for examples:</p> + * <table> + * <thead> + * <tr> + * <th align="center">State</th> + * <th align="center">Transition Cause</th> + * <th align="center">New State</th> + * <th align="center">Notes</th> + * </tr> + * </thead> + * <tbody> + * <tr> + * <td align="center">any state</td> + * <td align="center">CAF-->AUTO mode switch</td> + * <td align="center">INACTIVE</td> + * <td align="center">Mode switch without trigger, initial state must be INACTIVE</td> + * </tr> + * <tr> + * <td align="center">any state</td> + * <td align="center">CAF-->AUTO mode switch with AF_TRIGGER</td> + * <td align="center">trigger-reachable states from INACTIVE</td> + * <td align="center">Mode switch with trigger, INACTIVE is skipped</td> + * </tr> + * <tr> + * <td align="center">any state</td> + * <td align="center">AUTO-->CAF mode switch</td> + * <td align="center">passively reachable states from INACTIVE</td> + * <td align="center">Mode switch without trigger, passive transient state is skipped</td> + * </tr> + * </tbody> + * </table> + * + * @see CaptureRequest#CONTROL_AF_MODE + * @see CaptureRequest#CONTROL_MODE + * @see CaptureRequest#CONTROL_SCENE_MODE * @see #CONTROL_AF_STATE_INACTIVE * @see #CONTROL_AF_STATE_PASSIVE_SCAN * @see #CONTROL_AF_STATE_PASSIVE_FOCUSED @@ -295,30 +878,37 @@ public final class CaptureResult extends CameraMetadata { new Key<Integer>("android.control.afState", int.class); /** - * <p> - * The ID sent with the latest - * CAMERA2_TRIGGER_AUTOFOCUS call - * </p> - * <p> - * Must be 0 if no CAMERA2_TRIGGER_AUTOFOCUS trigger + * <p>The ID sent with the latest + * CAMERA2_TRIGGER_AUTOFOCUS call</p> + * <p>Must be 0 if no CAMERA2_TRIGGER_AUTOFOCUS trigger * received yet by HAL. Always updated even if AF algorithm - * ignores the trigger - * </p> - * + * ignores the trigger</p> * @hide */ public static final Key<Integer> CONTROL_AF_TRIGGER_ID = new Key<Integer>("android.control.afTriggerId", int.class); /** - * <p> - * Whether AWB is currently setting the color + * <p>Whether AWB is currently setting the color * transform fields, and what its illumination target - * is - * </p> - * <p> - * [BC - AWB lock,AWB modes] - * </p> + * is.</p> + * <p>This control is only effective if {@link CaptureRequest#CONTROL_MODE android.control.mode} is AUTO.</p> + * <p>When set to the ON mode, the camera device's auto white balance + * routine is enabled, overriding the application's selected + * {@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform}, {@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains} and + * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode}.</p> + * <p>When set to the OFF mode, the camera device's auto white balance + * routine is disabled. The application manually controls the white + * balance by {@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform}, {@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains} + * and {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode}.</p> + * <p>When set to any other modes, the camera device's auto white balance + * routine is disabled. The camera device uses each particular illumination + * target for white balance adjustment.</p> + * + * @see CaptureRequest#COLOR_CORRECTION_GAINS + * @see CaptureRequest#COLOR_CORRECTION_MODE + * @see CaptureRequest#COLOR_CORRECTION_TRANSFORM + * @see CaptureRequest#CONTROL_MODE * @see #CONTROL_AWB_MODE_OFF * @see #CONTROL_AWB_MODE_AUTO * @see #CONTROL_AWB_MODE_INCANDESCENT @@ -333,43 +923,152 @@ public final class CaptureResult extends CameraMetadata { new Key<Integer>("android.control.awbMode", int.class); /** - * <p> - * List of areas to use for illuminant - * estimation - * </p> - * <p> - * Only used in AUTO mode. - * </p><p> - * Each area is a rectangle plus weight: xmin, ymin, - * xmax, ymax, weight. The rectangle is defined inclusive of the - * specified coordinates. - * </p><p> - * The coordinate system is based on the active pixel array, + * <p>List of areas to use for illuminant + * estimation.</p> + * <p>Only used in AUTO mode.</p> + * <p>Each area is a rectangle plus weight: xmin, ymin, + * xmax, ymax, weight. The rectangle is defined to be inclusive of the + * specified coordinates.</p> + * <p>The coordinate system is based on the active pixel array, * with (0,0) being the top-left pixel in the active pixel array, and - * (android.sensor.info.activeArraySize.width - 1, - * android.sensor.info.activeArraySize.height - 1) being the + * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1, + * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the * bottom-right pixel in the active pixel array. The weight - * should be nonnegative. - * </p><p> - * If all regions have 0 weight, then no specific metering area - * needs to be used by the HAL. If the metering region is - * outside the current android.scaler.cropRegion, the HAL - * should ignore the sections outside the region and output the - * used sections in the frame metadata - * </p> + * should be nonnegative.</p> + * <p>If all regions have 0 weight, then no specific auto-white balance (AWB) area + * needs to be used by the camera device. If the AWB region is + * outside the current {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}, the camera device + * will ignore the sections outside the region and output the + * used sections in the frame metadata.</p> + * + * @see CaptureRequest#SCALER_CROP_REGION + * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE */ public static final Key<int[]> CONTROL_AWB_REGIONS = new Key<int[]>("android.control.awbRegions", int[].class); /** - * <p> - * Current state of AWB algorithm - * </p> - * <p> - * Whenever the AWB algorithm state changes, a - * MSG_AUTOWHITEBALANCE notification must be send if a - * notification callback is registered. - * </p> + * <p>Current state of AWB algorithm</p> + * <p>Switching between or enabling AWB modes ({@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode}) always + * resets the AWB state to INACTIVE. Similarly, switching between {@link CaptureRequest#CONTROL_MODE android.control.mode}, + * or {@link CaptureRequest#CONTROL_SCENE_MODE android.control.sceneMode} if <code>{@link CaptureRequest#CONTROL_MODE android.control.mode} == USE_SCENE_MODE</code> resets all + * the algorithm states to INACTIVE.</p> + * <p>The camera device can do several state transitions between two results, if it is + * allowed by the state transition table. So INACTIVE may never actually be seen in + * a result.</p> + * <p>The state in the result is the state for this image (in sync with this image): if + * AWB state becomes CONVERGED, then the image data associated with this result should + * be good to use.</p> + * <p>Below are state transition tables for different AWB modes.</p> + * <p>When <code>{@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode} != AWB_MODE_AUTO</code>:</p> + * <table> + * <thead> + * <tr> + * <th align="center">State</th> + * <th align="center">Transition Cause</th> + * <th align="center">New State</th> + * <th align="center">Notes</th> + * </tr> + * </thead> + * <tbody> + * <tr> + * <td align="center">INACTIVE</td> + * <td align="center"></td> + * <td align="center">INACTIVE</td> + * <td align="center">Camera device auto white balance algorithm is disabled</td> + * </tr> + * </tbody> + * </table> + * <p>When {@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode} is AWB_MODE_AUTO:</p> + * <table> + * <thead> + * <tr> + * <th align="center">State</th> + * <th align="center">Transition Cause</th> + * <th align="center">New State</th> + * <th align="center">Notes</th> + * </tr> + * </thead> + * <tbody> + * <tr> + * <td align="center">INACTIVE</td> + * <td align="center">Camera device initiates AWB scan</td> + * <td align="center">SEARCHING</td> + * <td align="center">Values changing</td> + * </tr> + * <tr> + * <td align="center">INACTIVE</td> + * <td align="center">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is ON</td> + * <td align="center">LOCKED</td> + * <td align="center">Values locked</td> + * </tr> + * <tr> + * <td align="center">SEARCHING</td> + * <td align="center">Camera device finishes AWB scan</td> + * <td align="center">CONVERGED</td> + * <td align="center">Good values, not changing</td> + * </tr> + * <tr> + * <td align="center">SEARCHING</td> + * <td align="center">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is ON</td> + * <td align="center">LOCKED</td> + * <td align="center">Values locked</td> + * </tr> + * <tr> + * <td align="center">CONVERGED</td> + * <td align="center">Camera device initiates AWB scan</td> + * <td align="center">SEARCHING</td> + * <td align="center">Values changing</td> + * </tr> + * <tr> + * <td align="center">CONVERGED</td> + * <td align="center">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is ON</td> + * <td align="center">LOCKED</td> + * <td align="center">Values locked</td> + * </tr> + * <tr> + * <td align="center">LOCKED</td> + * <td align="center">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is OFF</td> + * <td align="center">SEARCHING</td> + * <td align="center">Values not good after unlock</td> + * </tr> + * </tbody> + * </table> + * <p>For the above table, the camera device may skip reporting any state changes that happen + * without application intervention (i.e. mode switch, trigger, locking). Any state that + * can be skipped in that manner is called a transient state.</p> + * <p>For example, for this AWB mode (AWB_MODE_AUTO), in addition to the state transitions + * listed in above table, it is also legal for the camera device to skip one or more + * transient states between two results. See below table for examples:</p> + * <table> + * <thead> + * <tr> + * <th align="center">State</th> + * <th align="center">Transition Cause</th> + * <th align="center">New State</th> + * <th align="center">Notes</th> + * </tr> + * </thead> + * <tbody> + * <tr> + * <td align="center">INACTIVE</td> + * <td align="center">Camera device finished AWB scan</td> + * <td align="center">CONVERGED</td> + * <td align="center">Values are already good, transient states are skipped by camera device.</td> + * </tr> + * <tr> + * <td align="center">LOCKED</td> + * <td align="center">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is OFF</td> + * <td align="center">CONVERGED</td> + * <td align="center">Values good after unlock, transient states are skipped by camera device.</td> + * </tr> + * </tbody> + * </table> + * + * @see CaptureRequest#CONTROL_AWB_LOCK + * @see CaptureRequest#CONTROL_AWB_MODE + * @see CaptureRequest#CONTROL_MODE + * @see CaptureRequest#CONTROL_SCENE_MODE * @see #CONTROL_AWB_STATE_INACTIVE * @see #CONTROL_AWB_STATE_SEARCHING * @see #CONTROL_AWB_STATE_CONVERGED @@ -379,22 +1078,43 @@ public final class CaptureResult extends CameraMetadata { new Key<Integer>("android.control.awbState", int.class); /** - * <p> - * Overall mode of 3A control - * routines - * </p> + * <p>Overall mode of 3A control + * routines.</p> + * <p>High-level 3A control. When set to OFF, all 3A control + * by the camera device is disabled. The application must set the fields for + * capture parameters itself.</p> + * <p>When set to AUTO, the individual algorithm controls in + * android.control.* are in effect, such as {@link CaptureRequest#CONTROL_AF_MODE android.control.afMode}.</p> + * <p>When set to USE_SCENE_MODE, the individual controls in + * android.control.* are mostly disabled, and the camera device implements + * one of the scene mode settings (such as ACTION, SUNSET, or PARTY) + * as it wishes. The camera device scene mode 3A settings are provided by + * android.control.sceneModeOverrides.</p> + * <p>When set to OFF_KEEP_STATE, it is similar to OFF mode, the only difference + * is that this frame will not be used by camera device background 3A statistics + * update, as if this frame is never captured. This mode can be used in the scenario + * where the application doesn't want a 3A manual control capture to affect + * the subsequent auto 3A capture results.</p> + * + * @see CaptureRequest#CONTROL_AF_MODE * @see #CONTROL_MODE_OFF * @see #CONTROL_MODE_AUTO * @see #CONTROL_MODE_USE_SCENE_MODE + * @see #CONTROL_MODE_OFF_KEEP_STATE */ public static final Key<Integer> CONTROL_MODE = new Key<Integer>("android.control.mode", int.class); /** - * <p> - * Operation mode for edge - * enhancement - * </p> + * <p>Operation mode for edge + * enhancement.</p> + * <p>Edge/sharpness/detail enhancement. OFF means no + * enhancement will be applied by the camera device.</p> + * <p>FAST/HIGH_QUALITY both mean camera device determined enhancement + * will be applied. HIGH_QUALITY mode indicates that the + * camera device will use the highest-quality enhancement algorithms, + * even if it slows down capture rate. FAST means the camera device will + * not slow down capture rate when applying edge enhancement.</p> * @see #EDGE_MODE_OFF * @see #EDGE_MODE_FAST * @see #EDGE_MODE_HIGH_QUALITY @@ -403,9 +1123,25 @@ public final class CaptureResult extends CameraMetadata { new Key<Integer>("android.edge.mode", int.class); /** - * <p> - * Select flash operation mode - * </p> + * <p>The desired mode for for the camera device's flash control.</p> + * <p>This control is only effective when flash unit is available + * (<code>{@link CameraCharacteristics#FLASH_INFO_AVAILABLE android.flash.info.available} == true</code>).</p> + * <p>When this control is used, the {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} must be set to ON or OFF. + * Otherwise, the camera device auto-exposure related flash control (ON_AUTO_FLASH, + * ON_ALWAYS_FLASH, or ON_AUTO_FLASH_REDEYE) will override this control.</p> + * <p>When set to OFF, the camera device will not fire flash for this capture.</p> + * <p>When set to SINGLE, the camera device will fire flash regardless of the camera + * device's auto-exposure routine's result. When used in still capture case, this + * control should be used along with AE precapture metering sequence + * ({@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger}), otherwise, the image may be incorrectly exposed.</p> + * <p>When set to TORCH, the flash will be on continuously. This mode can be used + * for use cases such as preview, auto-focus assist, still capture, or video recording.</p> + * <p>The flash status will be reported by {@link CaptureResult#FLASH_STATE android.flash.state} in the capture result metadata.</p> + * + * @see CaptureRequest#CONTROL_AE_MODE + * @see CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER + * @see CameraCharacteristics#FLASH_INFO_AVAILABLE + * @see CaptureResult#FLASH_STATE * @see #FLASH_MODE_OFF * @see #FLASH_MODE_SINGLE * @see #FLASH_MODE_TORCH @@ -414,10 +1150,13 @@ public final class CaptureResult extends CameraMetadata { new Key<Integer>("android.flash.mode", int.class); /** - * <p> - * Current state of the flash - * unit - * </p> + * <p>Current state of the flash + * unit.</p> + * <p>When the camera device doesn't have flash unit + * (i.e. <code>{@link CameraCharacteristics#FLASH_INFO_AVAILABLE android.flash.info.available} == false</code>), this state will always be UNAVAILABLE. + * Other states indicate the current flash status.</p> + * + * @see CameraCharacteristics#FLASH_INFO_AVAILABLE * @see #FLASH_STATE_UNAVAILABLE * @see #FLASH_STATE_CHARGING * @see #FLASH_STATE_READY @@ -427,140 +1166,181 @@ public final class CaptureResult extends CameraMetadata { new Key<Integer>("android.flash.state", int.class); /** - * <p> - * GPS coordinates to include in output JPEG - * EXIF - * </p> + * <p>List of <code>(x, y)</code> coordinates of hot/defective pixels on the + * sensor, where <code>(x, y)</code> lies between <code>(0, 0)</code>, which is the top-left + * of the pixel array, and the width,height of the pixel array given in + * {@link CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE android.sensor.info.pixelArraySize}. This may include hot pixels + * that lie outside of the active array bounds given by + * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.</p> + * + * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE + * @see CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE + */ + public static final Key<int[]> HOT_PIXEL_MAP = + new Key<int[]>("android.hotPixel.map", int[].class); + + /** + * <p>Set operational mode for hot pixel correction.</p> + * <p>Hotpixel correction interpolates out, or otherwise removes, pixels + * that do not accurately encode the incoming light (i.e. pixels that + * are stuck at an arbitrary value).</p> + * @see #HOT_PIXEL_MODE_OFF + * @see #HOT_PIXEL_MODE_FAST + * @see #HOT_PIXEL_MODE_HIGH_QUALITY + */ + public static final Key<Integer> HOT_PIXEL_MODE = + new Key<Integer>("android.hotPixel.mode", int.class); + + /** + * <p>GPS coordinates to include in output JPEG + * EXIF</p> */ public static final Key<double[]> JPEG_GPS_COORDINATES = new Key<double[]>("android.jpeg.gpsCoordinates", double[].class); /** - * <p> - * 32 characters describing GPS algorithm to - * include in EXIF - * </p> + * <p>32 characters describing GPS algorithm to + * include in EXIF</p> */ public static final Key<String> JPEG_GPS_PROCESSING_METHOD = new Key<String>("android.jpeg.gpsProcessingMethod", String.class); /** - * <p> - * Time GPS fix was made to include in - * EXIF - * </p> + * <p>Time GPS fix was made to include in + * EXIF</p> */ public static final Key<Long> JPEG_GPS_TIMESTAMP = new Key<Long>("android.jpeg.gpsTimestamp", long.class); /** - * <p> - * Orientation of JPEG image to - * write - * </p> + * <p>Orientation of JPEG image to + * write</p> */ public static final Key<Integer> JPEG_ORIENTATION = new Key<Integer>("android.jpeg.orientation", int.class); /** - * <p> - * Compression quality of the final JPEG - * image - * </p> - * <p> - * 85-95 is typical usage range - * </p> + * <p>Compression quality of the final JPEG + * image</p> + * <p>85-95 is typical usage range</p> */ public static final Key<Byte> JPEG_QUALITY = new Key<Byte>("android.jpeg.quality", byte.class); /** - * <p> - * Compression quality of JPEG - * thumbnail - * </p> + * <p>Compression quality of JPEG + * thumbnail</p> */ public static final Key<Byte> JPEG_THUMBNAIL_QUALITY = new Key<Byte>("android.jpeg.thumbnailQuality", byte.class); /** - * <p> - * Resolution of embedded JPEG - * thumbnail - * </p> + * <p>Resolution of embedded JPEG thumbnail</p> + * <p>When set to (0, 0) value, the JPEG EXIF will not contain thumbnail, + * but the captured JPEG will still be a valid image.</p> + * <p>When a jpeg image capture is issued, the thumbnail size selected should have + * the same aspect ratio as the jpeg image.</p> */ public static final Key<android.hardware.camera2.Size> JPEG_THUMBNAIL_SIZE = new Key<android.hardware.camera2.Size>("android.jpeg.thumbnailSize", android.hardware.camera2.Size.class); /** - * <p> - * Size of the lens aperture - * </p> - * <p> - * Will not be supported on most devices. Can only - * pick from supported list - * </p> + * <p>The ratio of lens focal length to the effective + * aperture diameter.</p> + * <p>This will only be supported on the camera devices that + * have variable aperture lens. The aperture value can only be + * one of the values listed in {@link CameraCharacteristics#LENS_INFO_AVAILABLE_APERTURES android.lens.info.availableApertures}.</p> + * <p>When this is supported and {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is OFF, + * this can be set along with {@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}, + * {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}, and {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} + * to achieve manual exposure control.</p> + * <p>The requested aperture value may take several frames to reach the + * requested value; the camera device will report the current (intermediate) + * aperture size in capture result metadata while the aperture is changing. + * While the aperture is still changing, {@link CaptureResult#LENS_STATE android.lens.state} will be set to MOVING.</p> + * <p>When this is supported and {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is one of + * the ON modes, this will be overridden by the camera device + * auto-exposure algorithm, the overridden values are then provided + * back to the user in the corresponding result.</p> + * + * @see CaptureRequest#CONTROL_AE_MODE + * @see CameraCharacteristics#LENS_INFO_AVAILABLE_APERTURES + * @see CaptureResult#LENS_STATE + * @see CaptureRequest#SENSOR_EXPOSURE_TIME + * @see CaptureRequest#SENSOR_FRAME_DURATION + * @see CaptureRequest#SENSOR_SENSITIVITY */ public static final Key<Float> LENS_APERTURE = new Key<Float>("android.lens.aperture", float.class); /** - * <p> - * State of lens neutral density - * filter(s) - * </p> - * <p> - * Will not be supported on most devices. Can only - * pick from supported list - * </p> + * <p>State of lens neutral density filter(s).</p> + * <p>This will not be supported on most camera devices. On devices + * where this is supported, this may only be set to one of the + * values included in {@link CameraCharacteristics#LENS_INFO_AVAILABLE_FILTER_DENSITIES android.lens.info.availableFilterDensities}.</p> + * <p>Lens filters are typically used to lower the amount of light the + * sensor is exposed to (measured in steps of EV). As used here, an EV + * step is the standard logarithmic representation, which are + * non-negative, and inversely proportional to the amount of light + * hitting the sensor. For example, setting this to 0 would result + * in no reduction of the incoming light, and setting this to 2 would + * mean that the filter is set to reduce incoming light by two stops + * (allowing 1/4 of the prior amount of light to the sensor).</p> + * <p>It may take several frames before the lens filter density changes + * to the requested value. While the filter density is still changing, + * {@link CaptureResult#LENS_STATE android.lens.state} will be set to MOVING.</p> + * + * @see CameraCharacteristics#LENS_INFO_AVAILABLE_FILTER_DENSITIES + * @see CaptureResult#LENS_STATE */ public static final Key<Float> LENS_FILTER_DENSITY = new Key<Float>("android.lens.filterDensity", float.class); /** - * <p> - * Lens optical zoom setting - * </p> - * <p> - * Will not be supported on most devices. - * </p> + * <p>The current lens focal length; used for optical zoom.</p> + * <p>This setting controls the physical focal length of the camera + * device's lens. Changing the focal length changes the field of + * view of the camera device, and is usually used for optical zoom.</p> + * <p>Like {@link CaptureRequest#LENS_FOCUS_DISTANCE android.lens.focusDistance} and {@link CaptureRequest#LENS_APERTURE android.lens.aperture}, this + * setting won't be applied instantaneously, and it may take several + * frames before the lens can change to the requested focal length. + * While the focal length is still changing, {@link CaptureResult#LENS_STATE android.lens.state} will + * be set to MOVING.</p> + * <p>This is expected not to be supported on most devices.</p> + * + * @see CaptureRequest#LENS_APERTURE + * @see CaptureRequest#LENS_FOCUS_DISTANCE + * @see CaptureResult#LENS_STATE */ public static final Key<Float> LENS_FOCAL_LENGTH = new Key<Float>("android.lens.focalLength", float.class); /** - * <p> - * Distance to plane of sharpest focus, - * measured from frontmost surface of the lens - * </p> - * <p> - * Should be zero for fixed-focus cameras - * </p> + * <p>Distance to plane of sharpest focus, + * measured from frontmost surface of the lens</p> + * <p>Should be zero for fixed-focus cameras</p> */ public static final Key<Float> LENS_FOCUS_DISTANCE = new Key<Float>("android.lens.focusDistance", float.class); /** - * <p> - * The range of scene distances that are in - * sharp focus (depth of field) - * </p> - * <p> - * If variable focus not supported, can still report - * fixed depth of field range - * </p> + * <p>The range of scene distances that are in + * sharp focus (depth of field)</p> + * <p>If variable focus not supported, can still report + * fixed depth of field range</p> */ public static final Key<float[]> LENS_FOCUS_RANGE = new Key<float[]>("android.lens.focusRange", float[].class); /** - * <p> - * Whether optical image stabilization is - * enabled. - * </p> - * <p> - * Will not be supported on most devices. - * </p> + * <p>Sets whether the camera device uses optical image stabilization (OIS) + * when capturing images.</p> + * <p>OIS is used to compensate for motion blur due to small movements of + * the camera during capture. Unlike digital image stabilization, OIS makes + * use of mechanical elements to stabilize the camera sensor, and thus + * allows for longer exposure times before camera shake becomes + * apparent.</p> + * <p>This is not expected to be supported on most devices.</p> * @see #LENS_OPTICAL_STABILIZATION_MODE_OFF * @see #LENS_OPTICAL_STABILIZATION_MODE_ON */ @@ -568,9 +1348,35 @@ public final class CaptureResult extends CameraMetadata { new Key<Integer>("android.lens.opticalStabilizationMode", int.class); /** - * <p> - * Current lens status - * </p> + * <p>Current lens status.</p> + * <p>For lens parameters {@link CaptureRequest#LENS_FOCAL_LENGTH android.lens.focalLength}, {@link CaptureRequest#LENS_FOCUS_DISTANCE android.lens.focusDistance}, + * {@link CaptureRequest#LENS_FILTER_DENSITY android.lens.filterDensity} and {@link CaptureRequest#LENS_APERTURE android.lens.aperture}, when changes are requested, + * they may take several frames to reach the requested values. This state indicates + * the current status of the lens parameters.</p> + * <p>When the state is STATIONARY, the lens parameters are not changing. This could be + * either because the parameters are all fixed, or because the lens has had enough + * time to reach the most recently-requested values. + * If all these lens parameters are not changable for a camera device, as listed below:</p> + * <ul> + * <li>Fixed focus (<code>{@link CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE android.lens.info.minimumFocusDistance} == 0</code>), which means + * {@link CaptureRequest#LENS_FOCUS_DISTANCE android.lens.focusDistance} parameter will always be 0.</li> + * <li>Fixed focal length ({@link CameraCharacteristics#LENS_INFO_AVAILABLE_FOCAL_LENGTHS android.lens.info.availableFocalLengths} contains single value), + * which means the optical zoom is not supported.</li> + * <li>No ND filter ({@link CameraCharacteristics#LENS_INFO_AVAILABLE_FILTER_DENSITIES android.lens.info.availableFilterDensities} contains only 0).</li> + * <li>Fixed aperture ({@link CameraCharacteristics#LENS_INFO_AVAILABLE_APERTURES android.lens.info.availableApertures} contains single value).</li> + * </ul> + * <p>Then this state will always be STATIONARY.</p> + * <p>When the state is MOVING, it indicates that at least one of the lens parameters + * is changing.</p> + * + * @see CaptureRequest#LENS_APERTURE + * @see CaptureRequest#LENS_FILTER_DENSITY + * @see CaptureRequest#LENS_FOCAL_LENGTH + * @see CaptureRequest#LENS_FOCUS_DISTANCE + * @see CameraCharacteristics#LENS_INFO_AVAILABLE_APERTURES + * @see CameraCharacteristics#LENS_INFO_AVAILABLE_FILTER_DENSITIES + * @see CameraCharacteristics#LENS_INFO_AVAILABLE_FOCAL_LENGTHS + * @see CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE * @see #LENS_STATE_STATIONARY * @see #LENS_STATE_MOVING */ @@ -578,10 +1384,15 @@ public final class CaptureResult extends CameraMetadata { new Key<Integer>("android.lens.state", int.class); /** - * <p> - * Mode of operation for the noise reduction - * algorithm - * </p> + * <p>Mode of operation for the noise reduction + * algorithm</p> + * <p>Noise filtering control. OFF means no noise reduction + * will be applied by the camera device.</p> + * <p>FAST/HIGH_QUALITY both mean camera device determined noise filtering + * will be applied. HIGH_QUALITY mode indicates that the camera device + * will use the highest-quality noise filtering algorithms, + * even if it slows down capture rate. FAST means the camera device should not + * slow down capture rate when applying noise filtering.</p> * @see #NOISE_REDUCTION_MODE_OFF * @see #NOISE_REDUCTION_MODE_FAST * @see #NOISE_REDUCTION_MODE_HIGH_QUALITY @@ -590,14 +1401,11 @@ public final class CaptureResult extends CameraMetadata { new Key<Integer>("android.noiseReduction.mode", int.class); /** - * <p> - * Whether a result given to the framework is the + * <p>Whether a result given to the framework is the * final one for the capture, or only a partial that contains a * subset of the full set of dynamic metadata - * values. - * </p> - * <p> - * The entries in the result metadata buffers for a + * values.</p> + * <p>The entries in the result metadata buffers for a * single capture may not overlap, except for this entry. The * FINAL buffers must retain FIFO ordering relative to the * requests that generate them, so the FINAL buffer for frame 3 must @@ -605,68 +1413,64 @@ public final class CaptureResult extends CameraMetadata { * before the FINAL buffer for frame 4. PARTIAL buffers may be returned * in any order relative to other frames, but all PARTIAL buffers for a given * capture must arrive before the FINAL buffer for that capture. This entry may - * only be used by the HAL if quirks.usePartialResult is set to 1. - * </p> - * - * <b>Optional</b> - This value may be null on some devices. - * + * only be used by the camera device if quirks.usePartialResult is set to 1.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * @hide */ public static final Key<Boolean> QUIRKS_PARTIAL_RESULT = new Key<Boolean>("android.quirks.partialResult", boolean.class); /** - * <p> - * A frame counter set by the framework. This value monotonically + * <p>A frame counter set by the framework. This value monotonically * increases with every new result (that is, each new result has a unique - * frameCount value). - * </p> - * <p> - * Reset on release() - * </p> + * frameCount value).</p> + * <p>Reset on release()</p> */ public static final Key<Integer> REQUEST_FRAME_COUNT = new Key<Integer>("android.request.frameCount", int.class); /** - * <p> - * An application-specified ID for the current + * <p>An application-specified ID for the current * request. Must be maintained unchanged in output - * frame - * </p> - * + * frame</p> * @hide */ public static final Key<Integer> REQUEST_ID = new Key<Integer>("android.request.id", int.class); /** - * <p> - * (x, y, width, height). - * </p><p> - * A rectangle with the top-level corner of (x,y) and size + * <p>Specifies the number of pipeline stages the frame went + * through from when it was exposed to when the final completed result + * was available to the framework.</p> + * <p>Depending on what settings are used in the request, and + * what streams are configured, the data may undergo less processing, + * and some pipeline stages skipped.</p> + * <p>See {@link CameraCharacteristics#REQUEST_PIPELINE_MAX_DEPTH android.request.pipelineMaxDepth} for more details.</p> + * + * @see CameraCharacteristics#REQUEST_PIPELINE_MAX_DEPTH + */ + public static final Key<Byte> REQUEST_PIPELINE_DEPTH = + new Key<Byte>("android.request.pipelineDepth", byte.class); + + /** + * <p>(x, y, width, height).</p> + * <p>A rectangle with the top-level corner of (x,y) and size * (width, height). The region of the sensor that is used for * output. Each stream must use this rectangle to produce its * output, cropping to a smaller region if necessary to - * maintain the stream's aspect ratio. - * </p><p> - * HAL2.x uses only (x, y, width) - * </p> - * <p> - * Any additional per-stream cropping must be done to - * maximize the final pixel area of the stream. - * </p><p> - * For example, if the crop region is set to a 4:3 aspect + * maintain the stream's aspect ratio.</p> + * <p>HAL2.x uses only (x, y, width)</p> + * <p>Any additional per-stream cropping must be done to + * maximize the final pixel area of the stream.</p> + * <p>For example, if the crop region is set to a 4:3 aspect * ratio, then 4:3 streams should use the exact crop * region. 16:9 streams should further crop vertically - * (letterbox). - * </p><p> - * Conversely, if the crop region is set to a 16:9, then 4:3 + * (letterbox).</p> + * <p>Conversely, if the crop region is set to a 16:9, then 4:3 * outputs should crop horizontally (pillarbox), and 16:9 * streams should match exactly. These additional crops must - * be centered within the crop region. - * </p><p> - * The output streams must maintain square pixels at all + * be centered within the crop region.</p> + * <p>The output streams must maintain square pixels at all * times, no matter what the relative aspect ratios of the * crop region and the stream are. Negative values for * corner are allowed for raw output if full pixel array is @@ -675,100 +1479,337 @@ public final class CaptureResult extends CameraMetadata { * for raw output, where only a few fixed scales may be * possible. The width and height of the crop region cannot * be set to be smaller than floor( activeArraySize.width / - * android.scaler.maxDigitalZoom ) and floor( - * activeArraySize.height / android.scaler.maxDigitalZoom), - * respectively. - * </p> + * {@link CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM android.scaler.availableMaxDigitalZoom} ) and floor( + * activeArraySize.height / + * {@link CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM android.scaler.availableMaxDigitalZoom}), respectively.</p> + * + * @see CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM */ public static final Key<android.graphics.Rect> SCALER_CROP_REGION = new Key<android.graphics.Rect>("android.scaler.cropRegion", android.graphics.Rect.class); /** - * <p> - * Duration each pixel is exposed to - * light. - * </p><p> - * If the sensor can't expose this exact duration, it should shorten the - * duration exposed to the nearest possible value (rather than expose longer). - * </p> - * <p> - * 1/10000 - 30 sec range. No bulb mode - * </p> + * <p>Duration each pixel is exposed to + * light.</p> + * <p>If the sensor can't expose this exact duration, it should shorten the + * duration exposed to the nearest possible value (rather than expose longer).</p> + * <p>1/10000 - 30 sec range. No bulb mode</p> */ public static final Key<Long> SENSOR_EXPOSURE_TIME = new Key<Long>("android.sensor.exposureTime", long.class); /** - * <p> - * Duration from start of frame exposure to - * start of next frame exposure - * </p> - * <p> - * Exposure time has priority, so duration is set to - * max(duration, exposure time + overhead) - * </p> + * <p>Duration from start of frame exposure to + * start of next frame exposure.</p> + * <p>The maximum frame rate that can be supported by a camera subsystem is + * a function of many factors:</p> + * <ul> + * <li>Requested resolutions of output image streams</li> + * <li>Availability of binning / skipping modes on the imager</li> + * <li>The bandwidth of the imager interface</li> + * <li>The bandwidth of the various ISP processing blocks</li> + * </ul> + * <p>Since these factors can vary greatly between different ISPs and + * sensors, the camera abstraction tries to represent the bandwidth + * restrictions with as simple a model as possible.</p> + * <p>The model presented has the following characteristics:</p> + * <ul> + * <li>The image sensor is always configured to output the smallest + * resolution possible given the application's requested output stream + * sizes. The smallest resolution is defined as being at least as large + * as the largest requested output stream size; the camera pipeline must + * never digitally upsample sensor data when the crop region covers the + * whole sensor. In general, this means that if only small output stream + * resolutions are configured, the sensor can provide a higher frame + * rate.</li> + * <li>Since any request may use any or all the currently configured + * output streams, the sensor and ISP must be configured to support + * scaling a single capture to all the streams at the same time. This + * means the camera pipeline must be ready to produce the largest + * requested output size without any delay. Therefore, the overall + * frame rate of a given configured stream set is governed only by the + * largest requested stream resolution.</li> + * <li>Using more than one output stream in a request does not affect the + * frame duration.</li> + * <li>Certain format-streams may need to do additional background processing + * before data is consumed/produced by that stream. These processors + * can run concurrently to the rest of the camera pipeline, but + * cannot process more than 1 capture at a time.</li> + * </ul> + * <p>The necessary information for the application, given the model above, + * is provided via the {@link CameraCharacteristics#SCALER_AVAILABLE_MIN_FRAME_DURATIONS android.scaler.availableMinFrameDurations} field. + * These are used to determine the maximum frame rate / minimum frame + * duration that is possible for a given stream configuration.</p> + * <p>Specifically, the application can use the following rules to + * determine the minimum frame duration it can request from the camera + * device:</p> + * <ol> + * <li>Let the set of currently configured input/output streams + * be called <code>S</code>.</li> + * <li>Find the minimum frame durations for each stream in <code>S</code>, by + * looking it up in {@link CameraCharacteristics#SCALER_AVAILABLE_MIN_FRAME_DURATIONS android.scaler.availableMinFrameDurations} (with + * its respective size/format). Let this set of frame durations be called + * <code>F</code>.</li> + * <li>For any given request <code>R</code>, the minimum frame duration allowed + * for <code>R</code> is the maximum out of all values in <code>F</code>. Let the streams + * used in <code>R</code> be called <code>S_r</code>.</li> + * </ol> + * <p>If none of the streams in <code>S_r</code> have a stall time (listed in + * {@link CameraCharacteristics#SCALER_AVAILABLE_STALL_DURATIONS android.scaler.availableStallDurations}), then the frame duration in + * <code>F</code> determines the steady state frame rate that the application will + * get if it uses <code>R</code> as a repeating request. Let this special kind + * of request be called <code>Rsimple</code>.</p> + * <p>A repeating request <code>Rsimple</code> can be <em>occasionally</em> interleaved + * by a single capture of a new request <code>Rstall</code> (which has at least + * one in-use stream with a non-0 stall time) and if <code>Rstall</code> has the + * same minimum frame duration this will not cause a frame rate loss + * if all buffers from the previous <code>Rstall</code> have already been + * delivered.</p> + * <p>For more details about stalling, see + * {@link CameraCharacteristics#SCALER_AVAILABLE_STALL_DURATIONS android.scaler.availableStallDurations}.</p> + * + * @see CameraCharacteristics#SCALER_AVAILABLE_MIN_FRAME_DURATIONS + * @see CameraCharacteristics#SCALER_AVAILABLE_STALL_DURATIONS */ public static final Key<Long> SENSOR_FRAME_DURATION = new Key<Long>("android.sensor.frameDuration", long.class); /** - * <p> - * Gain applied to image data. Must be + * <p>Gain applied to image data. Must be * implemented through analog gain only if set to values - * below 'maximum analog sensitivity'. - * </p><p> - * If the sensor can't apply this exact gain, it should lessen the - * gain to the nearest possible value (rather than gain more). - * </p> - * <p> - * ISO 12232:2006 REI method - * </p> + * below 'maximum analog sensitivity'.</p> + * <p>If the sensor can't apply this exact gain, it should lessen the + * gain to the nearest possible value (rather than gain more).</p> + * <p>ISO 12232:2006 REI method</p> */ public static final Key<Integer> SENSOR_SENSITIVITY = new Key<Integer>("android.sensor.sensitivity", int.class); /** - * <p> - * Time at start of exposure of first - * row - * </p> - * <p> - * Monotonic, should be synced to other timestamps in - * system - * </p> + * <p>Time at start of exposure of first + * row</p> + * <p>Monotonic, should be synced to other timestamps in + * system</p> */ public static final Key<Long> SENSOR_TIMESTAMP = new Key<Long>("android.sensor.timestamp", long.class); /** - * <p> - * The temperature of the sensor, sampled at the time - * exposure began for this frame. - * </p><p> - * The thermal diode being queried should be inside the sensor PCB, or - * somewhere close to it. - * </p> - * - * <b>Optional</b> - This value may be null on some devices. + * <p>The temperature of the sensor, sampled at the time + * exposure began for this frame.</p> + * <p>The thermal diode being queried should be inside the sensor PCB, or + * somewhere close to it.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * <p><b>Full capability</b> - + * Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the + * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p> * - * <b>{@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL}</b> - - * Present on all devices that report being FULL level hardware devices in the - * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL HARDWARE_LEVEL} key. + * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL */ public static final Key<Float> SENSOR_TEMPERATURE = new Key<Float>("android.sensor.temperature", float.class); /** - * <p> - * State of the face detector - * unit - * </p> - * <p> - * Whether face detection is enabled, and whether it + * <p>A reference illumination source roughly matching the current scene + * illumination, which is used to describe the sensor color space + * transformations.</p> + * <p>The values in this tag correspond to the values defined for the + * EXIF LightSource tag. These illuminants are standard light sources + * that are often used for calibrating camera devices.</p> + * @see #SENSOR_REFERENCE_ILLUMINANT_DAYLIGHT + * @see #SENSOR_REFERENCE_ILLUMINANT_FLUORESCENT + * @see #SENSOR_REFERENCE_ILLUMINANT_TUNGSTEN + * @see #SENSOR_REFERENCE_ILLUMINANT_FLASH + * @see #SENSOR_REFERENCE_ILLUMINANT_FINE_WEATHER + * @see #SENSOR_REFERENCE_ILLUMINANT_CLOUDY_WEATHER + * @see #SENSOR_REFERENCE_ILLUMINANT_SHADE + * @see #SENSOR_REFERENCE_ILLUMINANT_DAYLIGHT_FLUORESCENT + * @see #SENSOR_REFERENCE_ILLUMINANT_DAY_WHITE_FLUORESCENT + * @see #SENSOR_REFERENCE_ILLUMINANT_COOL_WHITE_FLUORESCENT + * @see #SENSOR_REFERENCE_ILLUMINANT_WHITE_FLUORESCENT + * @see #SENSOR_REFERENCE_ILLUMINANT_STANDARD_A + * @see #SENSOR_REFERENCE_ILLUMINANT_STANDARD_B + * @see #SENSOR_REFERENCE_ILLUMINANT_STANDARD_C + * @see #SENSOR_REFERENCE_ILLUMINANT_D55 + * @see #SENSOR_REFERENCE_ILLUMINANT_D65 + * @see #SENSOR_REFERENCE_ILLUMINANT_D75 + * @see #SENSOR_REFERENCE_ILLUMINANT_D50 + * @see #SENSOR_REFERENCE_ILLUMINANT_ISO_STUDIO_TUNGSTEN + */ + public static final Key<Integer> SENSOR_REFERENCE_ILLUMINANT = + new Key<Integer>("android.sensor.referenceIlluminant", int.class); + + /** + * <p>A per-device calibration transform matrix to be applied after the + * color space transform when rendering the raw image buffer.</p> + * <p>This matrix is expressed as a 3x3 matrix in row-major-order, and + * contains a per-device calibration transform that maps colors + * from reference camera color space (i.e. the "golden module" + * colorspace) into this camera device's linear native sensor color + * space for the current scene illumination and white balance choice.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + */ + public static final Key<Rational[]> SENSOR_CALIBRATION_TRANSFORM = + new Key<Rational[]>("android.sensor.calibrationTransform", Rational[].class); + + /** + * <p>A matrix that transforms color values from CIE XYZ color space to + * reference camera color space when rendering the raw image buffer.</p> + * <p>This matrix is expressed as a 3x3 matrix in row-major-order, and + * contains a color transform matrix that maps colors from the CIE + * XYZ color space to the reference camera raw color space (i.e. the + * "golden module" colorspace) for the current scene illumination and + * white balance choice.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + */ + public static final Key<Rational[]> SENSOR_COLOR_TRANSFORM = + new Key<Rational[]>("android.sensor.colorTransform", Rational[].class); + + /** + * <p>A matrix that transforms white balanced camera colors to the CIE XYZ + * colorspace with a D50 whitepoint.</p> + * <p>This matrix is expressed as a 3x3 matrix in row-major-order, and contains + * a color transform matrix that maps a unit vector in the linear native + * sensor color space to the D50 whitepoint in CIE XYZ color space.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + */ + public static final Key<Rational[]> SENSOR_FORWARD_MATRIX = + new Key<Rational[]>("android.sensor.forwardMatrix", Rational[].class); + + /** + * <p>The estimated white balance at the time of capture.</p> + * <p>The estimated white balance encoded as the RGB values of the + * perfectly neutral color point in the linear native sensor color space. + * The order of the values is R, G, B; where R is in the lowest index.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + */ + public static final Key<Rational[]> SENSOR_NEUTRAL_COLOR_POINT = + new Key<Rational[]>("android.sensor.neutralColorPoint", Rational[].class); + + /** + * <p>A mapping containing a hue shift, saturation scale, and value scale + * for each pixel.</p> + * <p>hue_samples, saturation_samples, and value_samples are given in + * {@link CameraCharacteristics#SENSOR_PROFILE_HUE_SAT_MAP_DIMENSIONS android.sensor.profileHueSatMapDimensions}.</p> + * <p>Each entry of this map contains three floats corresponding to the + * hue shift, saturation scale, and value scale, respectively; where the + * hue shift has the lowest index. The map entries are stored in the tag + * in nested loop order, with the value divisions in the outer loop, the + * hue divisions in the middle loop, and the saturation divisions in the + * inner loop. All zero input saturation entries are required to have a + * value scale factor of 1.0.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * + * @see CameraCharacteristics#SENSOR_PROFILE_HUE_SAT_MAP_DIMENSIONS + */ + public static final Key<float[]> SENSOR_PROFILE_HUE_SAT_MAP = + new Key<float[]>("android.sensor.profileHueSatMap", float[].class); + + /** + * <p>A list of x,y samples defining a tone-mapping curve for gamma adjustment.</p> + * <p>This tag contains a default tone curve that can be applied while + * processing the image as a starting point for user adjustments. + * The curve is specified as a list of value pairs in linear gamma. + * The curve is interpolated using a cubic spline.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + */ + public static final Key<float[]> SENSOR_PROFILE_TONE_CURVE = + new Key<float[]>("android.sensor.profileToneCurve", float[].class); + + /** + * <p>The worst-case divergence between Bayer green channels.</p> + * <p>This value is an estimate of the worst case split between the + * Bayer green channels in the red and blue rows in the sensor color + * filter array.</p> + * <p>The green split is calculated as follows:</p> + * <ol> + * <li>A representative 5x5 pixel window W within the active + * sensor array is chosen.</li> + * <li>The arithmetic mean of the green channels from the red + * rows (mean_Gr) within W is computed.</li> + * <li>The arithmetic mean of the green channels from the blue + * rows (mean_Gb) within W is computed.</li> + * <li>The maximum ratio R of the two means is computed as follows: + * <code>R = max((mean_Gr + 1)/(mean_Gb + 1), (mean_Gb + 1)/(mean_Gr + 1))</code></li> + * </ol> + * <p>The ratio R is the green split divergence reported for this property, + * which represents how much the green channels differ in the mosaic + * pattern. This value is typically used to determine the treatment of + * the green mosaic channels when demosaicing.</p> + * <p>The green split value can be roughly interpreted as follows:</p> + * <ul> + * <li>R < 1.03 is a negligible split (<3% divergence).</li> + * <li>1.20 <= R >= 1.03 will require some software + * correction to avoid demosaic errors (3-20% divergence).</li> + * <li>R > 1.20 will require strong software correction to produce + * a usuable image (>20% divergence).</li> + * </ul> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + */ + public static final Key<Float> SENSOR_GREEN_SPLIT = + new Key<Float>("android.sensor.greenSplit", float.class); + + /** + * <p>When enabled, the sensor sends a test pattern instead of + * doing a real exposure from the camera.</p> + * <p>When a test pattern is enabled, all manual sensor controls specified + * by android.sensor.* should be ignored. All other controls should + * work as normal.</p> + * <p>For example, if manual flash is enabled, flash firing should still + * occur (and that the test pattern remain unmodified, since the flash + * would not actually affect it).</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * @see #SENSOR_TEST_PATTERN_MODE_OFF + * @see #SENSOR_TEST_PATTERN_MODE_SOLID_COLOR + * @see #SENSOR_TEST_PATTERN_MODE_COLOR_BARS + * @see #SENSOR_TEST_PATTERN_MODE_COLOR_BARS_FADE_TO_GRAY + * @see #SENSOR_TEST_PATTERN_MODE_PN9 + * @see #SENSOR_TEST_PATTERN_MODE_CUSTOM1 + */ + public static final Key<Integer> SENSOR_TEST_PATTERN_MODE = + new Key<Integer>("android.sensor.testPatternMode", int.class); + + /** + * <p>Quality of lens shading correction applied + * to the image data.</p> + * <p>When set to OFF mode, no lens shading correction will be applied by the + * camera device, and an identity lens shading map data will be provided + * if <code>{@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE android.statistics.lensShadingMapMode} == ON</code>. For example, for lens + * shading map with size specified as <code>{@link CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE android.lens.info.shadingMapSize} = [ 4, 3 ]</code>, + * the output {@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap} for this case will be an identity map + * shown below:</p> + * <pre><code>[ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + * 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + * 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + * 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + * 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + * 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 ] + * </code></pre> + * <p>When set to other modes, lens shading correction will be applied by the + * camera device. Applications can request lens shading map data by setting + * {@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE android.statistics.lensShadingMapMode} to ON, and then the camera device will provide + * lens shading map data in {@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap}, with size specified + * by {@link CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE android.lens.info.shadingMapSize}.</p> + * + * @see CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE + * @see CaptureResult#STATISTICS_LENS_SHADING_MAP + * @see CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE + * @see #SHADING_MODE_OFF + * @see #SHADING_MODE_FAST + * @see #SHADING_MODE_HIGH_QUALITY + */ + public static final Key<Integer> SHADING_MODE = + new Key<Integer>("android.shading.mode", int.class); + + /** + * <p>State of the face detector + * unit</p> + * <p>Whether face detection is enabled, and whether it * should output just the basic fields or the full set of * fields. Value must be one of the - * android.statistics.info.availableFaceDetectModes. - * </p> + * {@link CameraCharacteristics#STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES android.statistics.info.availableFaceDetectModes}.</p> + * + * @see CameraCharacteristics#STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES * @see #STATISTICS_FACE_DETECT_MODE_OFF * @see #STATISTICS_FACE_DETECT_MODE_SIMPLE * @see #STATISTICS_FACE_DETECT_MODE_FULL @@ -777,129 +1818,151 @@ public final class CaptureResult extends CameraMetadata { new Key<Integer>("android.statistics.faceDetectMode", int.class); /** - * <p> - * List of unique IDs for detected - * faces - * </p> - * <p> - * Only available if faceDetectMode == FULL - * </p> + * <p>List of unique IDs for detected + * faces</p> + * <p>Only available if faceDetectMode == FULL</p> + * @hide */ public static final Key<int[]> STATISTICS_FACE_IDS = new Key<int[]>("android.statistics.faceIds", int[].class); /** - * <p> - * List of landmarks for detected - * faces - * </p> - * <p> - * Only available if faceDetectMode == FULL - * </p> + * <p>List of landmarks for detected + * faces</p> + * <p>Only available if faceDetectMode == FULL</p> + * @hide */ public static final Key<int[]> STATISTICS_FACE_LANDMARKS = new Key<int[]>("android.statistics.faceLandmarks", int[].class); /** - * <p> - * List of the bounding rectangles for detected - * faces - * </p> - * <p> - * Only available if faceDetectMode != OFF - * </p> + * <p>List of the bounding rectangles for detected + * faces</p> + * <p>Only available if faceDetectMode != OFF</p> + * @hide */ public static final Key<android.graphics.Rect[]> STATISTICS_FACE_RECTANGLES = new Key<android.graphics.Rect[]>("android.statistics.faceRectangles", android.graphics.Rect[].class); /** - * <p> - * List of the face confidence scores for - * detected faces - * </p> - * <p> - * Only available if faceDetectMode != OFF. The value should be - * meaningful (for example, setting 100 at all times is illegal). - * </p> + * <p>List of the face confidence scores for + * detected faces</p> + * <p>Only available if faceDetectMode != OFF. The value should be + * meaningful (for example, setting 100 at all times is illegal).</p> + * @hide */ public static final Key<byte[]> STATISTICS_FACE_SCORES = new Key<byte[]>("android.statistics.faceScores", byte[].class); /** - * <p> - * A low-resolution map of lens shading, per - * color channel - * </p> - * <p> - * Assume bilinear interpolation of map. The least - * shaded section of the image should have a gain factor - * of 1; all other sections should have gains above 1. - * the map should be on the order of 30-40 rows, and - * must be smaller than 64x64. - * </p><p> - * When android.colorCorrection.mode = TRANSFORM_MATRIX, the map - * must take into account the colorCorrection settings. - * </p> + * <p>The shading map is a low-resolution floating-point map + * that lists the coefficients used to correct for vignetting, for each + * Bayer color channel.</p> + * <p>The least shaded section of the image should have a gain factor + * of 1; all other sections should have gains above 1.</p> + * <p>When {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} = TRANSFORM_MATRIX, the map + * must take into account the colorCorrection settings.</p> + * <p>The shading map is for the entire active pixel array, and is not + * affected by the crop region specified in the request. Each shading map + * entry is the value of the shading compensation map over a specific + * pixel on the sensor. Specifically, with a (N x M) resolution shading + * map, and an active pixel array size (W x H), shading map entry + * (x,y) ϵ (0 ... N-1, 0 ... M-1) is the value of the shading map at + * pixel ( ((W-1)/(N-1)) * x, ((H-1)/(M-1)) * y) for the four color channels. + * The map is assumed to be bilinearly interpolated between the sample points.</p> + * <p>The channel order is [R, Geven, Godd, B], where Geven is the green + * channel for the even rows of a Bayer pattern, and Godd is the odd rows. + * The shading map is stored in a fully interleaved format, and its size + * is provided in the camera static metadata by {@link CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE android.lens.info.shadingMapSize}.</p> + * <p>The shading map should have on the order of 30-40 rows and columns, + * and must be smaller than 64x64.</p> + * <p>As an example, given a very small map defined as:</p> + * <pre><code>{@link CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE android.lens.info.shadingMapSize} = [ 4, 3 ] + * {@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap} = + * [ 1.3, 1.2, 1.15, 1.2, 1.2, 1.2, 1.15, 1.2, + * 1.1, 1.2, 1.2, 1.2, 1.3, 1.2, 1.3, 1.3, + * 1.2, 1.2, 1.25, 1.1, 1.1, 1.1, 1.1, 1.0, + * 1.0, 1.0, 1.0, 1.0, 1.2, 1.3, 1.25, 1.2, + * 1.3, 1.2, 1.2, 1.3, 1.2, 1.15, 1.1, 1.2, + * 1.2, 1.1, 1.0, 1.2, 1.3, 1.15, 1.2, 1.3 ] + * </code></pre> + * <p>The low-resolution scaling map images for each channel are + * (displayed using nearest-neighbor interpolation):</p> + * <p><img alt="Red lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/red_shading.png" /> + * <img alt="Green (even rows) lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/green_e_shading.png" /> + * <img alt="Green (odd rows) lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/green_o_shading.png" /> + * <img alt="Blue lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/blue_shading.png" /></p> + * <p>As a visualization only, inverting the full-color map to recover an + * image of a gray wall (using bicubic interpolation for visual quality) as captured by the sensor gives:</p> + * <p><img alt="Image of a uniform white wall (inverse shading map)" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/inv_shading.png" /></p> + * + * @see CaptureRequest#COLOR_CORRECTION_MODE + * @see CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE + * @see CaptureResult#STATISTICS_LENS_SHADING_MAP */ public static final Key<float[]> STATISTICS_LENS_SHADING_MAP = new Key<float[]>("android.statistics.lensShadingMap", float[].class); /** - * <p> - * The best-fit color channel gains calculated - * by the HAL's statistics units for the current output frame - * </p> - * <p> - * This may be different than the gains used for this frame, + * <p>The best-fit color channel gains calculated + * by the camera device's statistics units for the current output frame.</p> + * <p>This may be different than the gains used for this frame, * since statistics processing on data from a new frame * typically completes after the transform has already been - * applied to that frame. - * </p><p> - * The 4 channel gains are defined in Bayer domain, - * see android.colorCorrection.gains for details. - * </p><p> - * This value should always be calculated by the AWB block, - * regardless of the android.control.* current values. - * </p> + * applied to that frame.</p> + * <p>The 4 channel gains are defined in Bayer domain, + * see {@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains} for details.</p> + * <p>This value should always be calculated by the AWB block, + * regardless of the android.control.* current values.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * + * @see CaptureRequest#COLOR_CORRECTION_GAINS + * @hide */ public static final Key<float[]> STATISTICS_PREDICTED_COLOR_GAINS = new Key<float[]>("android.statistics.predictedColorGains", float[].class); /** - * <p> - * The best-fit color transform matrix estimate - * calculated by the HAL's statistics units for the current - * output frame - * </p> - * <p> - * The HAL must provide the estimate from its + * <p>The best-fit color transform matrix estimate + * calculated by the camera device's statistics units for the current + * output frame.</p> + * <p>The camera device will provide the estimate from its * statistics unit on the white balance transforms to use - * for the next frame. These are the values the HAL believes + * for the next frame. These are the values the camera device believes * are the best fit for the current output frame. This may * be different than the transform used for this frame, since * statistics processing on data from a new frame typically * completes after the transform has already been applied to - * that frame. - * </p><p> - * These estimates must be provided for all frames, even if - * capture settings and color transforms are set by the application. - * </p><p> - * This value should always be calculated by the AWB block, - * regardless of the android.control.* current values. - * </p> + * that frame.</p> + * <p>These estimates must be provided for all frames, even if + * capture settings and color transforms are set by the application.</p> + * <p>This value should always be calculated by the AWB block, + * regardless of the android.control.* current values.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * @hide */ public static final Key<Rational[]> STATISTICS_PREDICTED_COLOR_TRANSFORM = new Key<Rational[]>("android.statistics.predictedColorTransform", Rational[].class); /** - * <p> - * The HAL estimated scene illumination lighting - * frequency - * </p> - * <p> - * Report NONE if there doesn't appear to be flickering - * illumination - * </p> + * <p>The camera device estimated scene illumination lighting + * frequency.</p> + * <p>Many light sources, such as most fluorescent lights, flicker at a rate + * that depends on the local utility power standards. This flicker must be + * accounted for by auto-exposure routines to avoid artifacts in captured images. + * The camera device uses this entry to tell the application what the scene + * illuminant frequency is.</p> + * <p>When manual exposure control is enabled + * (<code>{@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} == OFF</code> or <code>{@link CaptureRequest#CONTROL_MODE android.control.mode} == OFF</code>), + * the {@link CaptureRequest#CONTROL_AE_ANTIBANDING_MODE android.control.aeAntibandingMode} doesn't do the antibanding, and the + * application can ensure it selects exposure times that do not cause banding + * issues by looking into this metadata field. See {@link CaptureRequest#CONTROL_AE_ANTIBANDING_MODE android.control.aeAntibandingMode} + * for more details.</p> + * <p>Report NONE if there doesn't appear to be flickering illumination.</p> + * + * @see CaptureRequest#CONTROL_AE_ANTIBANDING_MODE + * @see CaptureRequest#CONTROL_AE_MODE + * @see CaptureRequest#CONTROL_MODE * @see #STATISTICS_SCENE_FLICKER_NONE * @see #STATISTICS_SCENE_FLICKER_50HZ * @see #STATISTICS_SCENE_FLICKER_60HZ @@ -908,61 +1971,107 @@ public final class CaptureResult extends CameraMetadata { new Key<Integer>("android.statistics.sceneFlicker", int.class); /** - * <p> - * Table mapping blue input values to output - * values - * </p> - * <p> - * Tonemapping / contrast / gamma curve for the blue - * channel, to use when android.tonemap.mode is CONTRAST_CURVE. - * </p><p> - * See android.tonemap.curveRed for more details. - * </p> + * <p>Tonemapping / contrast / gamma curve for the blue + * channel, to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is + * CONTRAST_CURVE.</p> + * <p>See {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} for more details.</p> + * + * @see CaptureRequest#TONEMAP_CURVE_RED + * @see CaptureRequest#TONEMAP_MODE */ public static final Key<float[]> TONEMAP_CURVE_BLUE = new Key<float[]>("android.tonemap.curveBlue", float[].class); /** - * <p> - * Table mapping green input values to output - * values - * </p> - * <p> - * Tonemapping / contrast / gamma curve for the green - * channel, to use when android.tonemap.mode is CONTRAST_CURVE. - * </p><p> - * See android.tonemap.curveRed for more details. - * </p> + * <p>Tonemapping / contrast / gamma curve for the green + * channel, to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is + * CONTRAST_CURVE.</p> + * <p>See {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} for more details.</p> + * + * @see CaptureRequest#TONEMAP_CURVE_RED + * @see CaptureRequest#TONEMAP_MODE */ public static final Key<float[]> TONEMAP_CURVE_GREEN = new Key<float[]>("android.tonemap.curveGreen", float[].class); /** - * <p> - * Table mapping red input values to output - * values - * </p> - * <p> - * Tonemapping / contrast / gamma curve for the red - * channel, to use when android.tonemap.mode is CONTRAST_CURVE. - * </p><p> - * Since the input and output ranges may vary depending on - * the camera pipeline, the input and output pixel values - * are represented by normalized floating-point values - * between 0 and 1, with 0 == black and 1 == white. - * </p><p> - * The curve should be linearly interpolated between the - * defined points. The points will be listed in increasing - * order of P_IN. For example, if the array is: [0.0, 0.0, - * 0.3, 0.5, 1.0, 1.0], then the input->output mapping - * for a few sample points would be: 0 -> 0, 0.15 -> - * 0.25, 0.3 -> 0.5, 0.5 -> 0.64 - * </p> + * <p>Tonemapping / contrast / gamma curve for the red + * channel, to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is + * CONTRAST_CURVE.</p> + * <p>Each channel's curve is defined by an array of control points:</p> + * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = + * [ P0in, P0out, P1in, P1out, P2in, P2out, P3in, P3out, ..., PNin, PNout ] + * 2 <= N <= {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}</code></pre> + * <p>These are sorted in order of increasing <code>Pin</code>; it is always + * guaranteed that input values 0.0 and 1.0 are included in the list to + * define a complete mapping. For input values between control points, + * the camera device must linearly interpolate between the control + * points.</p> + * <p>Each curve can have an independent number of points, and the number + * of points can be less than max (that is, the request doesn't have to + * always provide a curve with number of points equivalent to + * {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}).</p> + * <p>A few examples, and their corresponding graphical mappings; these + * only specify the red channel and the precision is limited to 4 + * digits, for conciseness.</p> + * <p>Linear mapping:</p> + * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [ 0, 0, 1.0, 1.0 ] + * </code></pre> + * <p><img alt="Linear mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/linear_tonemap.png" /></p> + * <p>Invert mapping:</p> + * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [ 0, 1.0, 1.0, 0 ] + * </code></pre> + * <p><img alt="Inverting mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/inverse_tonemap.png" /></p> + * <p>Gamma 1/2.2 mapping, with 16 control points:</p> + * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [ + * 0.0000, 0.0000, 0.0667, 0.2920, 0.1333, 0.4002, 0.2000, 0.4812, + * 0.2667, 0.5484, 0.3333, 0.6069, 0.4000, 0.6594, 0.4667, 0.7072, + * 0.5333, 0.7515, 0.6000, 0.7928, 0.6667, 0.8317, 0.7333, 0.8685, + * 0.8000, 0.9035, 0.8667, 0.9370, 0.9333, 0.9691, 1.0000, 1.0000 ] + * </code></pre> + * <p><img alt="Gamma = 1/2.2 tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/gamma_tonemap.png" /></p> + * <p>Standard sRGB gamma mapping, per IEC 61966-2-1:1999, with 16 control points:</p> + * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [ + * 0.0000, 0.0000, 0.0667, 0.2864, 0.1333, 0.4007, 0.2000, 0.4845, + * 0.2667, 0.5532, 0.3333, 0.6125, 0.4000, 0.6652, 0.4667, 0.7130, + * 0.5333, 0.7569, 0.6000, 0.7977, 0.6667, 0.8360, 0.7333, 0.8721, + * 0.8000, 0.9063, 0.8667, 0.9389, 0.9333, 0.9701, 1.0000, 1.0000 ] + * </code></pre> + * <p><img alt="sRGB tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p> + * + * @see CaptureRequest#TONEMAP_CURVE_RED + * @see CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS + * @see CaptureRequest#TONEMAP_MODE */ public static final Key<float[]> TONEMAP_CURVE_RED = new Key<float[]>("android.tonemap.curveRed", float[].class); /** + * <p>High-level global contrast/gamma/tonemapping control.</p> + * <p>When switching to an application-defined contrast curve by setting + * {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} to CONTRAST_CURVE, the curve is defined + * per-channel with a set of <code>(in, out)</code> points that specify the + * mapping from input high-bit-depth pixel value to the output + * low-bit-depth value. Since the actual pixel ranges of both input + * and output may change depending on the camera pipeline, the values + * are specified by normalized floating-point numbers.</p> + * <p>More-complex color mapping operations such as 3D color look-up + * tables, selective chroma enhancement, or other non-linear color + * transforms will be disabled when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is + * CONTRAST_CURVE.</p> + * <p>When using either FAST or HIGH_QUALITY, the camera device will + * emit its own tonemap curve in {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed}, + * {@link CaptureRequest#TONEMAP_CURVE_GREEN android.tonemap.curveGreen}, and {@link CaptureRequest#TONEMAP_CURVE_BLUE android.tonemap.curveBlue}. + * These values are always available, and as close as possible to the + * actually used nonlinear/nonglobal transforms.</p> + * <p>If a request is sent with TRANSFORM_MATRIX with the camera device's + * provided curve in FAST or HIGH_QUALITY, the image's tonemap will be + * roughly the same.</p> + * + * @see CaptureRequest#TONEMAP_CURVE_BLUE + * @see CaptureRequest#TONEMAP_CURVE_GREEN + * @see CaptureRequest#TONEMAP_CURVE_RED + * @see CaptureRequest#TONEMAP_MODE * @see #TONEMAP_MODE_CONTRAST_CURVE * @see #TONEMAP_MODE_FAST * @see #TONEMAP_MODE_HIGH_QUALITY @@ -971,53 +2080,95 @@ public final class CaptureResult extends CameraMetadata { new Key<Integer>("android.tonemap.mode", int.class); /** - * <p> - * This LED is nominally used to indicate to the user + * <p>This LED is nominally used to indicate to the user * that the camera is powered on and may be streaming images back to the * Application Processor. In certain rare circumstances, the OS may * disable this when video is processed locally and not transmitted to - * any untrusted applications. - * </p><p> - * In particular, the LED *must* always be on when the data could be - * transmitted off the device. The LED *should* always be on whenever - * data is stored locally on the device. - * </p><p> - * The LED *may* be off if a trusted application is using the data that - * doesn't violate the above rules. - * </p> - * + * any untrusted applications.</p> + * <p>In particular, the LED <em>must</em> always be on when the data could be + * transmitted off the device. The LED <em>should</em> always be on whenever + * data is stored locally on the device.</p> + * <p>The LED <em>may</em> be off if a trusted application is using the data that + * doesn't violate the above rules.</p> * @hide */ public static final Key<Boolean> LED_TRANSMIT = new Key<Boolean>("android.led.transmit", boolean.class); /** - * <p> - * Whether black-level compensation is locked - * to its current values, or is free to vary - * </p> - * <p> - * When set to ON, the values used for black-level - * compensation must not change until the lock is set to - * OFF - * </p><p> - * Since changes to certain capture parameters (such as - * exposure time) may require resetting of black level - * compensation, the HAL must report whether setting the - * black level lock was successful in the output result - * metadata. - * </p><p> - * The black level locking must happen at the sensor, and not at the ISP. - * If for some reason black level locking is no longer legal (for example, - * the analog gain has changed, which forces black levels to be - * recalculated), then the HAL is free to override this request (and it - * must report 'OFF' when this does happen) until the next time locking - * is legal again. - * </p> + * <p>Whether black-level compensation is locked + * to its current values, or is free to vary.</p> + * <p>Whether the black level offset was locked for this frame. Should be + * ON if {@link CaptureRequest#BLACK_LEVEL_LOCK android.blackLevel.lock} was ON in the capture request, unless + * a change in other capture settings forced the camera device to + * perform a black level reset.</p> + * + * @see CaptureRequest#BLACK_LEVEL_LOCK */ public static final Key<Boolean> BLACK_LEVEL_LOCK = new Key<Boolean>("android.blackLevel.lock", boolean.class); + /** + * <p>The frame number corresponding to the last request + * with which the output result (metadata + buffers) has been fully + * synchronized.</p> + * <p>When a request is submitted to the camera device, there is usually a + * delay of several frames before the controls get applied. A camera + * device may either choose to account for this delay by implementing a + * pipeline and carefully submit well-timed atomic control updates, or + * it may start streaming control changes that span over several frame + * boundaries.</p> + * <p>In the latter case, whenever a request's settings change relative to + * the previous submitted request, the full set of changes may take + * multiple frame durations to fully take effect. Some settings may + * take effect sooner (in less frame durations) than others.</p> + * <p>While a set of control changes are being propagated, this value + * will be CONVERGING.</p> + * <p>Once it is fully known that a set of control changes have been + * finished propagating, and the resulting updated control settings + * have been read back by the camera device, this value will be set + * to a non-negative frame number (corresponding to the request to + * which the results have synchronized to).</p> + * <p>Older camera device implementations may not have a way to detect + * when all camera controls have been applied, and will always set this + * value to UNKNOWN.</p> + * <p>FULL capability devices will always have this value set to the + * frame number of the request corresponding to this result.</p> + * <p><em>Further details</em>:</p> + * <ul> + * <li>Whenever a request differs from the last request, any future + * results not yet returned may have this value set to CONVERGING (this + * could include any in-progress captures not yet returned by the camera + * device, for more details see pipeline considerations below).</li> + * <li>Submitting a series of multiple requests that differ from the + * previous request (e.g. r1, r2, r3 s.t. r1 != r2 != r3) + * moves the new synchronization frame to the last non-repeating + * request (using the smallest frame number from the contiguous list of + * repeating requests).</li> + * <li>Submitting the same request repeatedly will not change this value + * to CONVERGING, if it was already a non-negative value.</li> + * <li>When this value changes to non-negative, that means that all of the + * metadata controls from the request have been applied, all of the + * metadata controls from the camera device have been read to the + * updated values (into the result), and all of the graphics buffers + * corresponding to this result are also synchronized to the request.</li> + * </ul> + * <p><em>Pipeline considerations</em>:</p> + * <p>Submitting a request with updated controls relative to the previously + * submitted requests may also invalidate the synchronization state + * of all the results corresponding to currently in-flight requests.</p> + * <p>In other words, results for this current request and up to + * {@link CameraCharacteristics#REQUEST_PIPELINE_MAX_DEPTH android.request.pipelineMaxDepth} prior requests may have their + * android.sync.frameNumber change to CONVERGING.</p> + * + * @see CameraCharacteristics#REQUEST_PIPELINE_MAX_DEPTH + * @see #SYNC_FRAME_NUMBER_CONVERGING + * @see #SYNC_FRAME_NUMBER_UNKNOWN + * @hide + */ + public static final Key<Integer> SYNC_FRAME_NUMBER = + new Key<Integer>("android.sync.frameNumber", int.class); + /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ * End generated code *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/ diff --git a/core/java/android/hardware/camera2/impl/CameraDevice.java b/core/java/android/hardware/camera2/impl/CameraDevice.java index 40586f0..2c8a5c2 100644 --- a/core/java/android/hardware/camera2/impl/CameraDevice.java +++ b/core/java/android/hardware/camera2/impl/CameraDevice.java @@ -106,9 +106,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { private final Runnable mCallOnClosed = new Runnable() { public void run() { - if (!CameraDevice.this.isClosed()) { - mDeviceListener.onClosed(CameraDevice.this); - } + mDeviceListener.onClosed(CameraDevice.this); } }; @@ -351,8 +349,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { } } - @Override - public void waitUntilIdle() throws CameraAccessException { + private void waitUntilIdle() throws CameraAccessException { synchronized (mLock) { checkIfCameraClosed(); diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java index 072c5bb..0d4a4cb 100644 --- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java +++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java @@ -105,6 +105,18 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable { } /** + * Set the global client-side vendor tag descriptor to allow use of vendor + * tags in camera applications. + * + * @return int A native status_t value corresponding to one of the + * {@link CameraBinderDecorator} integer constants. + * @see CameraBinderDecorator#throwOnError + * + * @hide + */ + public static native int nativeSetupGlobalVendorTagDescriptor(); + + /** * Set a camera metadata field to a value. The field definitions can be * found in {@link CameraCharacteristics}, {@link CaptureResult}, and * {@link CaptureRequest}. @@ -448,7 +460,7 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable { } else if (key.equals(CaptureResult.STATISTICS_FACES)) { return (T) getFaces(); } else if (key.equals(CaptureResult.STATISTICS_FACE_RECTANGLES)) { - return (T) fixFaceRectangles(); + return (T) getFaceRectangles(); } // For other keys, get() falls back to getBase() @@ -457,12 +469,15 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable { private int[] getAvailableFormats() { int[] availableFormats = getBase(CameraCharacteristics.SCALER_AVAILABLE_FORMATS); - for (int i = 0; i < availableFormats.length; i++) { - // JPEG has different value between native and managed side, need override. - if (availableFormats[i] == NATIVE_JPEG_FORMAT) { - availableFormats[i] = ImageFormat.JPEG; + if (availableFormats != null) { + for (int i = 0; i < availableFormats.length; i++) { + // JPEG has different value between native and managed side, need override. + if (availableFormats[i] == NATIVE_JPEG_FORMAT) { + availableFormats[i] = ImageFormat.JPEG; + } } } + return availableFormats; } @@ -550,7 +565,7 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable { // (left, top, width, height) at the native level, so the normal Rect // conversion that does (l, t, w, h) -> (l, t, r, b) is unnecessary. Undo // that conversion here for just the faces. - private Rect[] fixFaceRectangles() { + private Rect[] getFaceRectangles() { Rect[] faceRectangles = getBase(CaptureResult.STATISTICS_FACE_RECTANGLES); if (faceRectangles == null) return null; @@ -590,6 +605,8 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable { private <T> boolean setOverride(Key<T> key, T value) { if (key.equals(CameraCharacteristics.SCALER_AVAILABLE_FORMATS)) { return setAvailableFormats((int[]) value); + } else if (key.equals(CaptureResult.STATISTICS_FACE_RECTANGLES)) { + return setFaceRectangles((Rect[]) value); } // For other keys, set() falls back to setBase(). @@ -615,6 +632,36 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable { return true; } + /** + * Convert Face Rectangles from managed side to native side as they have different definitions. + * <p> + * Managed side face rectangles are defined as: left, top, width, height. + * Native side face rectangles are defined as: left, top, right, bottom. + * The input face rectangle need to be converted to native side definition when set is called. + * </p> + * + * @param faceRects Input face rectangles. + * @return true if face rectangles can be set successfully. Otherwise, Let the caller + * (setBase) to handle it appropriately. + */ + private boolean setFaceRectangles(Rect[] faceRects) { + if (faceRects == null) { + return false; + } + + Rect[] newFaceRects = new Rect[faceRects.length]; + for (int i = 0; i < newFaceRects.length; i++) { + newFaceRects[i] = new Rect( + faceRects[i].left, + faceRects[i].top, + faceRects[i].right + faceRects[i].left, + faceRects[i].bottom + faceRects[i].top); + } + + setBase(CaptureResult.STATISTICS_FACE_RECTANGLES, newFaceRects); + return true; + } + private long mMetadataPtr; // native CameraMetadata* private native long nativeAllocate(); diff --git a/core/java/android/hardware/camera2/package.html b/core/java/android/hardware/camera2/package.html index c619984..9f6c2a9 100644 --- a/core/java/android/hardware/camera2/package.html +++ b/core/java/android/hardware/camera2/package.html @@ -80,7 +80,5 @@ output streams included in the request. These are produced asynchronously relative to the output CaptureResult, sometimes substantially later.</p> -{@hide} - </BODY> </HTML> diff --git a/core/java/android/hardware/camera2/utils/CameraBinderDecorator.java b/core/java/android/hardware/camera2/utils/CameraBinderDecorator.java index e535e00..328ccbe 100644 --- a/core/java/android/hardware/camera2/utils/CameraBinderDecorator.java +++ b/core/java/android/hardware/camera2/utils/CameraBinderDecorator.java @@ -64,47 +64,7 @@ public class CameraBinderDecorator { // int return type => status_t => convert to exception if (m.getReturnType() == Integer.TYPE) { int returnValue = (Integer) result; - - switch (returnValue) { - case NO_ERROR: - return; - case PERMISSION_DENIED: - throw new SecurityException("Lacking privileges to access camera service"); - case ALREADY_EXISTS: - // This should be handled at the call site. Typically this isn't bad, - // just means we tried to do an operation that already completed. - return; - case BAD_VALUE: - throw new IllegalArgumentException("Bad argument passed to camera service"); - case DEAD_OBJECT: - UncheckedThrow.throwAnyException(new CameraRuntimeException( - CAMERA_DISCONNECTED)); - case EACCES: - UncheckedThrow.throwAnyException(new CameraRuntimeException( - CAMERA_DISABLED)); - case EBUSY: - UncheckedThrow.throwAnyException(new CameraRuntimeException( - CAMERA_IN_USE)); - case EUSERS: - UncheckedThrow.throwAnyException(new CameraRuntimeException( - MAX_CAMERAS_IN_USE)); - case ENODEV: - UncheckedThrow.throwAnyException(new CameraRuntimeException( - CAMERA_DISCONNECTED)); - case EOPNOTSUPP: - UncheckedThrow.throwAnyException(new CameraRuntimeException( - CAMERA_DEPRECATED_HAL)); - } - - /** - * Trap the rest of the negative return values. If we have known - * error codes i.e. ALREADY_EXISTS that aren't really runtime - * errors, then add them to the top switch statement - */ - if (returnValue < 0) { - throw new UnsupportedOperationException(String.format("Unknown error %d", - returnValue)); - } + throwOnError(returnValue); } } @@ -131,6 +91,54 @@ public class CameraBinderDecorator { } /** + * Throw error codes returned by the camera service as exceptions. + * + * @param errorFlag error to throw as an exception. + */ + public static void throwOnError(int errorFlag) { + switch (errorFlag) { + case NO_ERROR: + return; + case PERMISSION_DENIED: + throw new SecurityException("Lacking privileges to access camera service"); + case ALREADY_EXISTS: + // This should be handled at the call site. Typically this isn't bad, + // just means we tried to do an operation that already completed. + return; + case BAD_VALUE: + throw new IllegalArgumentException("Bad argument passed to camera service"); + case DEAD_OBJECT: + UncheckedThrow.throwAnyException(new CameraRuntimeException( + CAMERA_DISCONNECTED)); + case EACCES: + UncheckedThrow.throwAnyException(new CameraRuntimeException( + CAMERA_DISABLED)); + case EBUSY: + UncheckedThrow.throwAnyException(new CameraRuntimeException( + CAMERA_IN_USE)); + case EUSERS: + UncheckedThrow.throwAnyException(new CameraRuntimeException( + MAX_CAMERAS_IN_USE)); + case ENODEV: + UncheckedThrow.throwAnyException(new CameraRuntimeException( + CAMERA_DISCONNECTED)); + case EOPNOTSUPP: + UncheckedThrow.throwAnyException(new CameraRuntimeException( + CAMERA_DEPRECATED_HAL)); + } + + /** + * Trap the rest of the negative return values. If we have known + * error codes i.e. ALREADY_EXISTS that aren't really runtime + * errors, then add them to the top switch statement + */ + if (errorFlag < 0) { + throw new UnsupportedOperationException(String.format("Unknown error %d", + errorFlag)); + } + } + + /** * <p> * Wraps the type T with a proxy that will check 'status_t' return codes * from the native side of the camera service, and throw Java exceptions diff --git a/core/java/android/hardware/display/WifiDisplayStatus.java b/core/java/android/hardware/display/WifiDisplayStatus.java index 5216727..b645662 100644 --- a/core/java/android/hardware/display/WifiDisplayStatus.java +++ b/core/java/android/hardware/display/WifiDisplayStatus.java @@ -20,8 +20,6 @@ import android.os.Parcel; import android.os.Parcelable; import java.util.Arrays; -import java.util.ArrayList; -import java.util.List; /** * Describes the current global state of Wifi display connectivity, including the diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl index f1e7e98..465d142 100644 --- a/core/java/android/hardware/input/IInputManager.aidl +++ b/core/java/android/hardware/input/IInputManager.aidl @@ -19,6 +19,7 @@ package android.hardware.input; import android.hardware.input.InputDeviceIdentifier; import android.hardware.input.KeyboardLayout; import android.hardware.input.IInputDevicesChangedListener; +import android.hardware.input.TouchCalibration; import android.os.IBinder; import android.view.InputDevice; import android.view.InputEvent; @@ -39,6 +40,11 @@ interface IInputManager { // applications, the caller must have the INJECT_EVENTS permission. boolean injectInputEvent(in InputEvent ev, int mode); + // Calibrate input device position + TouchCalibration getTouchCalibrationForInputDevice(String inputDeviceDescriptor, int rotation); + void setTouchCalibrationForInputDevice(String inputDeviceDescriptor, int rotation, + in TouchCalibration calibration); + // Keyboard layouts configuration. KeyboardLayout[] getKeyboardLayouts(); KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor); diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index a2aeafb..e3a3830 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -500,6 +500,45 @@ public final class InputManager { } /** + * Gets the TouchCalibration applied to the specified input device's coordinates. + * + * @param inputDeviceDescriptor The input device descriptor. + * @return The TouchCalibration currently assigned for use with the given + * input device. If none is set, an identity TouchCalibration is returned. + * + * @hide + */ + public TouchCalibration getTouchCalibration(String inputDeviceDescriptor, int surfaceRotation) { + try { + return mIm.getTouchCalibrationForInputDevice(inputDeviceDescriptor, surfaceRotation); + } catch (RemoteException ex) { + Log.w(TAG, "Could not get calibration matrix for input device.", ex); + return TouchCalibration.IDENTITY; + } + } + + /** + * Sets the TouchCalibration to apply to the specified input device's coordinates. + * <p> + * This method may have the side-effect of causing the input device in question + * to be reconfigured. Requires {@link android.Manifest.permissions.SET_INPUT_CALIBRATION}. + * </p> + * + * @param inputDeviceDescriptor The input device descriptor. + * @param calibration The calibration to be applied + * + * @hide + */ + public void setTouchCalibration(String inputDeviceDescriptor, int surfaceRotation, + TouchCalibration calibration) { + try { + mIm.setTouchCalibrationForInputDevice(inputDeviceDescriptor, surfaceRotation, calibration); + } catch (RemoteException ex) { + Log.w(TAG, "Could not set calibration matrix for input device.", ex); + } + } + + /** * Gets the mouse pointer speed. * <p> * Only returns the permanent mouse pointer speed. Ignores any temporary pointer diff --git a/core/java/android/hardware/input/TouchCalibration.aidl b/core/java/android/hardware/input/TouchCalibration.aidl new file mode 100644 index 0000000..2c28774 --- /dev/null +++ b/core/java/android/hardware/input/TouchCalibration.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.input; + +parcelable TouchCalibration; diff --git a/core/java/android/hardware/input/TouchCalibration.java b/core/java/android/hardware/input/TouchCalibration.java new file mode 100644 index 0000000..025fad0 --- /dev/null +++ b/core/java/android/hardware/input/TouchCalibration.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.input; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Encapsulates calibration data for input devices. + * + * @hide + */ +public class TouchCalibration implements Parcelable { + + public static final TouchCalibration IDENTITY = new TouchCalibration(); + + public static final Parcelable.Creator<TouchCalibration> CREATOR + = new Parcelable.Creator<TouchCalibration>() { + public TouchCalibration createFromParcel(Parcel in) { + return new TouchCalibration(in); + } + + public TouchCalibration[] newArray(int size) { + return new TouchCalibration[size]; + } + }; + + private final float mXScale, mXYMix, mXOffset; + private final float mYXMix, mYScale, mYOffset; + + /** + * Create a new TouchCalibration initialized to the identity transformation. + */ + public TouchCalibration() { + this(1,0,0,0,1,0); + } + + /** + * Create a new TouchCalibration from affine transformation paramters. + * @param xScale Influence of input x-axis value on output x-axis value. + * @param xyMix Influence of input y-axis value on output x-axis value. + * @param xOffset Constant offset to be applied to output x-axis value. + * @param yXMix Influence of input x-axis value on output y-axis value. + * @param yScale Influence of input y-axis value on output y-axis value. + * @param yOffset Constant offset to be applied to output y-axis value. + */ + public TouchCalibration(float xScale, float xyMix, float xOffset, + float yxMix, float yScale, float yOffset) { + mXScale = xScale; + mXYMix = xyMix; + mXOffset = xOffset; + mYXMix = yxMix; + mYScale = yScale; + mYOffset = yOffset; + } + + public TouchCalibration(Parcel in) { + mXScale = in.readFloat(); + mXYMix = in.readFloat(); + mXOffset = in.readFloat(); + mYXMix = in.readFloat(); + mYScale = in.readFloat(); + mYOffset = in.readFloat(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeFloat(mXScale); + dest.writeFloat(mXYMix); + dest.writeFloat(mXOffset); + dest.writeFloat(mYXMix); + dest.writeFloat(mYScale); + dest.writeFloat(mYOffset); + } + + @Override + public int describeContents() { + return 0; + } + + public float[] getAffineTransform() { + return new float[] { mXScale, mXYMix, mXOffset, mYXMix, mYScale, mYOffset }; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } else if (obj instanceof TouchCalibration) { + TouchCalibration cal = (TouchCalibration)obj; + + return (cal.mXScale == mXScale) && + (cal.mXYMix == mXYMix) && + (cal.mXOffset == mXOffset) && + (cal.mYXMix == mYXMix) && + (cal.mYScale == mYScale) && + (cal.mYOffset == mYOffset); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Float.floatToIntBits(mXScale) ^ + Float.floatToIntBits(mXYMix) ^ + Float.floatToIntBits(mXOffset) ^ + Float.floatToIntBits(mYXMix) ^ + Float.floatToIntBits(mYScale) ^ + Float.floatToIntBits(mYOffset); + } +} diff --git a/core/java/android/hardware/location/GeofenceHardwareRequest.java b/core/java/android/hardware/location/GeofenceHardwareRequest.java index 6e7b592..796d7f8 100644 --- a/core/java/android/hardware/location/GeofenceHardwareRequest.java +++ b/core/java/android/hardware/location/GeofenceHardwareRequest.java @@ -16,8 +16,6 @@ package android.hardware.location; -import android.location.Location; - /** * This class represents the characteristics of the geofence. * diff --git a/core/java/android/hardware/usb/UsbAccessory.java b/core/java/android/hardware/usb/UsbAccessory.java index 5719452..2f9178c 100644 --- a/core/java/android/hardware/usb/UsbAccessory.java +++ b/core/java/android/hardware/usb/UsbAccessory.java @@ -16,10 +16,8 @@ package android.hardware.usb; -import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; -import android.util.Log; /** * A class representing a USB accessory, which is an external hardware component diff --git a/core/java/android/hardware/usb/UsbConfiguration.java b/core/java/android/hardware/usb/UsbConfiguration.java new file mode 100644 index 0000000..92d6f75 --- /dev/null +++ b/core/java/android/hardware/usb/UsbConfiguration.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.usb; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A class representing a configuration on a {@link UsbDevice}. + * A USB configuration can have one or more interfaces, each one providing a different + * piece of functionality, separate from the other interfaces. + * An interface will have one or more {@link UsbEndpoint}s, which are the + * channels by which the host transfers data with the device. + * + * <div class="special reference"> + * <h3>Developer Guides</h3> + * <p>For more information about communicating with USB hardware, read the + * <a href="{@docRoot}guide/topics/usb/index.html">USB</a> developer guide.</p> + * </div> + */ +public class UsbConfiguration implements Parcelable { + + private final int mId; + private final String mName; + private final int mAttributes; + private final int mMaxPower; + private Parcelable[] mInterfaces; + + /** + * Mask for "self-powered" bit in the configuration's attributes. + * @see #getAttributes + */ + public static final int ATTR_SELF_POWERED_MASK = 1 << 6; + + /** + * Mask for "remote wakeup" bit in the configuration's attributes. + * @see #getAttributes + */ + public static final int ATTR_REMOTE_WAKEUP_MASK = 1 << 5; + + /** + * UsbConfiguration should only be instantiated by UsbService implementation + * @hide + */ + public UsbConfiguration(int id, String name, int attributes, int maxPower) { + mId = id; + mName = name; + mAttributes = attributes; + mMaxPower = maxPower; + } + + /** + * Returns the configuration's ID field. + * This is an integer that uniquely identifies the configuration on the device. + * + * @return the configuration's ID + */ + public int getId() { + return mId; + } + + /** + * Returns the configuration's name. + * + * @return the configuration's name + */ + public String getName() { + return mName; + } + + /** + * Returns the configuration's attributes field. + * This field contains a bit field with the following flags: + * + * Bit 7: always set to 1 + * Bit 6: self-powered + * Bit 5: remote wakeup enabled + * Bit 0-4: reserved + * @see #ATTR_SELF_POWERED_MASK + * @see #ATTR_REMOTE_WAKEUP_MASK + * @return the configuration's attributes + */ + public int getAttributes() { + return mAttributes; + } + + /** + * Returns the configuration's max power consumption, in milliamps. + * + * @return the configuration's max power + */ + public int getMaxPower() { + return mMaxPower * 2; + } + + /** + * Returns the number of {@link UsbInterface}s this configuration contains. + * + * @return the number of endpoints + */ + public int getInterfaceCount() { + return mInterfaces.length; + } + + /** + * Returns the {@link UsbInterface} at the given index. + * + * @return the interface + */ + public UsbInterface getInterface(int index) { + return (UsbInterface)mInterfaces[index]; + } + + /** + * Only used by UsbService implementation + * @hide + */ + public void setInterfaces(Parcelable[] interfaces) { + mInterfaces = interfaces; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("UsbConfiguration[mId=" + mId + + ",mName=" + mName + ",mAttributes=" + mAttributes + + ",mMaxPower=" + mMaxPower + ",mInterfaces=["); + for (int i = 0; i < mInterfaces.length; i++) { + builder.append("\n"); + builder.append(mInterfaces[i].toString()); + } + builder.append("]"); + return builder.toString(); + } + + public static final Parcelable.Creator<UsbConfiguration> CREATOR = + new Parcelable.Creator<UsbConfiguration>() { + public UsbConfiguration createFromParcel(Parcel in) { + int id = in.readInt(); + String name = in.readString(); + int attributes = in.readInt(); + int maxPower = in.readInt(); + Parcelable[] interfaces = in.readParcelableArray(UsbInterface.class.getClassLoader()); + UsbConfiguration configuration = new UsbConfiguration(id, name, attributes, maxPower); + configuration.setInterfaces(interfaces); + return configuration; + } + + public UsbConfiguration[] newArray(int size) { + return new UsbConfiguration[size]; + } + }; + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(mId); + parcel.writeString(mName); + parcel.writeInt(mAttributes); + parcel.writeInt(mMaxPower); + parcel.writeParcelableArray(mInterfaces, 0); + } +} diff --git a/core/java/android/hardware/usb/UsbDevice.java b/core/java/android/hardware/usb/UsbDevice.java index d1e63f6..d90e06e 100644 --- a/core/java/android/hardware/usb/UsbDevice.java +++ b/core/java/android/hardware/usb/UsbDevice.java @@ -16,12 +16,8 @@ package android.hardware.usb; -import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; -import android.util.Log; - -import java.io.FileDescriptor; /** * This class represents a USB device attached to the android device with the android device @@ -54,7 +50,10 @@ public class UsbDevice implements Parcelable { private final int mClass; private final int mSubclass; private final int mProtocol; - private final Parcelable[] mInterfaces; + private Parcelable[] mConfigurations; + + // list of all interfaces on the device + private UsbInterface[] mInterfaces; /** * UsbDevice should only be instantiated by UsbService implementation @@ -62,8 +61,7 @@ public class UsbDevice implements Parcelable { */ public UsbDevice(String name, int vendorId, int productId, int Class, int subClass, int protocol, - String manufacturerName, String productName, String serialNumber, - Parcelable[] interfaces) { + String manufacturerName, String productName, String serialNumber) { mName = name; mVendorId = vendorId; mProductId = productId; @@ -73,7 +71,6 @@ public class UsbDevice implements Parcelable { mManufacturerName = manufacturerName; mProductName = productName; mSerialNumber = serialNumber; - mInterfaces = interfaces; } /** @@ -173,21 +170,74 @@ public class UsbDevice implements Parcelable { } /** + * Returns the number of {@link UsbConfiguration}s this device contains. + * + * @return the number of configurations + */ + public int getConfigurationCount() { + return mConfigurations.length; + } + + /** + * Returns the {@link UsbConfiguration} at the given index. + * + * @return the configuration + */ + public UsbConfiguration getConfiguration(int index) { + return (UsbConfiguration)mConfigurations[index]; + } + + private UsbInterface[] getInterfaceList() { + if (mInterfaces == null) { + int configurationCount = mConfigurations.length; + int interfaceCount = 0; + for (int i = 0; i < configurationCount; i++) { + UsbConfiguration configuration = (UsbConfiguration)mConfigurations[i]; + interfaceCount += configuration.getInterfaceCount(); + } + + mInterfaces = new UsbInterface[interfaceCount]; + int offset = 0; + for (int i = 0; i < configurationCount; i++) { + UsbConfiguration configuration = (UsbConfiguration)mConfigurations[i]; + interfaceCount = configuration.getInterfaceCount(); + for (int j = 0; j < interfaceCount; j++) { + mInterfaces[offset++] = configuration.getInterface(j); + } + } + } + + return mInterfaces; + } + + /** * Returns the number of {@link UsbInterface}s this device contains. + * For devices with multiple configurations, you will probably want to use + * {@link UsbConfiguration#getInterfaceCount} instead. * * @return the number of interfaces */ public int getInterfaceCount() { - return mInterfaces.length; + return getInterfaceList().length; } /** * Returns the {@link UsbInterface} at the given index. + * For devices with multiple configurations, you will probably want to use + * {@link UsbConfiguration#getInterface} instead. * * @return the interface */ public UsbInterface getInterface(int index) { - return (UsbInterface)mInterfaces[index]; + return getInterfaceList()[index]; + } + + /** + * Only used by UsbService implementation + * @hide + */ + public void setConfigurations(Parcelable[] configuration) { + mConfigurations = configuration; } @Override @@ -208,11 +258,17 @@ public class UsbDevice implements Parcelable { @Override public String toString() { - return "UsbDevice[mName=" + mName + ",mVendorId=" + mVendorId + - ",mProductId=" + mProductId + ",mClass=" + mClass + - ",mSubclass=" + mSubclass + ",mProtocol=" + mProtocol + + StringBuilder builder = new StringBuilder("UsbDevice[mName=" + mName + + ",mVendorId=" + mVendorId + ",mProductId=" + mProductId + + ",mClass=" + mClass + ",mSubclass=" + mSubclass + ",mProtocol=" + mProtocol + ",mManufacturerName=" + mManufacturerName + ",mProductName=" + mProductName + - ",mSerialNumber=" + mSerialNumber + ",mInterfaces=" + mInterfaces + "]"; + ",mSerialNumber=" + mSerialNumber + ",mConfigurations=["); + for (int i = 0; i < mConfigurations.length; i++) { + builder.append("\n"); + builder.append(mConfigurations[i].toString()); + } + builder.append("]"); + return builder.toString(); } public static final Parcelable.Creator<UsbDevice> CREATOR = @@ -227,9 +283,11 @@ public class UsbDevice implements Parcelable { String manufacturerName = in.readString(); String productName = in.readString(); String serialNumber = in.readString(); - Parcelable[] interfaces = in.readParcelableArray(UsbInterface.class.getClassLoader()); - return new UsbDevice(name, vendorId, productId, clasz, subClass, protocol, - manufacturerName, productName, serialNumber, interfaces); + Parcelable[] configurations = in.readParcelableArray(UsbInterface.class.getClassLoader()); + UsbDevice device = new UsbDevice(name, vendorId, productId, clasz, subClass, protocol, + manufacturerName, productName, serialNumber); + device.setConfigurations(configurations); + return device; } public UsbDevice[] newArray(int size) { @@ -251,7 +309,7 @@ public class UsbDevice implements Parcelable { parcel.writeString(mManufacturerName); parcel.writeString(mProductName); parcel.writeString(mSerialNumber); - parcel.writeParcelableArray(mInterfaces, 0); + parcel.writeParcelableArray(mConfigurations, 0); } public static int getDeviceId(String name) { diff --git a/core/java/android/hardware/usb/UsbDeviceConnection.java b/core/java/android/hardware/usb/UsbDeviceConnection.java index 389475f..6283951 100644 --- a/core/java/android/hardware/usb/UsbDeviceConnection.java +++ b/core/java/android/hardware/usb/UsbDeviceConnection.java @@ -101,6 +101,25 @@ public class UsbDeviceConnection { } /** + * Sets the current {@link android.hardware.usb.UsbInterface}. + * Used to select between two interfaces with the same ID but different alternate setting. + * + * @return true if the interface was successfully released + */ + public boolean setInterface(UsbInterface intf) { + return native_set_interface(intf.getId(), intf.getAlternateSetting()); + } + + /** + * Sets the device's current {@link android.hardware.usb.UsbConfiguration}. + * + * @return true if the configuration was successfully set + */ + public boolean setConfiguration(UsbConfiguration configuration) { + return native_set_configuration(configuration.getId()); + } + + /** * Performs a control transaction on endpoint zero for this device. * The direction of the transfer is determined by the request type. * If requestType & {@link UsbConstants#USB_ENDPOINT_DIR_MASK} is @@ -236,6 +255,8 @@ public class UsbDeviceConnection { private native byte[] native_get_desc(); private native boolean native_claim_interface(int interfaceID, boolean force); private native boolean native_release_interface(int interfaceID); + private native boolean native_set_interface(int interfaceID, int alternateSetting); + private native boolean native_set_configuration(int configurationID); private native int native_control_request(int requestType, int request, int value, int index, byte[] buffer, int offset, int length, int timeout); private native int native_bulk_request(int endpoint, byte[] buffer, diff --git a/core/java/android/hardware/usb/UsbEndpoint.java b/core/java/android/hardware/usb/UsbEndpoint.java index 753a447..708d651 100644 --- a/core/java/android/hardware/usb/UsbEndpoint.java +++ b/core/java/android/hardware/usb/UsbEndpoint.java @@ -16,7 +16,6 @@ package android.hardware.usb; -import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; diff --git a/core/java/android/hardware/usb/UsbInterface.java b/core/java/android/hardware/usb/UsbInterface.java index d6c54a8..de01a88 100644 --- a/core/java/android/hardware/usb/UsbInterface.java +++ b/core/java/android/hardware/usb/UsbInterface.java @@ -16,7 +16,6 @@ package android.hardware.usb; -import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -36,27 +35,31 @@ import android.os.Parcelable; public class UsbInterface implements Parcelable { private final int mId; + private final int mAlternateSetting; + private final String mName; private final int mClass; private final int mSubclass; private final int mProtocol; - private final Parcelable[] mEndpoints; + private Parcelable[] mEndpoints; /** * UsbInterface should only be instantiated by UsbService implementation * @hide */ - public UsbInterface(int id, int Class, int subClass, int protocol, - Parcelable[] endpoints) { + public UsbInterface(int id, int alternateSetting, String name, + int Class, int subClass, int protocol) { mId = id; + mAlternateSetting = alternateSetting; + mName = name; mClass = Class; mSubclass = subClass; mProtocol = protocol; - mEndpoints = endpoints; } /** - * Returns the interface's ID field. - * This is an integer that uniquely identifies the interface on the device. + * Returns the interface's bInterfaceNumber field. + * This is an integer that along with the alternate setting uniquely identifies + * the interface on the device. * * @return the interface's ID */ @@ -65,6 +68,28 @@ public class UsbInterface implements Parcelable { } /** + * Returns the interface's bAlternateSetting field. + * This is an integer that along with the ID uniquely identifies + * the interface on the device. + * {@link UsbDeviceConnection#setInterface} can be used to switch between + * two interfaces with the same ID but different alternate setting. + * + * @return the interface's alternate setting + */ + public int getAlternateSetting() { + return mAlternateSetting; + } + + /** + * Returns the interface's name. + * + * @return the interface's name + */ + public String getName() { + return mName; + } + + /** * Returns the interface's class field. * Some useful constants for USB classes can be found in {@link UsbConstants} * @@ -110,22 +135,42 @@ public class UsbInterface implements Parcelable { return (UsbEndpoint)mEndpoints[index]; } + /** + * Only used by UsbService implementation + * @hide + */ + public void setEndpoints(Parcelable[] endpoints) { + mEndpoints = endpoints; + } + @Override public String toString() { - return "UsbInterface[mId=" + mId + ",mClass=" + mClass + + StringBuilder builder = new StringBuilder("UsbInterface[mId=" + mId + + ",mAlternateSetting=" + mAlternateSetting + + ",mName=" + mName + ",mClass=" + mClass + ",mSubclass=" + mSubclass + ",mProtocol=" + mProtocol + - ",mEndpoints=" + mEndpoints + "]"; + ",mEndpoints=["); + for (int i = 0; i < mEndpoints.length; i++) { + builder.append("\n"); + builder.append(mEndpoints[i].toString()); + } + builder.append("]"); + return builder.toString(); } public static final Parcelable.Creator<UsbInterface> CREATOR = new Parcelable.Creator<UsbInterface>() { public UsbInterface createFromParcel(Parcel in) { int id = in.readInt(); + int alternateSetting = in.readInt(); + String name = in.readString(); int Class = in.readInt(); int subClass = in.readInt(); int protocol = in.readInt(); Parcelable[] endpoints = in.readParcelableArray(UsbEndpoint.class.getClassLoader()); - return new UsbInterface(id, Class, subClass, protocol, endpoints); + UsbInterface intf = new UsbInterface(id, alternateSetting, name, Class, subClass, protocol); + intf.setEndpoints(endpoints); + return intf; } public UsbInterface[] newArray(int size) { @@ -139,6 +184,8 @@ public class UsbInterface implements Parcelable { public void writeToParcel(Parcel parcel, int flags) { parcel.writeInt(mId); + parcel.writeInt(mAlternateSetting); + parcel.writeString(mName); parcel.writeInt(mClass); parcel.writeInt(mSubclass); parcel.writeInt(mProtocol); diff --git a/core/java/android/inputmethodservice/ExtractButton.java b/core/java/android/inputmethodservice/ExtractButton.java index b6b7595..fe63c1e 100644 --- a/core/java/android/inputmethodservice/ExtractButton.java +++ b/core/java/android/inputmethodservice/ExtractButton.java @@ -32,8 +32,12 @@ class ExtractButton extends Button { super(context, attrs, com.android.internal.R.attr.buttonStyle); } - public ExtractButton(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public ExtractButton(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public ExtractButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); } /** diff --git a/core/java/android/inputmethodservice/ExtractEditText.java b/core/java/android/inputmethodservice/ExtractEditText.java index 23ae21b..48b604c 100644 --- a/core/java/android/inputmethodservice/ExtractEditText.java +++ b/core/java/android/inputmethodservice/ExtractEditText.java @@ -38,8 +38,12 @@ public class ExtractEditText extends EditText { super(context, attrs, com.android.internal.R.attr.editTextStyle); } - public ExtractEditText(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public ExtractEditText(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public ExtractEditText(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); } void setIME(InputMethodService ime) { diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java index bbea8ff..8437228 100644 --- a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java @@ -25,7 +25,6 @@ import android.graphics.Rect; import android.os.Bundle; import android.os.Looper; import android.os.Message; -import android.os.RemoteException; import android.util.Log; import android.util.SparseArray; import android.view.InputChannel; diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 1b7d9ea..81ad28b 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -2322,6 +2322,21 @@ public class InputMethodService extends AbstractInputMethodService { } /** + * @return The recommended height of the input method window. + * An IME author can get the last input method's height as the recommended height + * by calling this in + * {@link android.inputmethodservice.InputMethodService#onStartInputView(EditorInfo, boolean)}. + * If you don't need to use a predefined fixed height, you can avoid the window-resizing of IME + * switching by using this value as a visible inset height. It's efficient for the smooth + * transition between different IMEs. However, note that this may return 0 (or possibly + * unexpectedly low height). You should thus avoid relying on the return value of this method + * all the time. Please make sure to use a reasonable height for the IME. + */ + public int getInputMethodWindowRecommendedHeight() { + return mImm.getInputMethodWindowVisibleHeight(); + } + + /** * Performs a dump of the InputMethodService's internal state. Override * to add your own information to the dump. */ diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java index 4916244..af75a0a 100644 --- a/core/java/android/inputmethodservice/KeyboardView.java +++ b/core/java/android/inputmethodservice/KeyboardView.java @@ -279,12 +279,15 @@ public class KeyboardView extends View implements View.OnClickListener { this(context, attrs, com.android.internal.R.attr.keyboardViewStyle); } - public KeyboardView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public KeyboardView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public KeyboardView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); - TypedArray a = - context.obtainStyledAttributes( - attrs, android.R.styleable.KeyboardView, defStyle, 0); + TypedArray a = context.obtainStyledAttributes( + attrs, android.R.styleable.KeyboardView, defStyleAttr, defStyleRes); LayoutInflater inflate = (LayoutInflater) context diff --git a/core/java/android/net/BaseNetworkStateTracker.java b/core/java/android/net/BaseNetworkStateTracker.java index 476fefe..804f8ee 100644 --- a/core/java/android/net/BaseNetworkStateTracker.java +++ b/core/java/android/net/BaseNetworkStateTracker.java @@ -20,10 +20,10 @@ import android.content.Context; import android.os.Handler; import android.os.Messenger; -import com.android.internal.util.Preconditions; - import java.util.concurrent.atomic.AtomicBoolean; +import com.android.internal.util.Preconditions; + /** * Interface to control and observe state of a specific network, hiding * network-specific details from {@link ConnectivityManager}. Surfaces events @@ -108,11 +108,6 @@ public abstract class BaseNetworkStateTracker implements NetworkStateTracker { } @Override - public void captivePortalCheckComplete() { - // not implemented - } - - @Override public void captivePortalCheckCompleted(boolean isCaptivePortal) { // not implemented } diff --git a/core/java/android/net/CaptivePortalTracker.java b/core/java/android/net/CaptivePortalTracker.java index d678f1e..89c17c7 100644 --- a/core/java/android/net/CaptivePortalTracker.java +++ b/core/java/android/net/CaptivePortalTracker.java @@ -48,7 +48,6 @@ import java.io.IOException; import java.net.HttpURLConnection; import java.net.InetAddress; import java.net.Inet4Address; -import java.net.SocketTimeoutException; import java.net.URL; import java.net.UnknownHostException; import java.util.List; @@ -84,13 +83,12 @@ public class CaptivePortalTracker extends StateMachine { private String mServer; private String mUrl; private boolean mIsCaptivePortalCheckEnabled = false; - private IConnectivityManager mConnService; - private TelephonyManager mTelephonyManager; - private WifiManager mWifiManager; - private Context mContext; + private final IConnectivityManager mConnService; + private final TelephonyManager mTelephonyManager; + private final WifiManager mWifiManager; + private final Context mContext; private NetworkInfo mNetworkInfo; - private static final int CMD_DETECT_PORTAL = 0; private static final int CMD_CONNECTIVITY_CHANGE = 1; private static final int CMD_DELAYED_CAPTIVE_CHECK = 2; @@ -98,14 +96,15 @@ public class CaptivePortalTracker extends StateMachine { private static final int DELAYED_CHECK_INTERVAL_MS = 10000; private int mDelayedCheckToken = 0; - private State mDefaultState = new DefaultState(); - private State mNoActiveNetworkState = new NoActiveNetworkState(); - private State mActiveNetworkState = new ActiveNetworkState(); - private State mDelayedCaptiveCheckState = new DelayedCaptiveCheckState(); + private final State mDefaultState = new DefaultState(); + private final State mNoActiveNetworkState = new NoActiveNetworkState(); + private final State mActiveNetworkState = new ActiveNetworkState(); + private final State mDelayedCaptiveCheckState = new DelayedCaptiveCheckState(); private static final String SETUP_WIZARD_PACKAGE = "com.google.android.setupwizard"; private boolean mDeviceProvisioned = false; - private ProvisioningObserver mProvisioningObserver; + @SuppressWarnings("unused") + private final ProvisioningObserver mProvisioningObserver; private CaptivePortalTracker(Context context, IConnectivityManager cs) { super(TAG); @@ -174,29 +173,11 @@ public class CaptivePortalTracker extends StateMachine { return captivePortal; } - public void detectCaptivePortal(NetworkInfo info) { - sendMessage(obtainMessage(CMD_DETECT_PORTAL, info)); - } - private class DefaultState extends State { @Override public boolean processMessage(Message message) { - if (DBG) log(getName() + message.toString()); - switch (message.what) { - case CMD_DETECT_PORTAL: - NetworkInfo info = (NetworkInfo) message.obj; - // Checking on a secondary connection is not supported - // yet - notifyPortalCheckComplete(info); - break; - case CMD_CONNECTIVITY_CHANGE: - case CMD_DELAYED_CAPTIVE_CHECK: - break; - default: - loge("Ignoring " + message); - break; - } + loge("Ignoring " + message); return HANDLED; } } @@ -316,19 +297,6 @@ public class CaptivePortalTracker extends StateMachine { } } - private void notifyPortalCheckComplete(NetworkInfo info) { - if (info == null) { - loge("notifyPortalCheckComplete on null"); - return; - } - try { - if (DBG) log("notifyPortalCheckComplete: ni=" + info); - mConnService.captivePortalCheckComplete(info); - } catch(RemoteException e) { - e.printStackTrace(); - } - } - private void notifyPortalCheckCompleted(NetworkInfo info, boolean isCaptivePortal) { if (info == null) { loge("notifyPortalCheckComplete on null"); @@ -453,6 +421,13 @@ public class CaptivePortalTracker extends StateMachine { case ConnectivityManager.TYPE_WIFI: WifiInfo currentWifiInfo = mWifiManager.getConnectionInfo(); if (currentWifiInfo != null) { + // NOTE: getSSID()'s behavior changed in API 17; before that, SSIDs were not + // surrounded by double quotation marks (thus violating the Javadoc), but this + // was changed to match the Javadoc in API 17. Since clients may have started + // sanitizing the output of this method since API 17 was released, we should + // not change it here as it would become impossible to tell whether the SSID is + // simply being surrounded by quotes due to the API, or whether those quotes + // are actually part of the SSID. latencyBroadcast.putExtra(EXTRA_SSID, currentWifiInfo.getSSID()); latencyBroadcast.putExtra(EXTRA_BSSID, currentWifiInfo.getBSSID()); } else { @@ -464,7 +439,6 @@ public class CaptivePortalTracker extends StateMachine { latencyBroadcast.putExtra(EXTRA_NETWORK_TYPE, mTelephonyManager.getNetworkType()); List<CellInfo> info = mTelephonyManager.getAllCellInfo(); if (info == null) return; - StringBuffer uniqueCellId = new StringBuffer(); int numRegisteredCellInfo = 0; for (CellInfo cellInfo : info) { if (cellInfo.isRegistered()) { diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 4eecfa9..5b2a29e 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -23,10 +23,14 @@ import android.annotation.SdkConstant.SdkConstantType; import android.content.Context; import android.os.Binder; import android.os.Build.VERSION_CODES; +import android.os.IBinder; +import android.os.INetworkActivityListener; +import android.os.INetworkManagementService; import android.os.Messenger; import android.os.RemoteException; -import android.os.ResultReceiver; +import android.os.ServiceManager; import android.provider.Settings; +import android.util.ArrayMap; import java.net.InetAddress; @@ -77,7 +81,7 @@ public class ConnectivityManager { /** * Identical to {@link #CONNECTIVITY_ACTION} broadcast, but sent without any - * applicable {@link Settings.Secure#CONNECTIVITY_CHANGE_DELAY}. + * applicable {@link Settings.Global#CONNECTIVITY_CHANGE_DELAY}. * * @hide */ @@ -403,6 +407,8 @@ public class ConnectivityManager { private final String mPackageName; + private INetworkManagementService mNMService; + /** * Tests if a given integer represents a valid network type. * @param networkType the type to be tested @@ -803,6 +809,8 @@ public class ConnectivityManager { * Ensure that a network route exists to deliver traffic to the specified * host via the specified network interface. An attempt to add a route that * already exists is ignored, but treated as successful. + * <p>This method requires the caller to hold the permission + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}. * @param networkType the type of the network over which traffic to the specified * host is to be routed * @param hostAddress the IP address of the host to which the route is desired @@ -906,6 +914,92 @@ public class ConnectivityManager { } /** + * Callback for use with {@link ConnectivityManager#registerNetworkActiveListener} to + * find out when the current network has gone in to a high power state. + */ + public interface OnNetworkActiveListener { + /** + * Called on the main thread of the process to report that the current data network + * has become active, and it is now a good time to perform any pending network + * operations. Note that this listener only tells you when the network becomes + * active; if at any other time you want to know whether it is active (and thus okay + * to initiate network traffic), you can retrieve its instantaneous state with + * {@link ConnectivityManager#isNetworkActive}. + */ + public void onNetworkActive(); + } + + private INetworkManagementService getNetworkManagementService() { + synchronized (this) { + if (mNMService != null) { + return mNMService; + } + IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); + mNMService = INetworkManagementService.Stub.asInterface(b); + return mNMService; + } + } + + private final ArrayMap<OnNetworkActiveListener, INetworkActivityListener> + mNetworkActivityListeners + = new ArrayMap<OnNetworkActiveListener, INetworkActivityListener>(); + + /** + * Start listening to reports when the data network is active, meaning it is + * a good time to perform network traffic. Use {@link #isNetworkActive()} + * to determine the current state of the network after registering the listener. + * + * @param l The listener to be told when the network is active. + */ + public void registerNetworkActiveListener(final OnNetworkActiveListener l) { + INetworkActivityListener rl = new INetworkActivityListener.Stub() { + @Override + public void onNetworkActive() throws RemoteException { + l.onNetworkActive(); + } + }; + + try { + getNetworkManagementService().registerNetworkActivityListener(rl); + mNetworkActivityListeners.put(l, rl); + } catch (RemoteException e) { + } + } + + /** + * Remove network active listener previously registered with + * {@link #registerNetworkActiveListener}. + * + * @param l Previously registered listener. + */ + public void unregisterNetworkActiveListener(OnNetworkActiveListener l) { + INetworkActivityListener rl = mNetworkActivityListeners.get(l); + if (rl == null) { + throw new IllegalArgumentException("Listener not registered: " + l); + } + try { + getNetworkManagementService().unregisterNetworkActivityListener(rl); + } catch (RemoteException e) { + } + } + + /** + * Return whether the data network is currently active. An active network means that + * it is currently in a high power state for performing data transmission. On some + * types of networks, it may be expensive to move and stay in such a state, so it is + * more power efficient to batch network traffic together when the radio is already in + * this state. This method tells you whether right now is currently a good time to + * initiate network traffic, as the network is already active. + */ + public boolean isNetworkActive() { + try { + return getNetworkManagementService().isNetworkActive(); + } catch (RemoteException e) { + } + return false; + } + + /** * {@hide} */ public ConnectivityManager(IConnectivityManager service, String packageName) { @@ -1020,7 +1114,7 @@ public class ConnectivityManager { /** * Check if the device allows for tethering. It may be disabled via - * {@code ro.tether.denied} system property, {@link Settings#TETHER_SUPPORTED} or + * {@code ro.tether.denied} system property, Settings.TETHER_SUPPORTED or * due to device configuration. * * @return a boolean - {@code true} indicating Tethering is supported. @@ -1208,7 +1302,7 @@ public class ConnectivityManager { * doing something unusual like general internal filtering this may be useful. On * a private network where the proxy is not accessible, you may break HTTP using this. * - * @param proxyProperties The a {@link ProxyProperites} object defining the new global + * @param p The a {@link ProxyProperties} object defining the new global * HTTP proxy. A {@code null} value will clear the global HTTP proxy. * * <p>This method requires the call to hold the permission @@ -1341,24 +1435,6 @@ public class ConnectivityManager { /** * Signal that the captive portal check on the indicated network - * is complete and we can turn the network on for general use. - * - * @param info the {@link NetworkInfo} object for the networkType - * in question. - * - * <p>This method requires the call to hold the permission - * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}. - * {@hide} - */ - public void captivePortalCheckComplete(NetworkInfo info) { - try { - mService.captivePortalCheckComplete(info); - } catch (RemoteException e) { - } - } - - /** - * Signal that the captive portal check on the indicated network * is complete and whether its a captive portal or not. * * @param info the {@link NetworkInfo} object for the networkType @@ -1379,7 +1455,7 @@ public class ConnectivityManager { /** * Supply the backend messenger for a network tracker * - * @param type NetworkType to set + * @param networkType NetworkType to set * @param messenger {@link Messenger} * {@hide} */ diff --git a/core/java/android/net/DhcpInfo.java b/core/java/android/net/DhcpInfo.java index 3bede5d..788d7d9 100644 --- a/core/java/android/net/DhcpInfo.java +++ b/core/java/android/net/DhcpInfo.java @@ -18,7 +18,6 @@ package android.net; import android.os.Parcelable; import android.os.Parcel; -import java.net.InetAddress; /** * A simple object for retrieving the results of a DHCP request. diff --git a/core/java/android/net/DhcpResults.java b/core/java/android/net/DhcpResults.java index a3f70da..22b26b1 100644 --- a/core/java/android/net/DhcpResults.java +++ b/core/java/android/net/DhcpResults.java @@ -23,9 +23,6 @@ import android.util.Log; import java.net.InetAddress; import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; /** * A simple object for retrieving the results of a DHCP request. diff --git a/core/java/android/net/DummyDataStateTracker.java b/core/java/android/net/DummyDataStateTracker.java index 51a1191..a5d059e 100644 --- a/core/java/android/net/DummyDataStateTracker.java +++ b/core/java/android/net/DummyDataStateTracker.java @@ -117,11 +117,6 @@ public class DummyDataStateTracker extends BaseNetworkStateTracker { } @Override - public void captivePortalCheckComplete() { - // not implemented - } - - @Override public void captivePortalCheckCompleted(boolean isCaptivePortal) { // not implemented } diff --git a/core/java/android/net/EthernetDataTracker.java b/core/java/android/net/EthernetDataTracker.java index cc8c771..ec44661 100644 --- a/core/java/android/net/EthernetDataTracker.java +++ b/core/java/android/net/EthernetDataTracker.java @@ -270,11 +270,6 @@ public class EthernetDataTracker extends BaseNetworkStateTracker { } @Override - public void captivePortalCheckComplete() { - // not implemented - } - - @Override public void captivePortalCheckCompleted(boolean isCaptivePortal) { // not implemented } @@ -423,4 +418,9 @@ public class EthernetDataTracker extends BaseNetworkStateTracker { public void supplyMessenger(Messenger messenger) { // not supported on this network } + + @Override + public String getNetworkInterfaceName() { + return mIface; + } } diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index 4bca7fe..381a817 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -129,8 +129,6 @@ interface IConnectivityManager boolean updateLockdownVpn(); - void captivePortalCheckComplete(in NetworkInfo info); - void captivePortalCheckCompleted(in NetworkInfo info, boolean isCaptivePortal); void supplyMessenger(int networkType, in Messenger messenger); diff --git a/core/java/android/net/LinkSocketNotifier.java b/core/java/android/net/LinkSocketNotifier.java index 28e2834..e2429d8 100644 --- a/core/java/android/net/LinkSocketNotifier.java +++ b/core/java/android/net/LinkSocketNotifier.java @@ -16,8 +16,6 @@ package android.net; -import java.util.Map; - /** * Interface used to get feedback about a {@link android.net.LinkSocket}. Instance is optionally * passed when a LinkSocket is constructed. Multiple LinkSockets may use the same notifier. diff --git a/core/java/android/net/MailTo.java b/core/java/android/net/MailTo.java index b90dcb1..dadb6d9 100644 --- a/core/java/android/net/MailTo.java +++ b/core/java/android/net/MailTo.java @@ -19,7 +19,6 @@ package android.net; import java.util.HashMap; import java.util.Locale; import java.util.Map; -import java.util.Set; /** * diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java index c106514..3c3d8ec 100644 --- a/core/java/android/net/MobileDataStateTracker.java +++ b/core/java/android/net/MobileDataStateTracker.java @@ -306,18 +306,18 @@ public class MobileDataStateTracker extends BaseNetworkStateTracker { if (VDBG) { Slog.d(TAG, "TelephonyMgr.DataConnectionStateChanged"); if (mNetworkInfo != null) { - Slog.d(TAG, "NetworkInfo = " + mNetworkInfo.toString()); - Slog.d(TAG, "subType = " + String.valueOf(mNetworkInfo.getSubtype())); + Slog.d(TAG, "NetworkInfo = " + mNetworkInfo); + Slog.d(TAG, "subType = " + mNetworkInfo.getSubtype()); Slog.d(TAG, "subType = " + mNetworkInfo.getSubtypeName()); } if (mLinkProperties != null) { - Slog.d(TAG, "LinkProperties = " + mLinkProperties.toString()); + Slog.d(TAG, "LinkProperties = " + mLinkProperties); } else { Slog.d(TAG, "LinkProperties = " ); } if (mLinkCapabilities != null) { - Slog.d(TAG, "LinkCapabilities = " + mLinkCapabilities.toString()); + Slog.d(TAG, "LinkCapabilities = " + mLinkCapabilities); } else { Slog.d(TAG, "LinkCapabilities = " ); } @@ -460,11 +460,6 @@ public class MobileDataStateTracker extends BaseNetworkStateTracker { } @Override - public void captivePortalCheckComplete() { - // not implemented - } - - @Override public void captivePortalCheckCompleted(boolean isCaptivePortal) { if (mIsCaptivePortal.getAndSet(isCaptivePortal) != isCaptivePortal) { // Captive portal change enable/disable failing fast diff --git a/core/java/android/net/NetworkConfig.java b/core/java/android/net/NetworkConfig.java index 5d95f41..32a2cda 100644 --- a/core/java/android/net/NetworkConfig.java +++ b/core/java/android/net/NetworkConfig.java @@ -16,7 +16,6 @@ package android.net; -import android.util.Log; import java.util.Locale; /** diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java index 4d2a70d..53b1308 100644 --- a/core/java/android/net/NetworkInfo.java +++ b/core/java/android/net/NetworkInfo.java @@ -156,18 +156,20 @@ public class NetworkInfo implements Parcelable { /** {@hide} */ public NetworkInfo(NetworkInfo source) { if (source != null) { - mNetworkType = source.mNetworkType; - mSubtype = source.mSubtype; - mTypeName = source.mTypeName; - mSubtypeName = source.mSubtypeName; - mState = source.mState; - mDetailedState = source.mDetailedState; - mReason = source.mReason; - mExtraInfo = source.mExtraInfo; - mIsFailover = source.mIsFailover; - mIsRoaming = source.mIsRoaming; - mIsAvailable = source.mIsAvailable; - mIsConnectedToProvisioningNetwork = source.mIsConnectedToProvisioningNetwork; + synchronized (source) { + mNetworkType = source.mNetworkType; + mSubtype = source.mSubtype; + mTypeName = source.mTypeName; + mSubtypeName = source.mSubtypeName; + mState = source.mState; + mDetailedState = source.mDetailedState; + mReason = source.mReason; + mExtraInfo = source.mExtraInfo; + mIsFailover = source.mIsFailover; + mIsRoaming = source.mIsRoaming; + mIsAvailable = source.mIsAvailable; + mIsConnectedToProvisioningNetwork = source.mIsConnectedToProvisioningNetwork; + } } } diff --git a/core/java/android/net/NetworkStateTracker.java b/core/java/android/net/NetworkStateTracker.java index 1ca9255..c49b1d1 100644 --- a/core/java/android/net/NetworkStateTracker.java +++ b/core/java/android/net/NetworkStateTracker.java @@ -144,11 +144,6 @@ public interface NetworkStateTracker { public boolean reconnect(); /** - * Ready to switch on to the network after captive portal check - */ - public void captivePortalCheckComplete(); - - /** * Captive portal check has completed */ public void captivePortalCheckCompleted(boolean isCaptive); diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java index a7aae2a..54d43d3 100644 --- a/core/java/android/net/NetworkStats.java +++ b/core/java/android/net/NetworkStats.java @@ -44,6 +44,8 @@ public class NetworkStats implements Parcelable { public static final String IFACE_ALL = null; /** {@link #uid} value when UID details unavailable. */ public static final int UID_ALL = -1; + /** {@link #tag} value matching any tag. */ + public static final int TAG_ALL = -1; /** {@link #set} value when all sets combined. */ public static final int SET_ALL = -1; /** {@link #set} value where background data is accounted. */ @@ -59,8 +61,9 @@ public class NetworkStats implements Parcelable { * {@link SystemClock#elapsedRealtime()} timestamp when this data was * generated. */ - private final long elapsedRealtime; + private long elapsedRealtime; private int size; + private int capacity; private String[] iface; private int[] uid; private int[] set; @@ -152,20 +155,27 @@ public class NetworkStats implements Parcelable { public NetworkStats(long elapsedRealtime, int initialSize) { this.elapsedRealtime = elapsedRealtime; this.size = 0; - this.iface = new String[initialSize]; - this.uid = new int[initialSize]; - this.set = new int[initialSize]; - this.tag = new int[initialSize]; - this.rxBytes = new long[initialSize]; - this.rxPackets = new long[initialSize]; - this.txBytes = new long[initialSize]; - this.txPackets = new long[initialSize]; - this.operations = new long[initialSize]; + if (initialSize >= 0) { + this.capacity = initialSize; + this.iface = new String[initialSize]; + this.uid = new int[initialSize]; + this.set = new int[initialSize]; + this.tag = new int[initialSize]; + this.rxBytes = new long[initialSize]; + this.rxPackets = new long[initialSize]; + this.txBytes = new long[initialSize]; + this.txPackets = new long[initialSize]; + this.operations = new long[initialSize]; + } else { + // Special case for use by NetworkStatsFactory to start out *really* empty. + this.capacity = 0; + } } public NetworkStats(Parcel parcel) { elapsedRealtime = parcel.readLong(); size = parcel.readInt(); + capacity = parcel.readInt(); iface = parcel.createStringArray(); uid = parcel.createIntArray(); set = parcel.createIntArray(); @@ -181,6 +191,7 @@ public class NetworkStats implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeLong(elapsedRealtime); dest.writeInt(size); + dest.writeInt(capacity); dest.writeStringArray(iface); dest.writeIntArray(uid); dest.writeIntArray(set); @@ -222,8 +233,8 @@ public class NetworkStats implements Parcelable { * object can be recycled across multiple calls. */ public NetworkStats addValues(Entry entry) { - if (size >= this.iface.length) { - final int newLength = Math.max(iface.length, 10) * 3 / 2; + if (size >= capacity) { + final int newLength = Math.max(size, 10) * 3 / 2; iface = Arrays.copyOf(iface, newLength); uid = Arrays.copyOf(uid, newLength); set = Arrays.copyOf(set, newLength); @@ -233,6 +244,7 @@ public class NetworkStats implements Parcelable { txBytes = Arrays.copyOf(txBytes, newLength); txPackets = Arrays.copyOf(txPackets, newLength); operations = Arrays.copyOf(operations, newLength); + capacity = newLength; } iface[size] = entry.iface; @@ -270,6 +282,10 @@ public class NetworkStats implements Parcelable { return elapsedRealtime; } + public void setElapsedRealtime(long time) { + elapsedRealtime = time; + } + /** * Return age of this {@link NetworkStats} object with respect to * {@link SystemClock#elapsedRealtime()}. @@ -284,7 +300,7 @@ public class NetworkStats implements Parcelable { @VisibleForTesting public int internalSize() { - return iface.length; + return capacity; } @Deprecated @@ -491,6 +507,17 @@ public class NetworkStats implements Parcelable { } /** + * Fast path for battery stats. + */ + public long getTotalPackets() { + long total = 0; + for (int i = size-1; i >= 0; i--) { + total += rxPackets[i] + txPackets[i]; + } + return total; + } + + /** * Subtract the given {@link NetworkStats}, effectively leaving the delta * between two snapshots in time. Assumes that statistics rows collect over * time, and that none of them have disappeared. @@ -507,8 +534,25 @@ public class NetworkStats implements Parcelable { * If counters have rolled backwards, they are clamped to {@code 0} and * reported to the given {@link NonMonotonicObserver}. */ - public static <C> NetworkStats subtract( - NetworkStats left, NetworkStats right, NonMonotonicObserver<C> observer, C cookie) { + public static <C> NetworkStats subtract(NetworkStats left, NetworkStats right, + NonMonotonicObserver<C> observer, C cookie) { + return subtract(left, right, observer, cookie, null); + } + + /** + * Subtract the two given {@link NetworkStats} objects, returning the delta + * between two snapshots in time. Assumes that statistics rows collect over + * time, and that none of them have disappeared. + * <p> + * If counters have rolled backwards, they are clamped to {@code 0} and + * reported to the given {@link NonMonotonicObserver}. + * <p> + * If <var>recycle</var> is supplied, this NetworkStats object will be + * reused (and returned) as the result if it is large enough to contain + * the data. + */ + public static <C> NetworkStats subtract(NetworkStats left, NetworkStats right, + NonMonotonicObserver<C> observer, C cookie, NetworkStats recycle) { long deltaRealtime = left.elapsedRealtime - right.elapsedRealtime; if (deltaRealtime < 0) { if (observer != null) { @@ -519,7 +563,14 @@ public class NetworkStats implements Parcelable { // result will have our rows, and elapsed time between snapshots final Entry entry = new Entry(); - final NetworkStats result = new NetworkStats(deltaRealtime, left.size); + final NetworkStats result; + if (recycle != null && recycle.capacity >= left.size) { + result = recycle; + result.size = 0; + result.elapsedRealtime = deltaRealtime; + } else { + result = new NetworkStats(deltaRealtime, left.size); + } for (int i = 0; i < left.size; i++) { entry.iface = left.iface[i]; entry.uid = left.uid[i]; diff --git a/core/java/android/net/Proxy.java b/core/java/android/net/Proxy.java index c3e1438..033332c 100644 --- a/core/java/android/net/Proxy.java +++ b/core/java/android/net/Proxy.java @@ -66,6 +66,19 @@ public final class Proxy { /** {@hide} **/ public static final String EXTRA_PROXY_INFO = "proxy"; + /** @hide */ + public static final int PROXY_VALID = 0; + /** @hide */ + public static final int PROXY_HOSTNAME_EMPTY = 1; + /** @hide */ + public static final int PROXY_HOSTNAME_INVALID = 2; + /** @hide */ + public static final int PROXY_PORT_EMPTY = 3; + /** @hide */ + public static final int PROXY_PORT_INVALID = 4; + /** @hide */ + public static final int PROXY_EXCLLIST_INVALID = 5; + private static ConnectivityManager sConnectivityManager = null; // Hostname / IP REGEX validation @@ -77,8 +90,10 @@ public final class Proxy { private static final Pattern HOSTNAME_PATTERN; - private static final String EXCLLIST_REGEXP = "$|^(.?" + NAME_IP_REGEX - + ")+(,(.?" + NAME_IP_REGEX + "))*$"; + private static final String EXCL_REGEX = + "[a-zA-Z0-9*]+(\\-[a-zA-Z0-9*]+)*(\\.[a-zA-Z0-9*]+(\\-[a-zA-Z0-9*]+)*)*"; + + private static final String EXCLLIST_REGEXP = "^$|^" + EXCL_REGEX + "(," + EXCL_REGEX + ")*$"; private static final Pattern EXCLLIST_PATTERN; @@ -236,36 +251,27 @@ public final class Proxy { * Validate syntax of hostname, port and exclusion list entries * {@hide} */ - public static void validate(String hostname, String port, String exclList) { + public static int validate(String hostname, String port, String exclList) { Matcher match = HOSTNAME_PATTERN.matcher(hostname); Matcher listMatch = EXCLLIST_PATTERN.matcher(exclList); - if (!match.matches()) { - throw new IllegalArgumentException(); - } + if (!match.matches()) return PROXY_HOSTNAME_INVALID; - if (!listMatch.matches()) { - throw new IllegalArgumentException(); - } + if (!listMatch.matches()) return PROXY_EXCLLIST_INVALID; - if (hostname.length() > 0 && port.length() == 0) { - throw new IllegalArgumentException(); - } + if (hostname.length() > 0 && port.length() == 0) return PROXY_PORT_EMPTY; if (port.length() > 0) { - if (hostname.length() == 0) { - throw new IllegalArgumentException(); - } + if (hostname.length() == 0) return PROXY_HOSTNAME_EMPTY; int portVal = -1; try { portVal = Integer.parseInt(port); } catch (NumberFormatException ex) { - throw new IllegalArgumentException(); - } - if (portVal <= 0 || portVal > 0xFFFF) { - throw new IllegalArgumentException(); + return PROXY_PORT_INVALID; } + if (portVal <= 0 || portVal > 0xFFFF) return PROXY_PORT_INVALID; } + return PROXY_VALID; } static class AndroidProxySelectorRoutePlanner diff --git a/core/java/android/net/ProxyProperties.java b/core/java/android/net/ProxyProperties.java index 010e527..50f45e8 100644 --- a/core/java/android/net/ProxyProperties.java +++ b/core/java/android/net/ProxyProperties.java @@ -22,7 +22,6 @@ import android.os.Parcelable; import android.text.TextUtils; import java.net.InetSocketAddress; -import java.net.UnknownHostException; import java.util.Locale; /** @@ -141,13 +140,9 @@ public class ProxyProperties implements Parcelable { public boolean isValid() { if (!TextUtils.isEmpty(mPacFileUrl)) return true; - try { - Proxy.validate(mHost == null ? "" : mHost, mPort == 0 ? "" : Integer.toString(mPort), - mExclusionList == null ? "" : mExclusionList); - } catch (IllegalArgumentException e) { - return false; - } - return true; + return Proxy.PROXY_VALID == Proxy.validate(mHost == null ? "" : mHost, + mPort == 0 ? "" : Integer.toString(mPort), + mExclusionList == null ? "" : mExclusionList); } public java.net.Proxy makeProxy() { diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java index b0278d3..12e8791 100644 --- a/core/java/android/net/SSLCertificateSocketFactory.java +++ b/core/java/android/net/SSLCertificateSocketFactory.java @@ -135,7 +135,8 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory { * disabled, using an optional handshake timeout and SSL session cache. * * <p class="caution"><b>Warning:</b> Sockets created using this factory - * are vulnerable to man-in-the-middle attacks!</p> + * are vulnerable to man-in-the-middle attacks!</p>. The caller must implement + * its own verification. * * @param handshakeTimeoutMillis to use for SSL connection handshake, or 0 * for none. The socket timeout is reset to 0 after the handshake. @@ -223,8 +224,6 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory { if (mInsecureFactory == null) { if (mSecure) { Log.w(TAG, "*** BYPASSING SSL SECURITY CHECKS (socket.relaxsslcheck=yes) ***"); - } else { - Log.w(TAG, "Bypassing SSL security checks at caller's request"); } mInsecureFactory = makeSocketFactory(mKeyManagers, INSECURE_TRUST_MANAGER); } @@ -431,6 +430,7 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory { s.setAlpnProtocols(mAlpnProtocols); s.setHandshakeTimeout(mHandshakeTimeoutMillis); s.setChannelIdPrivateKey(mChannelIdPrivateKey); + s.setHostname(host); if (mSecure) { verifyHostname(s, host); } diff --git a/core/java/android/net/SSLSessionCache.java b/core/java/android/net/SSLSessionCache.java index 15421de..65db2c3 100644 --- a/core/java/android/net/SSLSessionCache.java +++ b/core/java/android/net/SSLSessionCache.java @@ -19,12 +19,16 @@ package android.net; import android.content.Context; import android.util.Log; +import com.android.org.conscrypt.ClientSessionContext; import com.android.org.conscrypt.FileClientSessionCache; import com.android.org.conscrypt.SSLClientSessionCache; import java.io.File; import java.io.IOException; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSessionContext; + /** * File-based cache of established SSL sessions. When re-establishing a * connection to the same server, using an SSL session cache can save some time, @@ -38,6 +42,40 @@ public final class SSLSessionCache { /* package */ final SSLClientSessionCache mSessionCache; /** + * Installs a {@link SSLSessionCache} on a {@link SSLContext}. The cache will + * be used on all socket factories created by this context (including factories + * created before this call). + * + * @param cache the cache instance to install, or {@code null} to uninstall any + * existing cache. + * @param context the context to install it on. + * @throws IllegalArgumentException if the context does not support a session + * cache. + * + * @hide candidate for public API + */ + public static void install(SSLSessionCache cache, SSLContext context) { + SSLSessionContext clientContext = context.getClientSessionContext(); + if (clientContext instanceof ClientSessionContext) { + ((ClientSessionContext) clientContext).setPersistentCache( + cache == null ? null : cache.mSessionCache); + } else { + throw new IllegalArgumentException("Incompatible SSLContext: " + context); + } + } + + /** + * NOTE: This needs to be Object (and not SSLClientSessionCache) because apps + * that build directly against the framework (and not the SDK) might not declare + * a dependency on conscrypt. Javac will then has fail while resolving constructors. + * + * @hide For unit test use only + */ + public SSLSessionCache(Object cache) { + mSessionCache = (SSLClientSessionCache) cache; + } + + /** * Create a session cache using the specified directory. * Individual session entries will be files within the directory. * Multiple instances for the same directory share data internally. diff --git a/core/java/android/net/SntpClient.java b/core/java/android/net/SntpClient.java index 316440f..7673011 100644 --- a/core/java/android/net/SntpClient.java +++ b/core/java/android/net/SntpClient.java @@ -19,7 +19,6 @@ package android.net; import android.os.SystemClock; import android.util.Log; -import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java index d7dc7f5..7385dff 100644 --- a/core/java/android/net/VpnService.java +++ b/core/java/android/net/VpnService.java @@ -151,9 +151,10 @@ public class VpnService extends Service { } /** - * Protect a socket from VPN connections. The socket will be bound to the - * current default network interface, so its traffic will not be forwarded - * through VPN. This method is useful if some connections need to be kept + * Protect a socket from VPN connections. After protecting, data sent + * through this socket will go directly to the underlying network, + * so its traffic will not be forwarded through the VPN. + * This method is useful if some connections need to be kept * outside of VPN. For example, a VPN tunnel should protect itself if its * destination is covered by VPN routes. Otherwise its outgoing packets * will be sent back to the VPN interface and cause an infinite loop. This diff --git a/core/java/android/net/dhcp/DhcpAckPacket.java b/core/java/android/net/dhcp/DhcpAckPacket.java index 4eca531..7b8be9c 100644 --- a/core/java/android/net/dhcp/DhcpAckPacket.java +++ b/core/java/android/net/dhcp/DhcpAckPacket.java @@ -19,7 +19,6 @@ package android.net.dhcp; import java.net.InetAddress; import java.net.Inet4Address; import java.nio.ByteBuffer; -import java.util.List; /** * This class implements the DHCP-ACK packet. diff --git a/core/java/android/net/dhcp/DhcpOfferPacket.java b/core/java/android/net/dhcp/DhcpOfferPacket.java index 3d79f4d..f1c30e1 100644 --- a/core/java/android/net/dhcp/DhcpOfferPacket.java +++ b/core/java/android/net/dhcp/DhcpOfferPacket.java @@ -19,7 +19,6 @@ package android.net.dhcp; import java.net.InetAddress; import java.net.Inet4Address; import java.nio.ByteBuffer; -import java.util.List; /** * This class implements the DHCP-OFFER packet. diff --git a/core/java/android/net/dhcp/DhcpPacket.java b/core/java/android/net/dhcp/DhcpPacket.java index 317a9b4..c7c25f0 100644 --- a/core/java/android/net/dhcp/DhcpPacket.java +++ b/core/java/android/net/dhcp/DhcpPacket.java @@ -1,8 +1,5 @@ package android.net.dhcp; -import android.util.Log; - -import java.io.UnsupportedEncodingException; import java.net.InetAddress; import java.net.UnknownHostException; import java.nio.ByteBuffer; diff --git a/core/java/android/net/dhcp/DhcpStateMachine.java b/core/java/android/net/dhcp/DhcpStateMachine.java index b6c384d..bc9a798 100644 --- a/core/java/android/net/dhcp/DhcpStateMachine.java +++ b/core/java/android/net/dhcp/DhcpStateMachine.java @@ -17,7 +17,6 @@ package android.net.dhcp; import java.net.InetAddress; -import java.nio.ByteBuffer; import java.util.List; /** diff --git a/core/java/android/net/http/AndroidHttpClientConnection.java b/core/java/android/net/http/AndroidHttpClientConnection.java index eb96679..6d48fce 100644 --- a/core/java/android/net/http/AndroidHttpClientConnection.java +++ b/core/java/android/net/http/AndroidHttpClientConnection.java @@ -16,8 +16,6 @@ package android.net.http; -import org.apache.http.Header; - import org.apache.http.HttpConnection; import org.apache.http.HttpClientConnection; import org.apache.http.HttpConnectionMetrics; @@ -27,12 +25,10 @@ import org.apache.http.HttpException; import org.apache.http.HttpInetConnection; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; -import org.apache.http.HttpResponseFactory; import org.apache.http.NoHttpResponseException; import org.apache.http.StatusLine; import org.apache.http.entity.BasicHttpEntity; import org.apache.http.entity.ContentLengthStrategy; -import org.apache.http.impl.DefaultHttpResponseFactory; import org.apache.http.impl.HttpConnectionMetricsImpl; import org.apache.http.impl.entity.EntitySerializer; import org.apache.http.impl.entity.StrictContentLengthStrategy; diff --git a/core/java/android/net/http/Connection.java b/core/java/android/net/http/Connection.java index 95cecd2..834ad69 100644 --- a/core/java/android/net/http/Connection.java +++ b/core/java/android/net/http/Connection.java @@ -21,7 +21,6 @@ import android.os.SystemClock; import java.io.IOException; import java.net.UnknownHostException; -import java.util.ListIterator; import java.util.LinkedList; import javax.net.ssl.SSLHandshakeException; diff --git a/core/java/android/net/http/ConnectionThread.java b/core/java/android/net/http/ConnectionThread.java index 32191d2..d825530 100644 --- a/core/java/android/net/http/ConnectionThread.java +++ b/core/java/android/net/http/ConnectionThread.java @@ -19,8 +19,6 @@ package android.net.http; import android.content.Context; import android.os.SystemClock; -import org.apache.http.HttpHost; - import java.lang.Thread; /** diff --git a/core/java/android/net/http/HttpConnection.java b/core/java/android/net/http/HttpConnection.java index 6df86bf..edf8fed3 100644 --- a/core/java/android/net/http/HttpConnection.java +++ b/core/java/android/net/http/HttpConnection.java @@ -21,9 +21,7 @@ import android.content.Context; import java.net.Socket; import java.io.IOException; -import org.apache.http.HttpClientConnection; import org.apache.http.HttpHost; -import org.apache.http.impl.DefaultHttpClientConnection; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpConnectionParams; diff --git a/core/java/android/net/http/HttpResponseCache.java b/core/java/android/net/http/HttpResponseCache.java index 269dfb8..2785a15 100644 --- a/core/java/android/net/http/HttpResponseCache.java +++ b/core/java/android/net/http/HttpResponseCache.java @@ -17,9 +17,6 @@ package android.net.http; import android.content.Context; -import com.android.okhttp.OkResponseCache; -import com.android.okhttp.ResponseSource; -import com.android.okhttp.internal.DiskLruCache; import java.io.Closeable; import java.io.File; import java.io.IOException; @@ -32,7 +29,6 @@ import java.net.URLConnection; import java.util.List; import java.util.Map; import javax.net.ssl.HttpsURLConnection; -import libcore.io.IoUtils; import org.apache.http.impl.client.DefaultHttpClient; /** diff --git a/core/java/android/net/http/HttpsConnection.java b/core/java/android/net/http/HttpsConnection.java index 7a12e53..6bf01e2 100644 --- a/core/java/android/net/http/HttpsConnection.java +++ b/core/java/android/net/http/HttpsConnection.java @@ -40,7 +40,6 @@ import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import java.io.File; import java.io.IOException; -import java.net.InetSocketAddress; import java.net.Socket; import java.security.KeyManagementException; import java.security.cert.X509Certificate; diff --git a/core/java/android/net/http/Request.java b/core/java/android/net/http/Request.java index 8c0d503..76d7bb9 100644 --- a/core/java/android/net/http/Request.java +++ b/core/java/android/net/http/Request.java @@ -26,15 +26,12 @@ import java.util.zip.GZIPInputStream; import org.apache.http.entity.InputStreamEntity; import org.apache.http.Header; -import org.apache.http.HttpClientConnection; import org.apache.http.HttpEntity; import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.HttpException; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; -import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; -import org.apache.http.HttpVersion; import org.apache.http.ParseException; import org.apache.http.ProtocolVersion; diff --git a/core/java/android/net/http/RequestQueue.java b/core/java/android/net/http/RequestQueue.java index ce6b1ad..7d2da1b 100644 --- a/core/java/android/net/http/RequestQueue.java +++ b/core/java/android/net/http/RequestQueue.java @@ -29,10 +29,6 @@ import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Proxy; import android.net.WebAddress; -import android.os.Handler; -import android.os.Message; -import android.os.SystemProperties; -import android.text.TextUtils; import android.util.Log; import java.io.InputStream; diff --git a/core/java/android/net/http/X509TrustManagerExtensions.java b/core/java/android/net/http/X509TrustManagerExtensions.java index cfe5f27..db71279 100644 --- a/core/java/android/net/http/X509TrustManagerExtensions.java +++ b/core/java/android/net/http/X509TrustManagerExtensions.java @@ -63,4 +63,18 @@ public class X509TrustManagerExtensions { String host) throws CertificateException { return mDelegate.checkServerTrusted(chain, authType, host); } + + /** + * Checks whether a CA certificate is added by an user. + * + * <p>Since {@link X509TrustManager#checkServerTrusted} allows its parameter {@code chain} to + * chain up to user-added CA certificates, this method can be used to perform additional + * policies for user-added CA certificates. + * + * @return {@code true} to indicate that the certificate was added by the user, {@code false} + * otherwise. + */ + public boolean isUserAddedCertificate(X509Certificate cert) { + return mDelegate.isUserAddedCertificate(cert); + } } diff --git a/core/java/android/net/nsd/NsdManager.java b/core/java/android/net/nsd/NsdManager.java index 7b2c623..d8e8e2c 100644 --- a/core/java/android/net/nsd/NsdManager.java +++ b/core/java/android/net/nsd/NsdManager.java @@ -19,8 +19,6 @@ package android.net.nsd; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.content.Context; -import android.os.Binder; -import android.os.IBinder; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl index 8414738..635a50f 100644 --- a/core/java/android/nfc/INfcAdapter.aidl +++ b/core/java/android/nfc/INfcAdapter.aidl @@ -25,6 +25,7 @@ import android.nfc.IAppCallback; import android.nfc.INfcAdapterExtras; import android.nfc.INfcTag; import android.nfc.INfcCardEmulation; +import android.nfc.INfcUnlockSettings; import android.os.Bundle; /** @@ -35,6 +36,7 @@ interface INfcAdapter INfcTag getNfcTagInterface(); INfcCardEmulation getNfcCardEmulationInterface(); INfcAdapterExtras getNfcAdapterExtrasInterface(in String pkg); + INfcUnlockSettings getNfcUnlockSettingsInterface(); int getState(); boolean disable(boolean saveState); @@ -46,6 +48,7 @@ interface INfcAdapter void setForegroundDispatch(in PendingIntent intent, in IntentFilter[] filters, in TechListParcel techLists); void setAppCallback(in IAppCallback callback); + void invokeBeam(); void dispatch(in Tag tag); diff --git a/core/java/android/nfc/INfcUnlockSettings.aidl b/core/java/android/nfc/INfcUnlockSettings.aidl new file mode 100644 index 0000000..649eeed --- /dev/null +++ b/core/java/android/nfc/INfcUnlockSettings.aidl @@ -0,0 +1,70 @@ +/* + * 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.nfc; + +import android.nfc.Tag; +import java.util.List; + +/** + * Interface to NFC unlock functionality. + * + * @hide + */ +interface INfcUnlockSettings { + + /** + * Checks the validity of the tag and attempts to unlock the screen. + * + * @return true if the screen was successfuly unlocked. + */ + boolean tryUnlock(int userId, in Tag tag); + + /** + * Registers the given tag as an unlock tag. Subsequent calls to {@code tryUnlock} + * with the same {@code tag} should succeed. + * + * @return true if the tag was successfully registered. + */ + boolean registerTag(int userId, in Tag tag); + + /** + * Deregisters the tag with the corresponding timestamp. + * Subsequent calls to {@code tryUnlock} with the same tag should fail. + * + * @return true if the tag was successfully deleted. + */ + boolean deregisterTag(int userId, long timestamp); + + /** + * Used for user-visible rendering of registered tags. + * + * @return a list of the times in millis since epoch when the registered tags were paired. + */ + long[] getTagRegistryTimes(int userId); + + /** + * Determines the state of the NFC unlock feature. + * + * @return true if NFC unlock is enabled. + */ + boolean getNfcUnlockEnabled(int userId); + + /** + * Sets the state [ON | OFF] of the NFC unlock feature. + */ + void setNfcUnlockEnabled(int userId, boolean enabled); +} diff --git a/core/java/android/nfc/NdefRecord.java b/core/java/android/nfc/NdefRecord.java index 2b58818..83d17ba 100644 --- a/core/java/android/nfc/NdefRecord.java +++ b/core/java/android/nfc/NdefRecord.java @@ -20,6 +20,7 @@ import android.content.Intent; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; + import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; @@ -269,6 +270,7 @@ public final class NdefRecord implements Parcelable { "urn:epc:pat:", // 0x20 "urn:epc:raw:", // 0x21 "urn:epc:", // 0x22 + "urn:nfc:", // 0x23 }; private static final int MAX_PAYLOAD_SIZE = 10 * (1 << 20); // 10 MB payload limit @@ -473,6 +475,45 @@ public final class NdefRecord implements Parcelable { } /** + * Create a new NDEF record containing UTF-8 text data.<p> + * + * The caller can either specify the language code for the provided text, + * or otherwise the language code corresponding to the current default + * locale will be used. + * + * Reference specification: NFCForum-TS-RTD_Text_1.0 + * @param languageCode The languageCode for the record. If locale is empty or null, + * the language code of the current default locale will be used. + * @param text The text to be encoded in the record. Will be represented in UTF-8 format. + * @throws IllegalArgumentException if text is null + */ + public static NdefRecord createTextRecord(String languageCode, String text) { + if (text == null) throw new NullPointerException("text is null"); + + byte[] textBytes = text.getBytes(StandardCharsets.UTF_8); + + byte[] languageCodeBytes = null; + if (languageCode != null && !languageCode.isEmpty()) { + languageCodeBytes = languageCode.getBytes(StandardCharsets.US_ASCII); + } else { + languageCodeBytes = Locale.getDefault().getLanguage(). + getBytes(StandardCharsets.US_ASCII); + } + // We only have 6 bits to indicate ISO/IANA language code. + if (languageCodeBytes.length >= 64) { + throw new IllegalArgumentException("language code is too long, must be <64 bytes."); + } + ByteBuffer buffer = ByteBuffer.allocate(1 + languageCodeBytes.length + textBytes.length); + + byte status = (byte) (languageCodeBytes.length & 0xFF); + buffer.put(status); + buffer.put(languageCodeBytes); + buffer.put(textBytes); + + return new NdefRecord(TNF_WELL_KNOWN, RTD_TEXT, null, buffer.array()); + } + + /** * Construct an NDEF Record from its component fields.<p> * Recommend to use helpers such as {#createUri} or * {{@link #createExternal} where possible, since they perform @@ -774,7 +815,7 @@ public final class NdefRecord implements Parcelable { throw new FormatException("expected TNF_UNCHANGED in non-leading chunk"); } else if (!inChunk && tnf == NdefRecord.TNF_UNCHANGED) { throw new FormatException("" + - "unexpected TNF_UNCHANGED in first chunk or unchunked record"); + "unexpected TNF_UNCHANGED in first chunk or unchunked record"); } int typeLength = buffer.get() & 0xFF; diff --git a/core/java/android/nfc/NfcActivityManager.java b/core/java/android/nfc/NfcActivityManager.java index 77c0234..8643f2e 100644 --- a/core/java/android/nfc/NfcActivityManager.java +++ b/core/java/android/nfc/NfcActivityManager.java @@ -18,6 +18,7 @@ package android.nfc; import android.app.Activity; import android.app.Application; +import android.content.Intent; import android.net.Uri; import android.nfc.NfcAdapter.ReaderCallback; import android.os.Binder; @@ -327,6 +328,7 @@ public final class NfcActivityManager extends IAppCallback.Stub NfcAdapter.CreateNdefMessageCallback ndefCallback; NfcAdapter.CreateBeamUrisCallback urisCallback; NdefMessage message; + Activity activity; Uri[] uris; int flags; synchronized (NfcActivityManager.this) { @@ -338,6 +340,7 @@ public final class NfcActivityManager extends IAppCallback.Stub message = state.ndefMessage; uris = state.uris; flags = state.flags; + activity = state.activity; } // Make callbacks without lock @@ -362,7 +365,13 @@ public final class NfcActivityManager extends IAppCallback.Stub } } } - + if (uris != null && uris.length > 0) { + for (Uri uri : uris) { + // Grant the NFC process permission to read these URIs + activity.grantUriPermission("com.android.nfc", uri, + Intent.FLAG_GRANT_READ_URI_PERMISSION); + } + } return new BeamShareData(message, uris, flags); } diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java index 6743c6c..96a3947 100644 --- a/core/java/android/nfc/NfcAdapter.java +++ b/core/java/android/nfc/NfcAdapter.java @@ -292,6 +292,7 @@ public final class NfcAdapter { static INfcAdapter sService; static INfcTag sTagService; static INfcCardEmulation sCardEmulationService; + static INfcUnlockSettings sNfcUnlockSettingsService; /** * The NfcAdapter object for each application context. @@ -432,6 +433,13 @@ public final class NfcAdapter { throw new UnsupportedOperationException(); } + try { + sNfcUnlockSettingsService = sService.getNfcUnlockSettingsInterface(); + } catch (RemoteException e) { + Log.e(TAG, "could not retrieve NFC unlock settings service"); + sNfcUnlockSettingsService = null; + } + sIsInitialized = true; } if (context == null) { @@ -549,6 +557,22 @@ public final class NfcAdapter { } /** + * Returns the binder interface to the NFC unlock service. + * + * @throws UnsupportedOperationException if the service is not available. + * @hide + */ + public INfcUnlockSettings getNfcUnlockSettingsService() throws UnsupportedOperationException { + isEnabled(); + + if (sNfcUnlockSettingsService == null) { + throw new UnsupportedOperationException("NfcUnlockSettingsService not available"); + } + + return sNfcUnlockSettingsService; + } + + /** * NFC service dead - attempt best effort recovery * @hide */ @@ -1225,6 +1249,45 @@ public final class NfcAdapter { } /** + * Manually invoke Android Beam to share data. + * + * <p>The Android Beam animation is normally only shown when two NFC-capable + * devices come into range. + * By calling this method, an Activity can invoke the Beam animation directly + * even if no other NFC device is in range yet. The Beam animation will then + * prompt the user to tap another NFC-capable device to complete the data + * transfer. + * + * <p>The main advantage of using this method is that it avoids the need for the + * user to tap the screen to complete the transfer, as this method already + * establishes the direction of the transfer and the consent of the user to + * share data. Callers are responsible for making sure that the user has + * consented to sharing data on NFC tap. + * + * <p>Note that to use this method, the passed in Activity must have already + * set data to share over Beam by using method calls such as + * {@link #setNdefPushMessageCallback} or + * {@link #setBeamPushUrisCallback}. + * + * @param activity the current foreground Activity that has registered data to share + * @return whether the Beam animation was successfully invoked + */ + public boolean invokeBeam(Activity activity) { + if (activity == null) { + throw new NullPointerException("activity may not be null."); + } + enforceResumed(activity); + try { + sService.invokeBeam(); + return true; + } catch (RemoteException e) { + Log.e(TAG, "invokeBeam: NFC process has died."); + attemptDeadServiceRecovery(e); + return false; + } + } + + /** * Enable NDEF message push over NFC while this Activity is in the foreground. * * <p>You must explicitly call this method every time the activity is diff --git a/core/java/android/nfc/NfcUnlock.java b/core/java/android/nfc/NfcUnlock.java new file mode 100644 index 0000000..82dcd96 --- /dev/null +++ b/core/java/android/nfc/NfcUnlock.java @@ -0,0 +1,255 @@ +/* + * 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.nfc; + +import static com.android.internal.util.Preconditions.checkNotNull; + +import android.annotation.Nullable; +import android.app.ActivityManager; +import android.content.Context; +import android.os.RemoteException; +import android.os.SystemProperties; +import android.util.Log; + +import java.util.HashMap; + +/** + * Provides an interface to read and update NFC unlock settings. + * <p/> + * Allows system services (currently exclusively LockSettingsService) to + * register NFC tags to be used to unlock the device, as well as the ability + * to enable/disable the service entirely. + * + */ +public class NfcUnlock { + + /** + * Action to unlock the device. + * + * @hide + */ + public static final String ACTION_NFC_UNLOCK = "android.nfc.ACTION_NFC_UNLOCK"; + /** + * Permission to unlock the device. + * + * @hide + */ + public static final String NFC_UNLOCK_PERMISSION = "android.permission.NFC_UNLOCK"; + + /** + * Property to enable NFC Unlock + * + * @hide + */ + public static final String PROPERTY = "ro.com.android.nfc.unlock"; + + private static final String TAG = "NfcUnlock"; + private static HashMap<Context, NfcUnlock> sNfcUnlocks = new HashMap<Context, NfcUnlock>(); + + private final Context mContext; + private final boolean mEnabled; + private INfcUnlockSettings sService; + + private NfcUnlock(Context context, INfcUnlockSettings service) { + this.mContext = checkNotNull(context); + this.sService = checkNotNull(service); + this.mEnabled = getPropertyEnabled(); + } + + /** + * Returns an instance of {@link NfcUnlock}. + */ + public static synchronized NfcUnlock getInstance(NfcAdapter nfcAdapter) { + Context context = nfcAdapter.getContext(); + if (context == null) { + Log.e(TAG, "NfcAdapter context is null"); + throw new UnsupportedOperationException(); + } + + NfcUnlock manager = sNfcUnlocks.get(context); + if (manager == null) { + INfcUnlockSettings service = nfcAdapter.getNfcUnlockSettingsService(); + manager = new NfcUnlock(context, service); + sNfcUnlocks.put(context, manager); + } + + return manager; + } + + /** + * Registers the given {@code tag} as an unlock tag. + * + * @return true if the tag was successfully registered. + * @hide + */ + public boolean registerTag(Tag tag) { + enforcePropertyEnabled(); + + int currentUser = ActivityManager.getCurrentUser(); + + try { + return sService.registerTag(currentUser, tag); + } catch (RemoteException e) { + recoverService(); + if (sService == null) { + Log.e(TAG, "Failed to recover NfcUnlockSettingsService"); + return false; + } + + try { + return sService.registerTag(currentUser, tag); + } catch (RemoteException ee) { + Log.e(TAG, "Failed to reach NfcUnlockSettingsService", ee); + return false; + } + } + } + + /** + * Deregisters the given {@code tag} as an unlock tag. + * + * @return true if the tag was successfully deregistered. + * @hide + */ + public boolean deregisterTag(long timestamp) { + enforcePropertyEnabled(); + int currentUser = ActivityManager.getCurrentUser(); + + try { + return sService.deregisterTag(currentUser, timestamp); + } catch (RemoteException e) { + recoverService(); + if (sService == null) { + Log.e(TAG, "Failed to recover NfcUnlockSettingsService"); + return false; + } + + try { + return sService.deregisterTag(currentUser, timestamp); + } catch (RemoteException ee) { + Log.e(TAG, "Failed to reach NfcUnlockSettingsService", ee); + return false; + } + } + } + + /** + * Determines the enable state of the NFC unlock feature. + * + * @return true if NFC unlock is enabled. + */ + public boolean getNfcUnlockEnabled() { + enforcePropertyEnabled(); + int currentUser = ActivityManager.getCurrentUser(); + + try { + return sService.getNfcUnlockEnabled(currentUser); + } catch (RemoteException e) { + recoverService(); + if (sService == null) { + Log.e(TAG, "Failed to recover NfcUnlockSettingsService"); + return false; + } + + try { + return sService.getNfcUnlockEnabled(currentUser); + } catch (RemoteException ee) { + Log.e(TAG, "Failed to reach NfcUnlockSettingsService", ee); + return false; + } + } + } + + /** + * Set the enable state of the NFC unlock feature. + * + * @return true if the setting was successfully persisted. + * @hide + */ + public boolean setNfcUnlockEnabled(boolean enabled) { + enforcePropertyEnabled(); + int currentUser = ActivityManager.getCurrentUser(); + + try { + sService.setNfcUnlockEnabled(currentUser, enabled); + return true; + } catch (RemoteException e) { + recoverService(); + if (sService == null) { + Log.e(TAG, "Failed to recover NfcUnlockSettingsService"); + return false; + } + + try { + sService.setNfcUnlockEnabled(currentUser, enabled); + return true; + } catch (RemoteException ee) { + Log.e(TAG, "Failed to reach NfcUnlockSettingsService", ee); + return false; + } + + } + } + + /** + * Returns a list of times (in millis since epoch) corresponding to when + * unlock tags were registered. + * + * @hide + */ + @Nullable + public long[] getTagRegistryTimes() { + enforcePropertyEnabled(); + int currentUser = ActivityManager.getCurrentUser(); + + try { + return sService.getTagRegistryTimes(currentUser); + } catch (RemoteException e) { + recoverService(); + if (sService == null) { + Log.e(TAG, "Failed to recover NfcUnlockSettingsService"); + return null; + } + + try { + return sService.getTagRegistryTimes(currentUser); + } catch (RemoteException ee) { + Log.e(TAG, "Failed to reach NfcUnlockSettingsService", ee); + return null; + } + } + } + + /** + * @hide + */ + public static boolean getPropertyEnabled() { + return SystemProperties.get(PROPERTY).equals("ON"); + } + + private void recoverService() { + NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext); + sService = adapter.getNfcUnlockSettingsService(); + } + + + private void enforcePropertyEnabled() { + if (!mEnabled) { + throw new UnsupportedOperationException("NFC Unlock property is not enabled"); + } + } +} diff --git a/core/java/android/nfc/Tag.java b/core/java/android/nfc/Tag.java index f2cd232..0d261d1 100644 --- a/core/java/android/nfc/Tag.java +++ b/core/java/android/nfc/Tag.java @@ -204,6 +204,14 @@ public final class Tag implements Parcelable { } /** + * For use by NfcService only. + * @hide + */ + public int[] getTechCodeList() { + return mTechList; + } + + /** * Get the Tag Identifier (if it has one). * <p>The tag identifier is a low level serial number, used for anti-collision * and identification. diff --git a/core/java/android/nfc/tech/Ndef.java b/core/java/android/nfc/tech/Ndef.java index f16dc3b..5852ce4 100644 --- a/core/java/android/nfc/tech/Ndef.java +++ b/core/java/android/nfc/tech/Ndef.java @@ -20,7 +20,6 @@ import android.nfc.ErrorCodes; import android.nfc.FormatException; import android.nfc.INfcTag; import android.nfc.NdefMessage; -import android.nfc.NfcAdapter; import android.nfc.Tag; import android.nfc.TagLostException; import android.os.Bundle; diff --git a/core/java/android/nfc/tech/NdefFormatable.java b/core/java/android/nfc/tech/NdefFormatable.java index ffa6a2b..4175cd0 100644 --- a/core/java/android/nfc/tech/NdefFormatable.java +++ b/core/java/android/nfc/tech/NdefFormatable.java @@ -20,7 +20,6 @@ import android.nfc.ErrorCodes; import android.nfc.FormatException; import android.nfc.INfcTag; import android.nfc.NdefMessage; -import android.nfc.NfcAdapter; import android.nfc.Tag; import android.nfc.TagLostException; import android.os.RemoteException; diff --git a/core/java/android/os/AsyncTask.java b/core/java/android/os/AsyncTask.java index 26e09b6..4f91d19 100644 --- a/core/java/android/os/AsyncTask.java +++ b/core/java/android/os/AsyncTask.java @@ -40,7 +40,7 @@ import java.util.concurrent.atomic.AtomicInteger; * and does not constitute a generic threading framework. AsyncTasks should ideally be * used for short operations (a few seconds at the most.) If you need to keep threads * running for long periods of time, it is highly recommended you use the various APIs - * provided by the <code>java.util.concurrent</code> pacakge such as {@link Executor}, + * provided by the <code>java.util.concurrent</code> package such as {@link Executor}, * {@link ThreadPoolExecutor} and {@link FutureTask}.</p> * * <p>An asynchronous task is defined by a computation that runs on a background thread and diff --git a/core/java/android/os/BatteryProperties.java b/core/java/android/os/BatteryProperties.java index 5df5214..8f5cf8b 100644 --- a/core/java/android/os/BatteryProperties.java +++ b/core/java/android/os/BatteryProperties.java @@ -15,9 +15,6 @@ package android.os; -import android.os.Parcel; -import android.os.Parcelable; - /** * {@hide} */ @@ -30,11 +27,25 @@ public class BatteryProperties implements Parcelable { public boolean batteryPresent; public int batteryLevel; public int batteryVoltage; - public int batteryCurrentNow; - public int batteryChargeCounter; public int batteryTemperature; public String batteryTechnology; + public BatteryProperties() { + } + + public void set(BatteryProperties other) { + chargerAcOnline = other.chargerAcOnline; + chargerUsbOnline = other.chargerUsbOnline; + chargerWirelessOnline = other.chargerWirelessOnline; + batteryStatus = other.batteryStatus; + batteryHealth = other.batteryHealth; + batteryPresent = other.batteryPresent; + batteryLevel = other.batteryLevel; + batteryVoltage = other.batteryVoltage; + batteryTemperature = other.batteryTemperature; + batteryTechnology = other.batteryTechnology; + } + /* * Parcel read/write code must be kept in sync with * frameworks/native/services/batteryservice/BatteryProperties.cpp @@ -49,8 +60,6 @@ public class BatteryProperties implements Parcelable { batteryPresent = p.readInt() == 1 ? true : false; batteryLevel = p.readInt(); batteryVoltage = p.readInt(); - batteryCurrentNow = p.readInt(); - batteryChargeCounter = p.readInt(); batteryTemperature = p.readInt(); batteryTechnology = p.readString(); } @@ -64,8 +73,6 @@ public class BatteryProperties implements Parcelable { p.writeInt(batteryPresent ? 1 : 0); p.writeInt(batteryLevel); p.writeInt(batteryVoltage); - p.writeInt(batteryCurrentNow); - p.writeInt(batteryChargeCounter); p.writeInt(batteryTemperature); p.writeString(batteryTechnology); } diff --git a/core/java/android/os/BatteryProperty.aidl b/core/java/android/os/BatteryProperty.aidl new file mode 100644 index 0000000..b3f2ec3 --- /dev/null +++ b/core/java/android/os/BatteryProperty.aidl @@ -0,0 +1,19 @@ +/* +** Copyright 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.os; + +parcelable BatteryProperty; diff --git a/core/java/android/os/BatteryProperty.java b/core/java/android/os/BatteryProperty.java new file mode 100644 index 0000000..346f5de --- /dev/null +++ b/core/java/android/os/BatteryProperty.java @@ -0,0 +1,70 @@ +/* Copyright 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.os; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * {@hide} + */ +public class BatteryProperty implements Parcelable { + /* + * Battery property identifiers. These must match the values in + * frameworks/native/include/batteryservice/BatteryService.h + */ + public static final int BATTERY_PROP_CHARGE_COUNTER = 1; + public static final int BATTERY_PROP_CURRENT_NOW = 2; + public static final int BATTERY_PROP_CURRENT_AVG = 3; + + public int valueInt; + + public BatteryProperty() { + valueInt = Integer.MIN_VALUE; + } + + /* + * Parcel read/write code must be kept in sync with + * frameworks/native/services/batteryservice/BatteryProperty.cpp + */ + + private BatteryProperty(Parcel p) { + readFromParcel(p); + } + + public void readFromParcel(Parcel p) { + valueInt = p.readInt(); + } + + public void writeToParcel(Parcel p, int flags) { + p.writeInt(valueInt); + } + + public static final Parcelable.Creator<BatteryProperty> CREATOR + = new Parcelable.Creator<BatteryProperty>() { + public BatteryProperty createFromParcel(Parcel p) { + return new BatteryProperty(p); + } + + public BatteryProperty[] newArray(int size) { + return new BatteryProperty[size]; + } + }; + + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index b1a9ea3..7db4ac2 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -24,13 +24,15 @@ import java.util.Formatter; import java.util.List; import java.util.Map; +import android.content.Context; import android.content.pm.ApplicationInfo; import android.telephony.SignalStrength; -import android.util.Log; +import android.text.format.DateFormat; import android.util.Printer; -import android.util.Slog; import android.util.SparseArray; import android.util.TimeUtils; +import com.android.internal.os.BatterySipper; +import com.android.internal.os.BatteryStatsHelper; /** * A class providing access to battery usage statistics, including information on @@ -160,6 +162,8 @@ public abstract class BatteryStats implements Parcelable { private static final String BATTERY_LEVEL_DATA = "lv"; private static final String WIFI_DATA = "wfl"; private static final String MISC_DATA = "m"; + private static final String GLOBAL_NETWORK_DATA = "gn"; + private static final String HISTORY_STRING_POOL = "hsp"; private static final String HISTORY_DATA = "h"; private static final String SCREEN_BRIGHTNESS_DATA = "br"; private static final String SIGNAL_STRENGTH_TIME_DATA = "sgt"; @@ -167,6 +171,12 @@ public abstract class BatteryStats implements Parcelable { private static final String SIGNAL_STRENGTH_COUNT_DATA = "sgc"; private static final String DATA_CONNECTION_TIME_DATA = "dct"; private static final String DATA_CONNECTION_COUNT_DATA = "dcc"; + private static final String WIFI_STATE_TIME_DATA = "wst"; + private static final String WIFI_STATE_COUNT_DATA = "wsc"; + private static final String BLUETOOTH_STATE_TIME_DATA = "bst"; + private static final String BLUETOOTH_STATE_COUNT_DATA = "bsc"; + private static final String POWER_USE_SUMMARY_DATA = "pws"; + private static final String POWER_USE_ITEM_DATA = "pwi"; private final StringBuilder mFormatBuilder = new StringBuilder(32); private final Formatter mFormatter = new Formatter(mFormatBuilder); @@ -207,11 +217,11 @@ public abstract class BatteryStats implements Parcelable { * Returns the total time in microseconds associated with this Timer for the * selected type of statistics. * - * @param batteryRealtime system realtime on battery in microseconds + * @param elapsedRealtimeUs current elapsed realtime of system in microseconds * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT * @return a time in microseconds */ - public abstract long getTotalTimeLocked(long batteryRealtime, int which); + public abstract long getTotalTimeLocked(long elapsedRealtimeUs, int which); /** * Temporary for debugging. @@ -269,30 +279,29 @@ public abstract class BatteryStats implements Parcelable { */ public abstract int getUid(); - public abstract void noteWifiRunningLocked(); - public abstract void noteWifiStoppedLocked(); - public abstract void noteFullWifiLockAcquiredLocked(); - public abstract void noteFullWifiLockReleasedLocked(); - public abstract void noteWifiScanStartedLocked(); - public abstract void noteWifiScanStoppedLocked(); - public abstract void noteWifiBatchedScanStartedLocked(int csph); - public abstract void noteWifiBatchedScanStoppedLocked(); - public abstract void noteWifiMulticastEnabledLocked(); - public abstract void noteWifiMulticastDisabledLocked(); - public abstract void noteAudioTurnedOnLocked(); - public abstract void noteAudioTurnedOffLocked(); - public abstract void noteVideoTurnedOnLocked(); - public abstract void noteVideoTurnedOffLocked(); - public abstract void noteActivityResumedLocked(); - public abstract void noteActivityPausedLocked(); - public abstract long getWifiRunningTime(long batteryRealtime, int which); - public abstract long getFullWifiLockTime(long batteryRealtime, int which); - public abstract long getWifiScanTime(long batteryRealtime, int which); - public abstract long getWifiBatchedScanTime(int csphBin, long batteryRealtime, int which); - public abstract long getWifiMulticastTime(long batteryRealtime, - int which); - public abstract long getAudioTurnedOnTime(long batteryRealtime, int which); - public abstract long getVideoTurnedOnTime(long batteryRealtime, int which); + public abstract void noteWifiRunningLocked(long elapsedRealtime); + public abstract void noteWifiStoppedLocked(long elapsedRealtime); + public abstract void noteFullWifiLockAcquiredLocked(long elapsedRealtime); + public abstract void noteFullWifiLockReleasedLocked(long elapsedRealtime); + public abstract void noteWifiScanStartedLocked(long elapsedRealtime); + public abstract void noteWifiScanStoppedLocked(long elapsedRealtime); + public abstract void noteWifiBatchedScanStartedLocked(int csph, long elapsedRealtime); + public abstract void noteWifiBatchedScanStoppedLocked(long elapsedRealtime); + public abstract void noteWifiMulticastEnabledLocked(long elapsedRealtime); + public abstract void noteWifiMulticastDisabledLocked(long elapsedRealtime); + public abstract void noteAudioTurnedOnLocked(long elapsedRealtime); + public abstract void noteAudioTurnedOffLocked(long elapsedRealtime); + public abstract void noteVideoTurnedOnLocked(long elapsedRealtime); + public abstract void noteVideoTurnedOffLocked(long elapsedRealtime); + public abstract void noteActivityResumedLocked(long elapsedRealtime); + public abstract void noteActivityPausedLocked(long elapsedRealtime); + public abstract long getWifiRunningTime(long elapsedRealtimeUs, int which); + public abstract long getFullWifiLockTime(long elapsedRealtimeUs, int which); + public abstract long getWifiScanTime(long elapsedRealtimeUs, int which); + public abstract long getWifiBatchedScanTime(int csphBin, long elapsedRealtimeUs, int which); + public abstract long getWifiMulticastTime(long elapsedRealtimeUs, int which); + public abstract long getAudioTurnedOnTime(long elapsedRealtimeUs, int which); + public abstract long getVideoTurnedOnTime(long elapsedRealtimeUs, int which); public abstract Timer getForegroundActivityTimer(); public abstract Timer getVibratorOnTimer(); @@ -314,7 +323,10 @@ public abstract class BatteryStats implements Parcelable { public abstract int getUserActivityCount(int type, int which); public abstract boolean hasNetworkActivity(); - public abstract long getNetworkActivityCount(int type, int which); + public abstract long getNetworkActivityBytes(int type, int which); + public abstract long getNetworkActivityPackets(int type, int which); + public abstract long getMobileRadioActiveTime(int which); + public abstract int getMobileRadioActiveCount(int which); public static abstract class Sensor { /* @@ -331,8 +343,9 @@ public abstract class BatteryStats implements Parcelable { } public class Pid { - public long mWakeSum; - public long mWakeStart; + public int mWakeNesting; + public long mWakeSumMs; + public long mWakeStartMs; } /** @@ -350,6 +363,11 @@ public abstract class BatteryStats implements Parcelable { } /** + * Returns true if this process is still active in the battery stats. + */ + public abstract boolean isActive(); + + /** * Returns the total time (in 1/100 sec) spent executing in user code. * * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. @@ -440,21 +458,76 @@ public abstract class BatteryStats implements Parcelable { } } + public final static class HistoryTag { + public String string; + public int uid; + + public int poolIdx; + + public void setTo(HistoryTag o) { + string = o.string; + uid = o.uid; + poolIdx = o.poolIdx; + } + + public void setTo(String _string, int _uid) { + string = _string; + uid = _uid; + poolIdx = -1; + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(string); + dest.writeInt(uid); + } + + public void readFromParcel(Parcel src) { + string = src.readString(); + uid = src.readInt(); + poolIdx = -1; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + HistoryTag that = (HistoryTag) o; + + if (uid != that.uid) return false; + if (!string.equals(that.string)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = string.hashCode(); + result = 31 * result + uid; + return result; + } + } + public final static class HistoryItem implements Parcelable { - static final String TAG = "HistoryItem"; - static final boolean DEBUG = false; - public HistoryItem next; public long time; - - public static final byte CMD_NULL = 0; - public static final byte CMD_UPDATE = 1; - public static final byte CMD_START = 2; - public static final byte CMD_OVERFLOW = 3; - + + public static final byte CMD_UPDATE = 0; // These can be written as deltas + public static final byte CMD_NULL = -1; + public static final byte CMD_START = 4; + public static final byte CMD_CURRENT_TIME = 5; + public static final byte CMD_OVERFLOW = 6; + public byte cmd = CMD_NULL; + /** + * Return whether the command code is a delta data update. + */ + public boolean isDeltaData() { + return cmd == CMD_UPDATE; + } + public byte batteryLevel; public byte batteryStatus; public byte batteryHealth; @@ -464,31 +537,32 @@ public abstract class BatteryStats implements Parcelable { public char batteryVoltage; // Constants from SCREEN_BRIGHTNESS_* - public static final int STATE_BRIGHTNESS_MASK = 0x0000000f; public static final int STATE_BRIGHTNESS_SHIFT = 0; + public static final int STATE_BRIGHTNESS_MASK = 0x7; // Constants from SIGNAL_STRENGTH_* - public static final int STATE_SIGNAL_STRENGTH_MASK = 0x000000f0; - public static final int STATE_SIGNAL_STRENGTH_SHIFT = 4; + public static final int STATE_SIGNAL_STRENGTH_SHIFT = 3; + public static final int STATE_SIGNAL_STRENGTH_MASK = 0x7 << STATE_SIGNAL_STRENGTH_SHIFT; // Constants from ServiceState.STATE_* - public static final int STATE_PHONE_STATE_MASK = 0x00000f00; - public static final int STATE_PHONE_STATE_SHIFT = 8; + public static final int STATE_PHONE_STATE_SHIFT = 6; + public static final int STATE_PHONE_STATE_MASK = 0x7 << STATE_PHONE_STATE_SHIFT; // Constants from DATA_CONNECTION_* - public static final int STATE_DATA_CONNECTION_MASK = 0x0000f000; - public static final int STATE_DATA_CONNECTION_SHIFT = 12; - + public static final int STATE_DATA_CONNECTION_SHIFT = 9; + public static final int STATE_DATA_CONNECTION_MASK = 0x1f << STATE_DATA_CONNECTION_SHIFT; + // These states always appear directly in the first int token // of a delta change; they should be ones that change relatively // frequently. - public static final int STATE_WAKE_LOCK_FLAG = 1<<30; - public static final int STATE_SENSOR_ON_FLAG = 1<<29; - public static final int STATE_GPS_ON_FLAG = 1<<28; - public static final int STATE_PHONE_SCANNING_FLAG = 1<<27; - public static final int STATE_WIFI_RUNNING_FLAG = 1<<26; - public static final int STATE_WIFI_FULL_LOCK_FLAG = 1<<25; - public static final int STATE_WIFI_SCAN_FLAG = 1<<24; - public static final int STATE_WIFI_MULTICAST_ON_FLAG = 1<<23; + public static final int STATE_WAKE_LOCK_FLAG = 1<<31; + public static final int STATE_SENSOR_ON_FLAG = 1<<30; + public static final int STATE_GPS_ON_FLAG = 1<<29; + public static final int STATE_WIFI_FULL_LOCK_FLAG = 1<<28; + public static final int STATE_WIFI_SCAN_FLAG = 1<<27; + public static final int STATE_WIFI_MULTICAST_ON_FLAG = 1<<26; + public static final int STATE_MOBILE_RADIO_ACTIVE_FLAG = 1<<25; + public static final int STATE_WIFI_RUNNING_FLAG = 1<<24; // These are on the lower bits used for the command; if they change // we need to write another int of data. + public static final int STATE_PHONE_SCANNING_FLAG = 1<<23; public static final int STATE_AUDIO_ON_FLAG = 1<<22; public static final int STATE_VIDEO_ON_FLAG = 1<<21; public static final int STATE_SCREEN_ON_FLAG = 1<<20; @@ -503,11 +577,58 @@ public abstract class BatteryStats implements Parcelable { public int states; + // The wake lock that was acquired at this point. + public HistoryTag wakelockTag; + + // Kernel wakeup reason at this point. + public HistoryTag wakeReasonTag; + + public static final int EVENT_FLAG_START = 0x8000; + public static final int EVENT_FLAG_FINISH = 0x4000; + + // No event in this item. + public static final int EVENT_NONE = 0x0000; + // Event is about a process that is running. + public static final int EVENT_PROC = 0x0001; + // Event is about an application package that is in the foreground. + public static final int EVENT_FOREGROUND = 0x0002; + // Event is about an application package that is at the top of the screen. + public static final int EVENT_TOP = 0x0003; + // Event is about an application package that is at the top of the screen. + public static final int EVENT_SYNC = 0x0004; + // Number of event types. + public static final int EVENT_COUNT = 0x0005; + + public static final int EVENT_PROC_START = EVENT_PROC | EVENT_FLAG_START; + public static final int EVENT_PROC_FINISH = EVENT_PROC | EVENT_FLAG_FINISH; + public static final int EVENT_FOREGROUND_START = EVENT_FOREGROUND | EVENT_FLAG_START; + public static final int EVENT_FOREGROUND_FINISH = EVENT_FOREGROUND | EVENT_FLAG_FINISH; + public static final int EVENT_TOP_START = EVENT_TOP | EVENT_FLAG_START; + public static final int EVENT_TOP_FINISH = EVENT_TOP | EVENT_FLAG_FINISH; + public static final int EVENT_SYNC_START = EVENT_SYNC | EVENT_FLAG_START; + public static final int EVENT_SYNC_FINISH = EVENT_SYNC | EVENT_FLAG_FINISH; + + // For CMD_EVENT. + public int eventCode; + public HistoryTag eventTag; + + // Only set for CMD_CURRENT_TIME. + public long currentTime; + + // Meta-data when reading. + public int numReadInts; + + // Pre-allocated objects. + public final HistoryTag localWakelockTag = new HistoryTag(); + public final HistoryTag localWakeReasonTag = new HistoryTag(); + public final HistoryTag localEventTag = new HistoryTag(); + public HistoryItem() { } public HistoryItem(long time, Parcel src) { this.time = time; + numReadInts = 2; readFromParcel(src); } @@ -521,168 +642,68 @@ public abstract class BatteryStats implements Parcelable { | ((((int)batteryLevel)<<8)&0xff00) | ((((int)batteryStatus)<<16)&0xf0000) | ((((int)batteryHealth)<<20)&0xf00000) - | ((((int)batteryPlugType)<<24)&0xf000000); + | ((((int)batteryPlugType)<<24)&0xf000000) + | (wakelockTag != null ? 0x10000000 : 0) + | (wakeReasonTag != null ? 0x20000000 : 0) + | (eventCode != EVENT_NONE ? 0x40000000 : 0); dest.writeInt(bat); bat = (((int)batteryTemperature)&0xffff) | ((((int)batteryVoltage)<<16)&0xffff0000); dest.writeInt(bat); dest.writeInt(states); + if (wakelockTag != null) { + wakelockTag.writeToParcel(dest, flags); + } + if (wakeReasonTag != null) { + wakeReasonTag.writeToParcel(dest, flags); + } + if (eventCode != EVENT_NONE) { + dest.writeInt(eventCode); + eventTag.writeToParcel(dest, flags); + } + if (cmd == CMD_CURRENT_TIME) { + dest.writeLong(currentTime); + } } - private void readFromParcel(Parcel src) { + public void readFromParcel(Parcel src) { + int start = src.dataPosition(); int bat = src.readInt(); cmd = (byte)(bat&0xff); batteryLevel = (byte)((bat>>8)&0xff); batteryStatus = (byte)((bat>>16)&0xf); batteryHealth = (byte)((bat>>20)&0xf); batteryPlugType = (byte)((bat>>24)&0xf); - bat = src.readInt(); - batteryTemperature = (short)(bat&0xffff); - batteryVoltage = (char)((bat>>16)&0xffff); + int bat2 = src.readInt(); + batteryTemperature = (short)(bat2&0xffff); + batteryVoltage = (char)((bat2>>16)&0xffff); states = src.readInt(); - } - - // Part of initial delta int that specifies the time delta. - static final int DELTA_TIME_MASK = 0x3ffff; - static final int DELTA_TIME_ABS = 0x3fffd; // Following is an entire abs update. - static final int DELTA_TIME_INT = 0x3fffe; // The delta is a following int - static final int DELTA_TIME_LONG = 0x3ffff; // The delta is a following long - // Part of initial delta int holding the command code. - static final int DELTA_CMD_MASK = 0x3; - static final int DELTA_CMD_SHIFT = 18; - // Flag in delta int: a new battery level int follows. - static final int DELTA_BATTERY_LEVEL_FLAG = 1<<20; - // Flag in delta int: a new full state and battery status int follows. - static final int DELTA_STATE_FLAG = 1<<21; - static final int DELTA_STATE_MASK = 0xffc00000; - - public void writeDelta(Parcel dest, HistoryItem last) { - if (last == null || last.cmd != CMD_UPDATE) { - dest.writeInt(DELTA_TIME_ABS); - writeToParcel(dest, 0); - return; - } - - final long deltaTime = time - last.time; - final int lastBatteryLevelInt = last.buildBatteryLevelInt(); - final int lastStateInt = last.buildStateInt(); - - int deltaTimeToken; - if (deltaTime < 0 || deltaTime > Integer.MAX_VALUE) { - deltaTimeToken = DELTA_TIME_LONG; - } else if (deltaTime >= DELTA_TIME_ABS) { - deltaTimeToken = DELTA_TIME_INT; + if ((bat&0x10000000) != 0) { + wakelockTag = localWakelockTag; + wakelockTag.readFromParcel(src); } else { - deltaTimeToken = (int)deltaTime; - } - int firstToken = deltaTimeToken - | (cmd<<DELTA_CMD_SHIFT) - | (states&DELTA_STATE_MASK); - final int batteryLevelInt = buildBatteryLevelInt(); - final boolean batteryLevelIntChanged = batteryLevelInt != lastBatteryLevelInt; - if (batteryLevelIntChanged) { - firstToken |= DELTA_BATTERY_LEVEL_FLAG; - } - final int stateInt = buildStateInt(); - final boolean stateIntChanged = stateInt != lastStateInt; - if (stateIntChanged) { - firstToken |= DELTA_STATE_FLAG; - } - dest.writeInt(firstToken); - if (DEBUG) Slog.i(TAG, "WRITE DELTA: firstToken=0x" + Integer.toHexString(firstToken) - + " deltaTime=" + deltaTime); - - if (deltaTimeToken >= DELTA_TIME_INT) { - if (deltaTimeToken == DELTA_TIME_INT) { - if (DEBUG) Slog.i(TAG, "WRITE DELTA: int deltaTime=" + (int)deltaTime); - dest.writeInt((int)deltaTime); - } else { - if (DEBUG) Slog.i(TAG, "WRITE DELTA: long deltaTime=" + deltaTime); - dest.writeLong(deltaTime); - } - } - if (batteryLevelIntChanged) { - dest.writeInt(batteryLevelInt); - if (DEBUG) Slog.i(TAG, "WRITE DELTA: batteryToken=0x" - + Integer.toHexString(batteryLevelInt) - + " batteryLevel=" + batteryLevel - + " batteryTemp=" + batteryTemperature - + " batteryVolt=" + (int)batteryVoltage); + wakelockTag = null; } - if (stateIntChanged) { - dest.writeInt(stateInt); - if (DEBUG) Slog.i(TAG, "WRITE DELTA: stateToken=0x" - + Integer.toHexString(stateInt) - + " batteryStatus=" + batteryStatus - + " batteryHealth=" + batteryHealth - + " batteryPlugType=" + batteryPlugType - + " states=0x" + Integer.toHexString(states)); - } - } - - private int buildBatteryLevelInt() { - return ((((int)batteryLevel)<<25)&0xfe000000) - | ((((int)batteryTemperature)<<14)&0x01ffc000) - | (((int)batteryVoltage)&0x00003fff); - } - - private int buildStateInt() { - return ((((int)batteryStatus)<<28)&0xf0000000) - | ((((int)batteryHealth)<<24)&0x0f000000) - | ((((int)batteryPlugType)<<22)&0x00c00000) - | (states&(~DELTA_STATE_MASK)); - } - - public void readDelta(Parcel src) { - int firstToken = src.readInt(); - int deltaTimeToken = firstToken&DELTA_TIME_MASK; - cmd = (byte)((firstToken>>DELTA_CMD_SHIFT)&DELTA_CMD_MASK); - if (DEBUG) Slog.i(TAG, "READ DELTA: firstToken=0x" + Integer.toHexString(firstToken) - + " deltaTimeToken=" + deltaTimeToken); - - if (deltaTimeToken < DELTA_TIME_ABS) { - time += deltaTimeToken; - } else if (deltaTimeToken == DELTA_TIME_ABS) { - time = src.readLong(); - readFromParcel(src); - return; - } else if (deltaTimeToken == DELTA_TIME_INT) { - int delta = src.readInt(); - time += delta; - if (DEBUG) Slog.i(TAG, "READ DELTA: time delta=" + delta + " new time=" + time); + if ((bat&0x20000000) != 0) { + wakeReasonTag = localWakeReasonTag; + wakeReasonTag.readFromParcel(src); } else { - long delta = src.readLong(); - if (DEBUG) Slog.i(TAG, "READ DELTA: time delta=" + delta + " new time=" + time); - time += delta; + wakeReasonTag = null; } - - if ((firstToken&DELTA_BATTERY_LEVEL_FLAG) != 0) { - int batteryLevelInt = src.readInt(); - batteryLevel = (byte)((batteryLevelInt>>25)&0x7f); - batteryTemperature = (short)((batteryLevelInt<<7)>>21); - batteryVoltage = (char)(batteryLevelInt&0x3fff); - if (DEBUG) Slog.i(TAG, "READ DELTA: batteryToken=0x" - + Integer.toHexString(batteryLevelInt) - + " batteryLevel=" + batteryLevel - + " batteryTemp=" + batteryTemperature - + " batteryVolt=" + (int)batteryVoltage); + if ((bat&0x40000000) != 0) { + eventCode = src.readInt(); + eventTag = localEventTag; + eventTag.readFromParcel(src); + } else { + eventCode = EVENT_NONE; + eventTag = null; } - - if ((firstToken&DELTA_STATE_FLAG) != 0) { - int stateInt = src.readInt(); - states = (firstToken&DELTA_STATE_MASK) | (stateInt&(~DELTA_STATE_MASK)); - batteryStatus = (byte)((stateInt>>28)&0xf); - batteryHealth = (byte)((stateInt>>24)&0xf); - batteryPlugType = (byte)((stateInt>>22)&0x3); - if (DEBUG) Slog.i(TAG, "READ DELTA: stateToken=0x" - + Integer.toHexString(stateInt) - + " batteryStatus=" + batteryStatus - + " batteryHealth=" + batteryHealth - + " batteryPlugType=" + batteryPlugType - + " states=0x" + Integer.toHexString(states)); + if (cmd == CMD_CURRENT_TIME) { + currentTime = src.readLong(); } else { - states = (firstToken&DELTA_STATE_MASK) | (states&(~DELTA_STATE_MASK)); + currentTime = 0; } + numReadInts += (src.dataPosition()-start)/4; } public void clear() { @@ -695,23 +716,25 @@ public abstract class BatteryStats implements Parcelable { batteryTemperature = 0; batteryVoltage = 0; states = 0; + wakelockTag = null; + wakeReasonTag = null; + eventCode = EVENT_NONE; + eventTag = null; } public void setTo(HistoryItem o) { time = o.time; cmd = o.cmd; - batteryLevel = o.batteryLevel; - batteryStatus = o.batteryStatus; - batteryHealth = o.batteryHealth; - batteryPlugType = o.batteryPlugType; - batteryTemperature = o.batteryTemperature; - batteryVoltage = o.batteryVoltage; - states = o.states; + setToCommon(o); } public void setTo(long time, byte cmd, HistoryItem o) { this.time = time; this.cmd = cmd; + setToCommon(o); + } + + private void setToCommon(HistoryItem o) { batteryLevel = o.batteryLevel; batteryStatus = o.batteryStatus; batteryHealth = o.batteryHealth; @@ -719,16 +742,68 @@ public abstract class BatteryStats implements Parcelable { batteryTemperature = o.batteryTemperature; batteryVoltage = o.batteryVoltage; states = o.states; + if (o.wakelockTag != null) { + wakelockTag = localWakelockTag; + wakelockTag.setTo(o.wakelockTag); + } else { + wakelockTag = null; + } + if (o.wakeReasonTag != null) { + wakeReasonTag = localWakeReasonTag; + wakeReasonTag.setTo(o.wakeReasonTag); + } else { + wakeReasonTag = null; + } + eventCode = o.eventCode; + if (o.eventTag != null) { + eventTag = localEventTag; + eventTag.setTo(o.eventTag); + } else { + eventTag = null; + } + currentTime = o.currentTime; } - public boolean same(HistoryItem o) { + public boolean sameNonEvent(HistoryItem o) { return batteryLevel == o.batteryLevel && batteryStatus == o.batteryStatus && batteryHealth == o.batteryHealth && batteryPlugType == o.batteryPlugType && batteryTemperature == o.batteryTemperature && batteryVoltage == o.batteryVoltage - && states == o.states; + && states == o.states + && currentTime == o.currentTime; + } + + public boolean same(HistoryItem o) { + if (!sameNonEvent(o) || eventCode != o.eventCode) { + return false; + } + if (wakelockTag != o.wakelockTag) { + if (wakelockTag == null || o.wakelockTag == null) { + return false; + } + if (!wakelockTag.equals(o.wakelockTag)) { + return false; + } + } + if (wakeReasonTag != o.wakeReasonTag) { + if (wakeReasonTag == null || o.wakeReasonTag == null) { + return false; + } + if (!wakeReasonTag.equals(o.wakeReasonTag)) { + return false; + } + } + if (eventTag != o.eventTag) { + if (eventTag == null || o.eventTag == null) { + return false; + } + if (!eventTag.equals(o.eventTag)) { + return false; + } + } + return true; } } @@ -736,25 +811,44 @@ public abstract class BatteryStats implements Parcelable { public final int mask; public final int shift; public final String name; + public final String shortName; public final String[] values; + public final String[] shortValues; - public BitDescription(int mask, String name) { + public BitDescription(int mask, String name, String shortName) { this.mask = mask; this.shift = -1; this.name = name; + this.shortName = shortName; this.values = null; + this.shortValues = null; } - public BitDescription(int mask, int shift, String name, String[] values) { + public BitDescription(int mask, int shift, String name, String shortName, + String[] values, String[] shortValues) { this.mask = mask; this.shift = shift; this.name = name; + this.shortName = shortName; this.values = values; + this.shortValues = shortValues; } } + public abstract int getHistoryTotalSize(); + + public abstract int getHistoryUsedSize(); + public abstract boolean startIteratingHistoryLocked(); + public abstract int getHistoryStringPoolSize(); + + public abstract int getHistoryStringPoolBytes(); + + public abstract String getHistoryTagPoolString(int index); + + public abstract int getHistoryTagPoolUid(int index); + public abstract boolean getNextHistoryLocked(HistoryItem out); public abstract void finishIteratingHistoryLocked(); @@ -781,8 +875,15 @@ public abstract class BatteryStats implements Parcelable { * * {@hide} */ - public abstract long getScreenOnTime(long batteryRealtime, int which); + public abstract long getScreenOnTime(long elapsedRealtimeUs, int which); + /** + * Returns the number of times the screen was turned on. + * + * {@hide} + */ + public abstract int getScreenOnCount(int which); + public static final int SCREEN_BRIGHTNESS_DARK = 0; public static final int SCREEN_BRIGHTNESS_DIM = 1; public static final int SCREEN_BRIGHTNESS_MEDIUM = 2; @@ -793,6 +894,10 @@ public abstract class BatteryStats implements Parcelable { "dark", "dim", "medium", "light", "bright" }; + static final String[] SCREEN_BRIGHTNESS_SHORT_NAMES = { + "0", "1", "2", "3", "4" + }; + public static final int NUM_SCREEN_BRIGHTNESS_BINS = 5; /** @@ -802,7 +907,7 @@ public abstract class BatteryStats implements Parcelable { * {@hide} */ public abstract long getScreenBrightnessTime(int brightnessBin, - long batteryRealtime, int which); + long elapsedRealtimeUs, int which); public abstract int getInputEventCount(int which); @@ -812,16 +917,23 @@ public abstract class BatteryStats implements Parcelable { * * {@hide} */ - public abstract long getPhoneOnTime(long batteryRealtime, int which); + public abstract long getPhoneOnTime(long elapsedRealtimeUs, int which); /** + * Returns the number of times a phone call was activated. + * + * {@hide} + */ + public abstract int getPhoneOnCount(int which); + + /** * Returns the time in microseconds that the phone has been running with * the given signal strength. * * {@hide} */ public abstract long getPhoneSignalStrengthTime(int strengthBin, - long batteryRealtime, int which); + long elapsedRealtimeUs, int which); /** * Returns the time in microseconds that the phone has been trying to @@ -830,7 +942,7 @@ public abstract class BatteryStats implements Parcelable { * {@hide} */ public abstract long getPhoneSignalScanningTime( - long batteryRealtime, int which); + long elapsedRealtimeUs, int which); /** * Returns the number of times the phone has entered the given signal strength. @@ -839,6 +951,37 @@ public abstract class BatteryStats implements Parcelable { */ public abstract int getPhoneSignalStrengthCount(int strengthBin, int which); + /** + * Returns the time in microseconds that the mobile network has been active + * (in a high power state). + * + * {@hide} + */ + public abstract long getMobileRadioActiveTime(long elapsedRealtimeUs, int which); + + /** + * Returns the number of times that the mobile network has transitioned to the + * active state. + * + * {@hide} + */ + public abstract int getMobileRadioActiveCount(int which); + + /** + * Returns the time in microseconds that the mobile network has been active + * (in a high power state) but not being able to blame on an app. + * + * {@hide} + */ + public abstract long getMobileRadioActiveUnknownTime(int which); + + /** + * Return count of number of times radio was up that could not be blamed on apps. + * + * {@hide} + */ + public abstract int getMobileRadioActiveUnknownCount(int which); + public static final int DATA_CONNECTION_NONE = 0; public static final int DATA_CONNECTION_GPRS = 1; public static final int DATA_CONNECTION_EDGE = 2; @@ -872,7 +1015,7 @@ public abstract class BatteryStats implements Parcelable { * {@hide} */ public abstract long getPhoneDataConnectionTime(int dataType, - long batteryRealtime, int which); + long elapsedRealtimeUs, int which); /** * Returns the number of times the phone has entered the given data @@ -884,33 +1027,45 @@ public abstract class BatteryStats implements Parcelable { public static final BitDescription[] HISTORY_STATE_DESCRIPTIONS = new BitDescription[] { - new BitDescription(HistoryItem.STATE_BATTERY_PLUGGED_FLAG, "plugged"), - new BitDescription(HistoryItem.STATE_SCREEN_ON_FLAG, "screen"), - new BitDescription(HistoryItem.STATE_GPS_ON_FLAG, "gps"), - new BitDescription(HistoryItem.STATE_PHONE_IN_CALL_FLAG, "phone_in_call"), - new BitDescription(HistoryItem.STATE_PHONE_SCANNING_FLAG, "phone_scanning"), - new BitDescription(HistoryItem.STATE_WIFI_ON_FLAG, "wifi"), - new BitDescription(HistoryItem.STATE_WIFI_RUNNING_FLAG, "wifi_running"), - new BitDescription(HistoryItem.STATE_WIFI_FULL_LOCK_FLAG, "wifi_full_lock"), - new BitDescription(HistoryItem.STATE_WIFI_SCAN_FLAG, "wifi_scan"), - new BitDescription(HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG, "wifi_multicast"), - new BitDescription(HistoryItem.STATE_BLUETOOTH_ON_FLAG, "bluetooth"), - new BitDescription(HistoryItem.STATE_AUDIO_ON_FLAG, "audio"), - new BitDescription(HistoryItem.STATE_VIDEO_ON_FLAG, "video"), - new BitDescription(HistoryItem.STATE_WAKE_LOCK_FLAG, "wake_lock"), - new BitDescription(HistoryItem.STATE_SENSOR_ON_FLAG, "sensor"), - new BitDescription(HistoryItem.STATE_BRIGHTNESS_MASK, - HistoryItem.STATE_BRIGHTNESS_SHIFT, "brightness", - SCREEN_BRIGHTNESS_NAMES), - new BitDescription(HistoryItem.STATE_SIGNAL_STRENGTH_MASK, - HistoryItem.STATE_SIGNAL_STRENGTH_SHIFT, "signal_strength", - SignalStrength.SIGNAL_STRENGTH_NAMES), - new BitDescription(HistoryItem.STATE_PHONE_STATE_MASK, - HistoryItem.STATE_PHONE_STATE_SHIFT, "phone_state", - new String[] {"in", "out", "emergency", "off"}), + new BitDescription(HistoryItem.STATE_WAKE_LOCK_FLAG, "wake_lock", "w"), + new BitDescription(HistoryItem.STATE_SENSOR_ON_FLAG, "sensor", "s"), + new BitDescription(HistoryItem.STATE_GPS_ON_FLAG, "gps", "g"), + new BitDescription(HistoryItem.STATE_WIFI_FULL_LOCK_FLAG, "wifi_full_lock", "Wl"), + new BitDescription(HistoryItem.STATE_WIFI_SCAN_FLAG, "wifi_scan", "Ws"), + new BitDescription(HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG, "wifi_multicast", "Wm"), + new BitDescription(HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG, "mobile_radio", "Pr"), + new BitDescription(HistoryItem.STATE_WIFI_RUNNING_FLAG, "wifi_running", "Wr"), + new BitDescription(HistoryItem.STATE_PHONE_SCANNING_FLAG, "phone_scanning", "Psc"), + new BitDescription(HistoryItem.STATE_AUDIO_ON_FLAG, "audio", "a"), + new BitDescription(HistoryItem.STATE_VIDEO_ON_FLAG, "video", "v"), + new BitDescription(HistoryItem.STATE_SCREEN_ON_FLAG, "screen", "S"), + new BitDescription(HistoryItem.STATE_BATTERY_PLUGGED_FLAG, "plugged", "BP"), + new BitDescription(HistoryItem.STATE_PHONE_IN_CALL_FLAG, "phone_in_call", "Pcl"), + new BitDescription(HistoryItem.STATE_WIFI_ON_FLAG, "wifi", "W"), + new BitDescription(HistoryItem.STATE_BLUETOOTH_ON_FLAG, "bluetooth", "b"), new BitDescription(HistoryItem.STATE_DATA_CONNECTION_MASK, - HistoryItem.STATE_DATA_CONNECTION_SHIFT, "data_conn", - DATA_CONNECTION_NAMES), + HistoryItem.STATE_DATA_CONNECTION_SHIFT, "data_conn", "Pcn", + DATA_CONNECTION_NAMES, DATA_CONNECTION_NAMES), + new BitDescription(HistoryItem.STATE_PHONE_STATE_MASK, + HistoryItem.STATE_PHONE_STATE_SHIFT, "phone_state", "Pst", + new String[] {"in", "out", "emergency", "off"}, + new String[] {"in", "out", "em", "off"}), + new BitDescription(HistoryItem.STATE_SIGNAL_STRENGTH_MASK, + HistoryItem.STATE_SIGNAL_STRENGTH_SHIFT, "signal_strength", "Pss", + SignalStrength.SIGNAL_STRENGTH_NAMES, new String[] { + "0", "1", "2", "3", "4" + }), + new BitDescription(HistoryItem.STATE_BRIGHTNESS_MASK, + HistoryItem.STATE_BRIGHTNESS_SHIFT, "brightness", "Sb", + SCREEN_BRIGHTNESS_NAMES, SCREEN_BRIGHTNESS_SHORT_NAMES), + }; + + public static final String[] HISTORY_EVENT_NAMES = new String[] { + "null", "proc", "fg", "top", "sync" + }; + + public static final String[] HISTORY_EVENT_CHECKIN_NAMES = new String[] { + "Enl", "Epr", "Efg", "Etp", "Esy" }; /** @@ -919,7 +1074,7 @@ public abstract class BatteryStats implements Parcelable { * * {@hide} */ - public abstract long getWifiOnTime(long batteryRealtime, int which); + public abstract long getWifiOnTime(long elapsedRealtimeUs, int which); /** * Returns the time in microseconds that wifi has been on and the driver has @@ -927,7 +1082,38 @@ public abstract class BatteryStats implements Parcelable { * * {@hide} */ - public abstract long getGlobalWifiRunningTime(long batteryRealtime, int which); + public abstract long getGlobalWifiRunningTime(long elapsedRealtimeUs, int which); + + public static final int WIFI_STATE_OFF = 0; + public static final int WIFI_STATE_OFF_SCANNING = 1; + public static final int WIFI_STATE_ON_NO_NETWORKS = 2; + public static final int WIFI_STATE_ON_DISCONNECTED = 3; + public static final int WIFI_STATE_ON_CONNECTED_STA = 4; + public static final int WIFI_STATE_ON_CONNECTED_P2P = 5; + public static final int WIFI_STATE_ON_CONNECTED_STA_P2P = 6; + public static final int WIFI_STATE_SOFT_AP = 7; + + static final String[] WIFI_STATE_NAMES = { + "off", "scanning", "no_net", "disconn", + "sta", "p2p", "sta_p2p", "soft_ap" + }; + + public static final int NUM_WIFI_STATES = WIFI_STATE_SOFT_AP+1; + + /** + * Returns the time in microseconds that WiFi has been running in the given state. + * + * {@hide} + */ + public abstract long getWifiStateTime(int wifiState, + long elapsedRealtimeUs, int which); + + /** + * Returns the number of times that WiFi has entered the given state. + * + * {@hide} + */ + public abstract int getWifiStateCount(int wifiState, int which); /** * Returns the time in microseconds that bluetooth has been on while the device was @@ -935,16 +1121,51 @@ public abstract class BatteryStats implements Parcelable { * * {@hide} */ - public abstract long getBluetoothOnTime(long batteryRealtime, int which); + public abstract long getBluetoothOnTime(long elapsedRealtimeUs, int which); - public static final int NETWORK_MOBILE_RX_BYTES = 0; - public static final int NETWORK_MOBILE_TX_BYTES = 1; - public static final int NETWORK_WIFI_RX_BYTES = 2; - public static final int NETWORK_WIFI_TX_BYTES = 3; + public abstract int getBluetoothPingCount(); + + public static final int BLUETOOTH_STATE_INACTIVE = 0; + public static final int BLUETOOTH_STATE_LOW = 1; + public static final int BLUETOOTH_STATE_MEDIUM = 2; + public static final int BLUETOOTH_STATE_HIGH = 3; + + static final String[] BLUETOOTH_STATE_NAMES = { + "inactive", "low", "med", "high" + }; - public static final int NUM_NETWORK_ACTIVITY_TYPES = NETWORK_WIFI_TX_BYTES + 1; + public static final int NUM_BLUETOOTH_STATES = BLUETOOTH_STATE_HIGH +1; - public abstract long getNetworkActivityCount(int type, int which); + /** + * Returns the time in microseconds that Bluetooth has been running in the + * given active state. + * + * {@hide} + */ + public abstract long getBluetoothStateTime(int bluetoothState, + long elapsedRealtimeUs, int which); + + /** + * Returns the number of times that Bluetooth has entered the given active state. + * + * {@hide} + */ + public abstract int getBluetoothStateCount(int bluetoothState, int which); + + public static final int NETWORK_MOBILE_RX_DATA = 0; + public static final int NETWORK_MOBILE_TX_DATA = 1; + public static final int NETWORK_WIFI_RX_DATA = 2; + public static final int NETWORK_WIFI_TX_DATA = 3; + + public static final int NUM_NETWORK_ACTIVITY_TYPES = NETWORK_WIFI_TX_DATA + 1; + + public abstract long getNetworkActivityBytes(int type, int which); + public abstract long getNetworkActivityPackets(int type, int which); + + /** + * Return the wall clock time when battery stats data collection started. + */ + public abstract long getStartClockTime(); /** * Return whether we are currently running on battery. @@ -964,19 +1185,6 @@ public abstract class BatteryStats implements Parcelable { public abstract long getBatteryUptime(long curTime); /** - * @deprecated use getRadioDataUptime - */ - public long getRadioDataUptimeMs() { - return getRadioDataUptime() / 1000; - } - - /** - * Returns the time that the radio was on for data transfers. - * @return the uptime in microseconds while unplugged - */ - public abstract long getRadioDataUptime(); - - /** * Returns the current battery realtime in microseconds. * * @param curTime the amount of elapsed realtime in microseconds. @@ -1048,6 +1256,22 @@ public abstract class BatteryStats implements Parcelable { public abstract long computeBatteryRealtime(long curTime, int which); /** + * Returns the total, last, or current battery screen off uptime in microseconds. + * + * @param curTime the elapsed realtime in microseconds. + * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. + */ + public abstract long computeBatteryScreenOffUptime(long curTime, int which); + + /** + * Returns the total, last, or current battery screen off realtime in microseconds. + * + * @param curTime the current elapsed realtime in microseconds. + * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. + */ + public abstract long computeBatteryScreenOffRealtime(long curTime, int which); + + /** * Returns the total, last, or current uptime in microseconds. * * @param curTime the current elapsed realtime in microseconds. @@ -1068,6 +1292,8 @@ public abstract class BatteryStats implements Parcelable { /** Returns the number of different speeds that the CPU can run at */ public abstract int getCpuSpeedSteps(); + public abstract void writeToParcelWithoutUids(Parcel out, int flags); + private final static void formatTimeRaw(StringBuilder out, long seconds) { long days = seconds / (60 * 60 * 24); if (days != 0) { @@ -1096,23 +1322,30 @@ public abstract class BatteryStats implements Parcelable { } } - private final static void formatTime(StringBuilder sb, long time) { + public final static void formatTime(StringBuilder sb, long time) { long sec = time / 100; formatTimeRaw(sb, sec); sb.append((time - (sec * 100)) * 10); sb.append("ms "); } - private final static void formatTimeMs(StringBuilder sb, long time) { + public final static void formatTimeMs(StringBuilder sb, long time) { long sec = time / 1000; formatTimeRaw(sb, sec); sb.append(time - (sec * 1000)); sb.append("ms "); } - private final String formatRatioLocked(long num, long den) { + public final static void formatTimeMsNoSpace(StringBuilder sb, long time) { + long sec = time / 1000; + formatTimeRaw(sb, sec); + sb.append(time - (sec * 1000)); + sb.append("ms"); + } + + public final String formatRatioLocked(long num, long den) { if (den == 0L) { - return "---%"; + return "--%"; } float perc = ((float)num) / ((float)den) * 100; mFormatBuilder.setLength(0); @@ -1120,7 +1353,7 @@ public abstract class BatteryStats implements Parcelable { return mFormatBuilder.toString(); } - private final String formatBytesLocked(long bytes) { + final String formatBytesLocked(long bytes) { mFormatBuilder.setLength(0); if (bytes < BYTES_PER_KB) { @@ -1137,10 +1370,10 @@ public abstract class BatteryStats implements Parcelable { } } - private static long computeWakeLock(Timer timer, long batteryRealtime, int which) { + private static long computeWakeLock(Timer timer, long elapsedRealtimeUs, int which) { if (timer != null) { // Convert from microseconds to milliseconds with rounding - long totalTimeMicros = timer.getTotalTimeLocked(batteryRealtime, which); + long totalTimeMicros = timer.getTotalTimeLocked(elapsedRealtimeUs, which); long totalTimeMillis = (totalTimeMicros + 500) / 1000; return totalTimeMillis; } @@ -1151,17 +1384,17 @@ public abstract class BatteryStats implements Parcelable { * * @param sb a StringBuilder object. * @param timer a Timer object contining the wakelock times. - * @param batteryRealtime the current on-battery time in microseconds. + * @param elapsedRealtimeUs the current on-battery time in microseconds. * @param name the name of the wakelock. * @param which which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. * @param linePrefix a String to be prepended to each line of output. * @return the line prefix */ private static final String printWakeLock(StringBuilder sb, Timer timer, - long batteryRealtime, String name, int which, String linePrefix) { + long elapsedRealtimeUs, String name, int which, String linePrefix) { if (timer != null) { - long totalTimeMillis = computeWakeLock(timer, batteryRealtime, which); + long totalTimeMillis = computeWakeLock(timer, elapsedRealtimeUs, which); int count = timer.getCountLocked(which); if (totalTimeMillis != 0) { @@ -1185,18 +1418,18 @@ public abstract class BatteryStats implements Parcelable { * * @param sb a StringBuilder object. * @param timer a Timer object contining the wakelock times. - * @param now the current time in microseconds. + * @param elapsedRealtimeUs the current time in microseconds. * @param name the name of the wakelock. * @param which which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. * @param linePrefix a String to be prepended to each line of output. * @return the line prefix */ - private static final String printWakeLockCheckin(StringBuilder sb, Timer timer, long now, - String name, int which, String linePrefix) { + private static final String printWakeLockCheckin(StringBuilder sb, Timer timer, + long elapsedRealtimeUs, String name, int which, String linePrefix) { long totalTimeMicros = 0; int count = 0; if (timer != null) { - totalTimeMicros = timer.getTotalTimeLocked(now, which); + totalTimeMicros = timer.getTotalTimeLocked(elapsedRealtimeUs, which); count = timer.getCountLocked(which); } sb.append(linePrefix); @@ -1234,20 +1467,22 @@ public abstract class BatteryStats implements Parcelable { * * NOTE: all times are expressed in 'ms'. */ - public final void dumpCheckinLocked(PrintWriter pw, int which, int reqUid) { + public final void dumpCheckinLocked(Context context, PrintWriter pw, int which, int reqUid) { final long rawUptime = SystemClock.uptimeMillis() * 1000; final long rawRealtime = SystemClock.elapsedRealtime() * 1000; final long batteryUptime = getBatteryUptime(rawUptime); - final long batteryRealtime = getBatteryRealtime(rawRealtime); final long whichBatteryUptime = computeBatteryUptime(rawUptime, which); final long whichBatteryRealtime = computeBatteryRealtime(rawRealtime, which); + final long whichBatteryScreenOffUptime = computeBatteryScreenOffUptime(rawUptime, which); + final long whichBatteryScreenOffRealtime = computeBatteryScreenOffRealtime(rawRealtime, + which); final long totalRealtime = computeRealtime(rawRealtime, which); final long totalUptime = computeUptime(rawUptime, which); - final long screenOnTime = getScreenOnTime(batteryRealtime, which); - final long phoneOnTime = getPhoneOnTime(batteryRealtime, which); - final long wifiOnTime = getWifiOnTime(batteryRealtime, which); - final long wifiRunningTime = getGlobalWifiRunningTime(batteryRealtime, which); - final long bluetoothOnTime = getBluetoothOnTime(batteryRealtime, which); + final long screenOnTime = getScreenOnTime(rawRealtime, which); + final long phoneOnTime = getPhoneOnTime(rawRealtime, which); + final long wifiOnTime = getWifiOnTime(rawRealtime, which); + final long wifiRunningTime = getGlobalWifiRunningTime(rawRealtime, which); + final long bluetoothOnTime = getBluetoothOnTime(rawRealtime, which); StringBuilder sb = new StringBuilder(128); @@ -1260,22 +1495,16 @@ public abstract class BatteryStats implements Parcelable { dumpLine(pw, 0 /* uid */, category, BATTERY_DATA, which == STATS_SINCE_CHARGED ? getStartCount() : "N/A", whichBatteryRealtime / 1000, whichBatteryUptime / 1000, - totalRealtime / 1000, totalUptime / 1000); + totalRealtime / 1000, totalUptime / 1000, + getStartClockTime(), + whichBatteryScreenOffRealtime / 1000, whichBatteryScreenOffUptime / 1000); - // Calculate total network and wakelock times across all uids. - long mobileRxTotal = 0; - long mobileTxTotal = 0; - long wifiRxTotal = 0; - long wifiTxTotal = 0; + // Calculate wakelock times across all uids. long fullWakeLockTimeTotal = 0; long partialWakeLockTimeTotal = 0; for (int iu = 0; iu < NU; iu++) { Uid u = uidStats.valueAt(iu); - mobileRxTotal += u.getNetworkActivityCount(NETWORK_MOBILE_RX_BYTES, which); - mobileTxTotal += u.getNetworkActivityCount(NETWORK_MOBILE_TX_BYTES, which); - wifiRxTotal += u.getNetworkActivityCount(NETWORK_WIFI_RX_BYTES, which); - wifiTxTotal += u.getNetworkActivityCount(NETWORK_WIFI_TX_BYTES, which); Map<String, ? extends BatteryStats.Uid.Wakelock> wakelocks = u.getWakelockStats(); if (wakelocks.size() > 0) { @@ -1285,59 +1514,96 @@ public abstract class BatteryStats implements Parcelable { Timer fullWakeTimer = wl.getWakeTime(WAKE_TYPE_FULL); if (fullWakeTimer != null) { - fullWakeLockTimeTotal += fullWakeTimer.getTotalTimeLocked(batteryRealtime, which); + fullWakeLockTimeTotal += fullWakeTimer.getTotalTimeLocked(rawRealtime, + which); } Timer partialWakeTimer = wl.getWakeTime(WAKE_TYPE_PARTIAL); if (partialWakeTimer != null) { partialWakeLockTimeTotal += partialWakeTimer.getTotalTimeLocked( - batteryRealtime, which); + rawRealtime, which); } } } } + long mobileRxTotalBytes = getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which); + long mobileTxTotalBytes = getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which); + long wifiRxTotalBytes = getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, which); + long wifiTxTotalBytes = getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, which); + long mobileRxTotalPackets = getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, which); + long mobileTxTotalPackets = getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, which); + long wifiRxTotalPackets = getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, which); + long wifiTxTotalPackets = getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which); + + // Dump network stats + dumpLine(pw, 0 /* uid */, category, GLOBAL_NETWORK_DATA, + mobileRxTotalBytes, mobileTxTotalBytes, wifiRxTotalBytes, wifiTxTotalBytes, + mobileRxTotalPackets, mobileTxTotalPackets, wifiRxTotalPackets, wifiTxTotalPackets); + // Dump misc stats dumpLine(pw, 0 /* uid */, category, MISC_DATA, screenOnTime / 1000, phoneOnTime / 1000, wifiOnTime / 1000, wifiRunningTime / 1000, bluetoothOnTime / 1000, - mobileRxTotal, mobileTxTotal, wifiRxTotal, wifiTxTotal, + mobileRxTotalBytes, mobileTxTotalBytes, wifiRxTotalBytes, wifiTxTotalBytes, fullWakeLockTimeTotal, partialWakeLockTimeTotal, - getInputEventCount(which)); + getInputEventCount(which), getMobileRadioActiveTime(rawRealtime, which)); // Dump screen brightness stats Object[] args = new Object[NUM_SCREEN_BRIGHTNESS_BINS]; for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) { - args[i] = getScreenBrightnessTime(i, batteryRealtime, which) / 1000; + args[i] = getScreenBrightnessTime(i, rawRealtime, which) / 1000; } dumpLine(pw, 0 /* uid */, category, SCREEN_BRIGHTNESS_DATA, args); // Dump signal strength stats args = new Object[SignalStrength.NUM_SIGNAL_STRENGTH_BINS]; for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) { - args[i] = getPhoneSignalStrengthTime(i, batteryRealtime, which) / 1000; + args[i] = getPhoneSignalStrengthTime(i, rawRealtime, which) / 1000; } dumpLine(pw, 0 /* uid */, category, SIGNAL_STRENGTH_TIME_DATA, args); dumpLine(pw, 0 /* uid */, category, SIGNAL_SCANNING_TIME_DATA, - getPhoneSignalScanningTime(batteryRealtime, which) / 1000); + getPhoneSignalScanningTime(rawRealtime, which) / 1000); for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) { args[i] = getPhoneSignalStrengthCount(i, which); } dumpLine(pw, 0 /* uid */, category, SIGNAL_STRENGTH_COUNT_DATA, args); - + // Dump network type stats args = new Object[NUM_DATA_CONNECTION_TYPES]; for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) { - args[i] = getPhoneDataConnectionTime(i, batteryRealtime, which) / 1000; + args[i] = getPhoneDataConnectionTime(i, rawRealtime, which) / 1000; } dumpLine(pw, 0 /* uid */, category, DATA_CONNECTION_TIME_DATA, args); for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) { args[i] = getPhoneDataConnectionCount(i, which); } dumpLine(pw, 0 /* uid */, category, DATA_CONNECTION_COUNT_DATA, args); - + + // Dump wifi state stats + args = new Object[NUM_WIFI_STATES]; + for (int i=0; i<NUM_WIFI_STATES; i++) { + args[i] = getWifiStateTime(i, rawRealtime, which) / 1000; + } + dumpLine(pw, 0 /* uid */, category, WIFI_STATE_TIME_DATA, args); + for (int i=0; i<NUM_WIFI_STATES; i++) { + args[i] = getWifiStateCount(i, which); + } + dumpLine(pw, 0 /* uid */, category, WIFI_STATE_COUNT_DATA, args); + + // Dump bluetooth state stats + args = new Object[NUM_BLUETOOTH_STATES]; + for (int i=0; i<NUM_BLUETOOTH_STATES; i++) { + args[i] = getBluetoothStateTime(i, rawRealtime, which) / 1000; + } + dumpLine(pw, 0 /* uid */, category, BLUETOOTH_STATE_TIME_DATA, args); + for (int i=0; i<NUM_BLUETOOTH_STATES; i++) { + args[i] = getBluetoothStateCount(i, which); + } + dumpLine(pw, 0 /* uid */, category, BLUETOOTH_STATE_COUNT_DATA, args); + if (which == STATS_SINCE_UNPLUGGED) { - dumpLine(pw, 0 /* uid */, category, BATTERY_LEVEL_DATA, getDischargeStartLevel(), + dumpLine(pw, 0 /* uid */, category, BATTERY_LEVEL_DATA, getDischargeStartLevel(), getDischargeCurrentLevel()); } @@ -1357,7 +1623,7 @@ public abstract class BatteryStats implements Parcelable { if (kernelWakelocks.size() > 0) { for (Map.Entry<String, ? extends BatteryStats.Timer> ent : kernelWakelocks.entrySet()) { sb.setLength(0); - printWakeLockCheckin(sb, ent.getValue(), batteryRealtime, null, which, ""); + printWakeLockCheckin(sb, ent.getValue(), rawRealtime, null, which, ""); dumpLine(pw, 0 /* uid */, category, KERNEL_WAKELOCK_DATA, ent.getKey(), sb.toString()); @@ -1365,6 +1631,61 @@ public abstract class BatteryStats implements Parcelable { } } + BatteryStatsHelper helper = new BatteryStatsHelper(context); + helper.create(this); + helper.refreshStats(which, UserHandle.USER_ALL); + List<BatterySipper> sippers = helper.getUsageList(); + if (sippers != null && sippers.size() > 0) { + dumpLine(pw, 0 /* uid */, category, POWER_USE_SUMMARY_DATA, + BatteryStatsHelper.makemAh(helper.getPowerProfile().getBatteryCapacity()), + BatteryStatsHelper.makemAh(helper.getComputedPower()), + BatteryStatsHelper.makemAh(helper.getMinDrainedPower()), + BatteryStatsHelper.makemAh(helper.getMaxDrainedPower())); + for (int i=0; i<sippers.size(); i++) { + BatterySipper bs = sippers.get(i); + int uid = 0; + String label; + switch (bs.drainType) { + case IDLE: + label="idle"; + break; + case CELL: + label="cell"; + break; + case PHONE: + label="phone"; + break; + case WIFI: + label="wifi"; + break; + case BLUETOOTH: + label="blue"; + break; + case SCREEN: + label="scrn"; + break; + case APP: + uid = bs.uidObj.getUid(); + label = "uid"; + break; + case USER: + uid = UserHandle.getUid(bs.userId, 0); + label = "user"; + break; + case UNACCOUNTED: + label = "unacc"; + break; + case OVERCOUNTED: + label = "over"; + break; + default: + label = "???"; + } + dumpLine(pw, uid, category, POWER_USE_ITEM_DATA, label, + BatteryStatsHelper.makemAh(bs.value)); + } + } + for (int iu = 0; iu < NU; iu++) { final int uid = uidStats.keyAt(iu); if (reqUid >= 0 && uid != reqUid) { @@ -1372,16 +1693,28 @@ public abstract class BatteryStats implements Parcelable { } Uid u = uidStats.valueAt(iu); // Dump Network stats per uid, if any - long mobileRx = u.getNetworkActivityCount(NETWORK_MOBILE_RX_BYTES, which); - long mobileTx = u.getNetworkActivityCount(NETWORK_MOBILE_TX_BYTES, which); - long wifiRx = u.getNetworkActivityCount(NETWORK_WIFI_RX_BYTES, which); - long wifiTx = u.getNetworkActivityCount(NETWORK_WIFI_TX_BYTES, which); - long fullWifiLockOnTime = u.getFullWifiLockTime(batteryRealtime, which); - long wifiScanTime = u.getWifiScanTime(batteryRealtime, which); - long uidWifiRunningTime = u.getWifiRunningTime(batteryRealtime, which); - - if (mobileRx > 0 || mobileTx > 0 || wifiRx > 0 || wifiTx > 0) { - dumpLine(pw, uid, category, NETWORK_DATA, mobileRx, mobileTx, wifiRx, wifiTx); + long mobileBytesRx = u.getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which); + long mobileBytesTx = u.getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which); + long wifiBytesRx = u.getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, which); + long wifiBytesTx = u.getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, which); + long mobilePacketsRx = u.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, which); + long mobilePacketsTx = u.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, which); + long mobileActiveTime = u.getMobileRadioActiveTime(which); + int mobileActiveCount = u.getMobileRadioActiveCount(which); + long wifiPacketsRx = u.getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, which); + long wifiPacketsTx = u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which); + long fullWifiLockOnTime = u.getFullWifiLockTime(rawRealtime, which); + long wifiScanTime = u.getWifiScanTime(rawRealtime, which); + long uidWifiRunningTime = u.getWifiRunningTime(rawRealtime, which); + + if (mobileBytesRx > 0 || mobileBytesTx > 0 || wifiBytesRx > 0 || wifiBytesTx > 0 + || mobilePacketsRx > 0 || mobilePacketsTx > 0 || wifiPacketsRx > 0 + || wifiPacketsTx > 0 || mobileActiveTime > 0 || mobileActiveCount > 0) { + dumpLine(pw, uid, category, NETWORK_DATA, mobileBytesRx, mobileBytesTx, + wifiBytesRx, wifiBytesTx, + mobilePacketsRx, mobilePacketsTx, + wifiPacketsRx, wifiPacketsTx, + mobileActiveTime, mobileActiveCount); } if (fullWifiLockOnTime != 0 || wifiScanTime != 0 @@ -1411,11 +1744,11 @@ public abstract class BatteryStats implements Parcelable { String linePrefix = ""; sb.setLength(0); linePrefix = printWakeLockCheckin(sb, wl.getWakeTime(WAKE_TYPE_FULL), - batteryRealtime, "f", which, linePrefix); + rawRealtime, "f", which, linePrefix); linePrefix = printWakeLockCheckin(sb, wl.getWakeTime(WAKE_TYPE_PARTIAL), - batteryRealtime, "p", which, linePrefix); + rawRealtime, "p", which, linePrefix); linePrefix = printWakeLockCheckin(sb, wl.getWakeTime(WAKE_TYPE_WINDOW), - batteryRealtime, "w", which, linePrefix); + rawRealtime, "w", which, linePrefix); // Only log if we had at lease one wakelock... if (sb.length() > 0) { @@ -1437,7 +1770,7 @@ public abstract class BatteryStats implements Parcelable { Timer timer = se.getSensorTime(); if (timer != null) { // Convert from microseconds to milliseconds with rounding - long totalTime = (timer.getTotalTimeLocked(batteryRealtime, which) + 500) / 1000; + long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000; int count = timer.getCountLocked(which); if (totalTime != 0) { dumpLine(pw, uid, category, SENSOR_DATA, sensorNumber, totalTime, count); @@ -1449,7 +1782,7 @@ public abstract class BatteryStats implements Parcelable { Timer vibTimer = u.getVibratorOnTimer(); if (vibTimer != null) { // Convert from microseconds to milliseconds with rounding - long totalTime = (vibTimer.getTotalTimeLocked(batteryRealtime, which) + 500) / 1000; + long totalTime = (vibTimer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000; int count = vibTimer.getCountLocked(which); if (totalTime != 0) { dumpLine(pw, uid, category, VIBRATOR_DATA, totalTime, count); @@ -1459,7 +1792,7 @@ public abstract class BatteryStats implements Parcelable { Timer fgTimer = u.getForegroundActivityTimer(); if (fgTimer != null) { // Convert from microseconds to milliseconds with rounding - long totalTime = (fgTimer.getTotalTimeLocked(batteryRealtime, which) + 500) / 1000; + long totalTime = (fgTimer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000; int count = fgTimer.getCountLocked(which); if (totalTime != 0) { dumpLine(pw, uid, category, FOREGROUND_DATA, totalTime, count); @@ -1527,18 +1860,25 @@ public abstract class BatteryStats implements Parcelable { } } + private void printmAh(PrintWriter printer, double power) { + printer.print(BatteryStatsHelper.makemAh(power)); + } + @SuppressWarnings("unused") - public final void dumpLocked(PrintWriter pw, String prefix, final int which, int reqUid) { + public final void dumpLocked(Context context, PrintWriter pw, String prefix, final int which, + int reqUid) { final long rawUptime = SystemClock.uptimeMillis() * 1000; final long rawRealtime = SystemClock.elapsedRealtime() * 1000; final long batteryUptime = getBatteryUptime(rawUptime); - final long batteryRealtime = getBatteryRealtime(rawRealtime); final long whichBatteryUptime = computeBatteryUptime(rawUptime, which); final long whichBatteryRealtime = computeBatteryRealtime(rawRealtime, which); final long totalRealtime = computeRealtime(rawRealtime, which); final long totalUptime = computeUptime(rawUptime, which); - + final long whichBatteryScreenOffUptime = computeBatteryScreenOffUptime(rawUptime, which); + final long whichBatteryScreenOffRealtime = computeBatteryScreenOffRealtime(rawRealtime, + which); + StringBuilder sb = new StringBuilder(128); SparseArray<? extends Uid> uidStats = getUidStats(); @@ -1556,37 +1896,56 @@ public abstract class BatteryStats implements Parcelable { pw.println(sb.toString()); sb.setLength(0); sb.append(prefix); + sb.append(" Time on battery screen off: "); + formatTimeMs(sb, whichBatteryScreenOffRealtime / 1000); sb.append("("); + sb.append(formatRatioLocked(whichBatteryScreenOffRealtime, totalRealtime)); + sb.append(") realtime, "); + formatTimeMs(sb, whichBatteryScreenOffUptime / 1000); + sb.append("("); + sb.append(formatRatioLocked(whichBatteryScreenOffUptime, totalRealtime)); + sb.append(") uptime"); + pw.println(sb.toString()); + sb.setLength(0); + sb.append(prefix); sb.append(" Total run time: "); formatTimeMs(sb, totalRealtime / 1000); sb.append("realtime, "); formatTimeMs(sb, totalUptime / 1000); - sb.append("uptime, "); + sb.append("uptime"); pw.println(sb.toString()); - - final long screenOnTime = getScreenOnTime(batteryRealtime, which); - final long phoneOnTime = getPhoneOnTime(batteryRealtime, which); - final long wifiRunningTime = getGlobalWifiRunningTime(batteryRealtime, which); - final long wifiOnTime = getWifiOnTime(batteryRealtime, which); - final long bluetoothOnTime = getBluetoothOnTime(batteryRealtime, which); + pw.print(" Start clock time: "); + pw.println(DateFormat.format("yyyy-MM-dd-HH-mm-ss", getStartClockTime()).toString()); + + final long screenOnTime = getScreenOnTime(rawRealtime, which); + final long phoneOnTime = getPhoneOnTime(rawRealtime, which); + final long wifiRunningTime = getGlobalWifiRunningTime(rawRealtime, which); + final long wifiOnTime = getWifiOnTime(rawRealtime, which); + final long bluetoothOnTime = getBluetoothOnTime(rawRealtime, which); sb.setLength(0); sb.append(prefix); sb.append(" Screen on: "); formatTimeMs(sb, screenOnTime / 1000); sb.append("("); sb.append(formatRatioLocked(screenOnTime, whichBatteryRealtime)); - sb.append("), Input events: "); sb.append(getInputEventCount(which)); - sb.append(", Active phone call: "); formatTimeMs(sb, phoneOnTime / 1000); - sb.append("("); sb.append(formatRatioLocked(phoneOnTime, whichBatteryRealtime)); - sb.append(")"); + sb.append(") "); sb.append(getScreenOnCount(which)); + sb.append("x, Input events: "); sb.append(getInputEventCount(which)); pw.println(sb.toString()); + if (phoneOnTime != 0) { + sb.setLength(0); + sb.append(prefix); + sb.append(" Active phone call: "); formatTimeMs(sb, phoneOnTime / 1000); + sb.append("("); sb.append(formatRatioLocked(phoneOnTime, whichBatteryRealtime)); + sb.append(") "); sb.append(getPhoneOnCount(which)); + } sb.setLength(0); sb.append(prefix); - sb.append(" Screen brightnesses: "); + sb.append(" Screen brightnesses:"); boolean didOne = false; for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) { - final long time = getScreenBrightnessTime(i, batteryRealtime, which); + final long time = getScreenBrightnessTime(i, rawRealtime, which); if (time == 0) { continue; } - if (didOne) sb.append(", "); + sb.append("\n "); + sb.append(prefix); didOne = true; sb.append(SCREEN_BRIGHTNESS_NAMES[i]); sb.append(" "); @@ -1595,70 +1954,17 @@ public abstract class BatteryStats implements Parcelable { sb.append(formatRatioLocked(time, screenOnTime)); sb.append(")"); } - if (!didOne) sb.append("No activity"); + if (!didOne) sb.append(" (no activity)"); pw.println(sb.toString()); - // Calculate total network and wakelock times across all uids. - long mobileRxTotal = 0; - long mobileTxTotal = 0; - long wifiRxTotal = 0; - long wifiTxTotal = 0; + // Calculate wakelock times across all uids. long fullWakeLockTimeTotalMicros = 0; long partialWakeLockTimeTotalMicros = 0; - final Comparator<TimerEntry> timerComparator = new Comparator<TimerEntry>() { - @Override - public int compare(TimerEntry lhs, TimerEntry rhs) { - long lhsTime = lhs.mTime; - long rhsTime = rhs.mTime; - if (lhsTime < rhsTime) { - return 1; - } - if (lhsTime > rhsTime) { - return -1; - } - return 0; - } - }; - - if (reqUid < 0) { - Map<String, ? extends BatteryStats.Timer> kernelWakelocks = getKernelWakelockStats(); - if (kernelWakelocks.size() > 0) { - final ArrayList<TimerEntry> timers = new ArrayList<TimerEntry>(); - for (Map.Entry<String, ? extends BatteryStats.Timer> ent : kernelWakelocks.entrySet()) { - BatteryStats.Timer timer = ent.getValue(); - long totalTimeMillis = computeWakeLock(timer, batteryRealtime, which); - if (totalTimeMillis > 0) { - timers.add(new TimerEntry(ent.getKey(), 0, timer, totalTimeMillis)); - } - } - Collections.sort(timers, timerComparator); - for (int i=0; i<timers.size(); i++) { - TimerEntry timer = timers.get(i); - String linePrefix = ": "; - sb.setLength(0); - sb.append(prefix); - sb.append(" Kernel Wake lock "); - sb.append(timer.mName); - linePrefix = printWakeLock(sb, timer.mTimer, batteryRealtime, null, - which, linePrefix); - if (!linePrefix.equals(": ")) { - sb.append(" realtime"); - // Only print out wake locks that were held - pw.println(sb.toString()); - } - } - } - } - final ArrayList<TimerEntry> timers = new ArrayList<TimerEntry>(); for (int iu = 0; iu < NU; iu++) { Uid u = uidStats.valueAt(iu); - mobileRxTotal += u.getNetworkActivityCount(NETWORK_MOBILE_RX_BYTES, which); - mobileTxTotal += u.getNetworkActivityCount(NETWORK_MOBILE_TX_BYTES, which); - wifiRxTotal += u.getNetworkActivityCount(NETWORK_WIFI_RX_BYTES, which); - wifiTxTotal += u.getNetworkActivityCount(NETWORK_WIFI_TX_BYTES, which); Map<String, ? extends BatteryStats.Uid.Wakelock> wakelocks = u.getWakelockStats(); if (wakelocks.size() > 0) { @@ -1669,13 +1975,13 @@ public abstract class BatteryStats implements Parcelable { Timer fullWakeTimer = wl.getWakeTime(WAKE_TYPE_FULL); if (fullWakeTimer != null) { fullWakeLockTimeTotalMicros += fullWakeTimer.getTotalTimeLocked( - batteryRealtime, which); + rawRealtime, which); } Timer partialWakeTimer = wl.getWakeTime(WAKE_TYPE_PARTIAL); if (partialWakeTimer != null) { long totalTimeMicros = partialWakeTimer.getTotalTimeLocked( - batteryRealtime, which); + rawRealtime, which); if (totalTimeMicros > 0) { if (reqUid < 0) { // Only show the ordered list of all wake @@ -1691,30 +1997,47 @@ public abstract class BatteryStats implements Parcelable { } } + long mobileRxTotalBytes = getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which); + long mobileTxTotalBytes = getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which); + long wifiRxTotalBytes = getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, which); + long wifiTxTotalBytes = getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, which); + long mobileRxTotalPackets = getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, which); + long mobileTxTotalPackets = getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, which); + long wifiRxTotalPackets = getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, which); + long wifiTxTotalPackets = getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which); + + if (fullWakeLockTimeTotalMicros != 0) { + sb.setLength(0); + sb.append(prefix); + sb.append(" Total full wakelock time: "); formatTimeMsNoSpace(sb, + (fullWakeLockTimeTotalMicros + 500) / 1000); + pw.println(sb.toString()); + } + + if (partialWakeLockTimeTotalMicros != 0) { + sb.setLength(0); + sb.append(prefix); + sb.append(" Total partial wakelock time: "); formatTimeMsNoSpace(sb, + (partialWakeLockTimeTotalMicros + 500) / 1000); + pw.println(sb.toString()); + } + pw.print(prefix); - pw.print(" Mobile total received: "); pw.print(formatBytesLocked(mobileRxTotal)); - pw.print(", Total sent: "); pw.println(formatBytesLocked(mobileTxTotal)); - pw.print(prefix); - pw.print(" Wi-Fi total received: "); pw.print(formatBytesLocked(wifiRxTotal)); - pw.print(", Total sent: "); pw.println(formatBytesLocked(wifiTxTotal)); - sb.setLength(0); - sb.append(prefix); - sb.append(" Total full wakelock time: "); formatTimeMs(sb, - (fullWakeLockTimeTotalMicros + 500) / 1000); - sb.append(", Total partial wakelock time: "); formatTimeMs(sb, - (partialWakeLockTimeTotalMicros + 500) / 1000); - pw.println(sb.toString()); - + pw.print(" Mobile total received: "); pw.print(formatBytesLocked(mobileRxTotalBytes)); + pw.print(", sent: "); pw.print(formatBytesLocked(mobileTxTotalBytes)); + pw.print(" (packets received "); pw.print(mobileRxTotalPackets); + pw.print(", sent "); pw.print(mobileTxTotalPackets); pw.println(")"); sb.setLength(0); sb.append(prefix); - sb.append(" Signal levels: "); + sb.append(" Signal levels:"); didOne = false; for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) { - final long time = getPhoneSignalStrengthTime(i, batteryRealtime, which); + final long time = getPhoneSignalStrengthTime(i, rawRealtime, which); if (time == 0) { continue; } - if (didOne) sb.append(", "); + sb.append("\n "); + sb.append(prefix); didOne = true; sb.append(SignalStrength.SIGNAL_STRENGTH_NAMES[i]); sb.append(" "); @@ -1725,25 +2048,26 @@ public abstract class BatteryStats implements Parcelable { sb.append(getPhoneSignalStrengthCount(i, which)); sb.append("x"); } - if (!didOne) sb.append("No activity"); + if (!didOne) sb.append(" (no activity)"); pw.println(sb.toString()); sb.setLength(0); sb.append(prefix); sb.append(" Signal scanning time: "); - formatTimeMs(sb, getPhoneSignalScanningTime(batteryRealtime, which) / 1000); + formatTimeMsNoSpace(sb, getPhoneSignalScanningTime(rawRealtime, which) / 1000); pw.println(sb.toString()); sb.setLength(0); sb.append(prefix); - sb.append(" Radio types: "); + sb.append(" Radio types:"); didOne = false; for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) { - final long time = getPhoneDataConnectionTime(i, batteryRealtime, which); + final long time = getPhoneDataConnectionTime(i, rawRealtime, which); if (time == 0) { continue; } - if (didOne) sb.append(", "); + sb.append("\n "); + sb.append(prefix); didOne = true; sb.append(DATA_CONNECTION_NAMES[i]); sb.append(" "); @@ -1754,28 +2078,100 @@ public abstract class BatteryStats implements Parcelable { sb.append(getPhoneDataConnectionCount(i, which)); sb.append("x"); } - if (!didOne) sb.append("No activity"); + if (!didOne) sb.append(" (no activity)"); pw.println(sb.toString()); sb.setLength(0); sb.append(prefix); - sb.append(" Radio data uptime when unplugged: "); - sb.append(getRadioDataUptime() / 1000); - sb.append(" ms"); + sb.append(" Mobile radio active time: "); + final long mobileActiveTime = getMobileRadioActiveTime(rawRealtime, which); + formatTimeMs(sb, mobileActiveTime / 1000); + sb.append("("); sb.append(formatRatioLocked(mobileActiveTime, whichBatteryRealtime)); + sb.append(") "); sb.append(getMobileRadioActiveCount(which)); + sb.append("x"); pw.println(sb.toString()); + final long mobileActiveUnknownTime = getMobileRadioActiveUnknownTime(which); + if (mobileActiveUnknownTime != 0) { + sb.setLength(0); + sb.append(prefix); + sb.append(" Mobile radio active unknown time: "); + formatTimeMs(sb, mobileActiveUnknownTime / 1000); + sb.append("("); + sb.append(formatRatioLocked(mobileActiveUnknownTime, whichBatteryRealtime)); + sb.append(") "); sb.append(getMobileRadioActiveUnknownCount(which)); + sb.append("x"); + pw.println(sb.toString()); + } + + pw.print(prefix); + pw.print(" Wi-Fi total received: "); pw.print(formatBytesLocked(wifiRxTotalBytes)); + pw.print(", sent: "); pw.print(formatBytesLocked(wifiTxTotalBytes)); + pw.print(" (packets received "); pw.print(wifiRxTotalPackets); + pw.print(", sent "); pw.print(wifiTxTotalPackets); pw.println(")"); sb.setLength(0); sb.append(prefix); sb.append(" Wifi on: "); formatTimeMs(sb, wifiOnTime / 1000); sb.append("("); sb.append(formatRatioLocked(wifiOnTime, whichBatteryRealtime)); sb.append("), Wifi running: "); formatTimeMs(sb, wifiRunningTime / 1000); sb.append("("); sb.append(formatRatioLocked(wifiRunningTime, whichBatteryRealtime)); - sb.append("), Bluetooth on: "); formatTimeMs(sb, bluetoothOnTime / 1000); + sb.append(")"); + pw.println(sb.toString()); + + sb.setLength(0); + sb.append(prefix); + sb.append(" Wifi states:"); + didOne = false; + for (int i=0; i<NUM_WIFI_STATES; i++) { + final long time = getWifiStateTime(i, rawRealtime, which); + if (time == 0) { + continue; + } + sb.append("\n "); + didOne = true; + sb.append(WIFI_STATE_NAMES[i]); + sb.append(" "); + formatTimeMs(sb, time/1000); + sb.append("("); + sb.append(formatRatioLocked(time, whichBatteryRealtime)); + sb.append(") "); + sb.append(getPhoneDataConnectionCount(i, which)); + sb.append("x"); + } + if (!didOne) sb.append(" (no activity)"); + pw.println(sb.toString()); + + sb.setLength(0); + sb.append(prefix); + sb.append(" Bluetooth on: "); formatTimeMs(sb, bluetoothOnTime / 1000); sb.append("("); sb.append(formatRatioLocked(bluetoothOnTime, whichBatteryRealtime)); sb.append(")"); pw.println(sb.toString()); - - pw.println(" "); + + sb.setLength(0); + sb.append(prefix); + sb.append(" Bluetooth states:"); + didOne = false; + for (int i=0; i<NUM_BLUETOOTH_STATES; i++) { + final long time = getBluetoothStateTime(i, rawRealtime, which); + if (time == 0) { + continue; + } + sb.append("\n "); + didOne = true; + sb.append(BLUETOOTH_STATE_NAMES[i]); + sb.append(" "); + formatTimeMs(sb, time/1000); + sb.append("("); + sb.append(formatRatioLocked(time, whichBatteryRealtime)); + sb.append(") "); + sb.append(getPhoneDataConnectionCount(i, which)); + sb.append("x"); + } + if (!didOne) sb.append(" (no activity)"); + pw.println(sb.toString()); + + pw.println(); if (which == STATS_SINCE_UNPLUGGED) { if (getIsOnBattery()) { @@ -1809,6 +2205,142 @@ public abstract class BatteryStats implements Parcelable { pw.println(); } + BatteryStatsHelper helper = new BatteryStatsHelper(context); + helper.create(this); + helper.refreshStats(which, UserHandle.USER_ALL); + List<BatterySipper> sippers = helper.getUsageList(); + if (sippers != null && sippers.size() > 0) { + pw.print(prefix); pw.println(" Estimated power use (mAh):"); + pw.print(prefix); pw.print(" Capacity: "); + printmAh(pw, helper.getPowerProfile().getBatteryCapacity()); + pw.print(", Computed drain: "); printmAh(pw, helper.getComputedPower()); + pw.print(", Min drain: "); printmAh(pw, helper.getMinDrainedPower()); + pw.print(", Max drain: "); printmAh(pw, helper.getMaxDrainedPower()); + pw.println(); + for (int i=0; i<sippers.size(); i++) { + BatterySipper bs = sippers.get(i); + switch (bs.drainType) { + case IDLE: + pw.print(prefix); pw.print(" Idle: "); printmAh(pw, bs.value); + pw.println(); + break; + case CELL: + pw.print(prefix); pw.print(" Cell standby: "); printmAh(pw, bs.value); + pw.println(); + break; + case PHONE: + pw.print(prefix); pw.print(" Phone calls: "); printmAh(pw, bs.value); + pw.println(); + break; + case WIFI: + pw.print(prefix); pw.print(" Wifi: "); printmAh(pw, bs.value); + pw.println(); + break; + case BLUETOOTH: + pw.print(prefix); pw.print(" Bluetooth: "); printmAh(pw, bs.value); + pw.println(); + break; + case SCREEN: + pw.print(prefix); pw.print(" Screen: "); printmAh(pw, bs.value); + pw.println(); + break; + case APP: + pw.print(prefix); pw.print(" Uid "); + UserHandle.formatUid(pw, bs.uidObj.getUid()); + pw.print(": "); printmAh(pw, bs.value); pw.println(); + break; + case USER: + pw.print(prefix); pw.print(" User "); pw.print(bs.userId); + pw.print(": "); printmAh(pw, bs.value); pw.println(); + break; + case UNACCOUNTED: + pw.print(prefix); pw.print(" Unaccounted: "); printmAh(pw, bs.value); + pw.println(); + break; + case OVERCOUNTED: + pw.print(prefix); pw.print(" Over-counted: "); printmAh(pw, bs.value); + pw.println(); + break; + } + } + pw.println(); + } + + sippers = helper.getMobilemsppList(); + if (sippers != null && sippers.size() > 0) { + pw.print(prefix); pw.println(" Per-app mobile ms per packet:"); + long totalTime = 0; + for (int i=0; i<sippers.size(); i++) { + BatterySipper bs = sippers.get(i); + sb.setLength(0); + sb.append(prefix); sb.append(" Uid "); + UserHandle.formatUid(sb, bs.uidObj.getUid()); + sb.append(": "); sb.append(BatteryStatsHelper.makemAh(bs.mobilemspp)); + sb.append(" ("); sb.append(bs.mobileRxPackets+bs.mobileTxPackets); + sb.append(" packets over "); formatTimeMsNoSpace(sb, bs.mobileActive); + sb.append(") "); sb.append(bs.mobileActiveCount); sb.append("x"); + pw.println(sb.toString()); + totalTime += bs.mobileActive; + } + sb.setLength(0); + sb.append(prefix); + sb.append(" TOTAL TIME: "); + formatTimeMs(sb, totalTime); + sb.append("("); sb.append(formatRatioLocked(totalTime, whichBatteryRealtime)); + sb.append(")"); + pw.println(sb.toString()); + pw.println(); + } + + final Comparator<TimerEntry> timerComparator = new Comparator<TimerEntry>() { + @Override + public int compare(TimerEntry lhs, TimerEntry rhs) { + long lhsTime = lhs.mTime; + long rhsTime = rhs.mTime; + if (lhsTime < rhsTime) { + return 1; + } + if (lhsTime > rhsTime) { + return -1; + } + return 0; + } + }; + + if (reqUid < 0) { + Map<String, ? extends BatteryStats.Timer> kernelWakelocks = getKernelWakelockStats(); + if (kernelWakelocks.size() > 0) { + final ArrayList<TimerEntry> ktimers = new ArrayList<TimerEntry>(); + for (Map.Entry<String, ? extends BatteryStats.Timer> ent : kernelWakelocks.entrySet()) { + BatteryStats.Timer timer = ent.getValue(); + long totalTimeMillis = computeWakeLock(timer, rawRealtime, which); + if (totalTimeMillis > 0) { + ktimers.add(new TimerEntry(ent.getKey(), 0, timer, totalTimeMillis)); + } + } + if (ktimers.size() > 0) { + Collections.sort(ktimers, timerComparator); + pw.print(prefix); pw.println(" All kernel wake locks:"); + for (int i=0; i<ktimers.size(); i++) { + TimerEntry timer = ktimers.get(i); + String linePrefix = ": "; + sb.setLength(0); + sb.append(prefix); + sb.append(" Kernel Wake lock "); + sb.append(timer.mName); + linePrefix = printWakeLock(sb, timer.mTimer, rawRealtime, null, + which, linePrefix); + if (!linePrefix.equals(": ")) { + sb.append(" realtime"); + // Only print out wake locks that were held + pw.println(sb.toString()); + } + } + pw.println(); + } + } + } + if (timers.size() > 0) { Collections.sort(timers, timerComparator); pw.print(prefix); pw.println(" All partial wake locks:"); @@ -1819,7 +2351,7 @@ public abstract class BatteryStats implements Parcelable { UserHandle.formatUid(sb, timer.mId); sb.append(" "); sb.append(timer.mName); - printWakeLock(sb, timer.mTimer, batteryRealtime, null, which, ": "); + printWakeLock(sb, timer.mTimer, rawRealtime, null, which, ": "); sb.append(" realtime"); pw.println(sb.toString()); } @@ -1840,24 +2372,70 @@ public abstract class BatteryStats implements Parcelable { UserHandle.formatUid(pw, uid); pw.println(":"); boolean uidActivity = false; - - long mobileRxBytes = u.getNetworkActivityCount(NETWORK_MOBILE_RX_BYTES, which); - long mobileTxBytes = u.getNetworkActivityCount(NETWORK_MOBILE_TX_BYTES, which); - long wifiRxBytes = u.getNetworkActivityCount(NETWORK_WIFI_RX_BYTES, which); - long wifiTxBytes = u.getNetworkActivityCount(NETWORK_WIFI_TX_BYTES, which); - long fullWifiLockOnTime = u.getFullWifiLockTime(batteryRealtime, which); - long wifiScanTime = u.getWifiScanTime(batteryRealtime, which); - long uidWifiRunningTime = u.getWifiRunningTime(batteryRealtime, which); - - if (mobileRxBytes > 0 || mobileTxBytes > 0) { + + long mobileRxBytes = u.getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which); + long mobileTxBytes = u.getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which); + long wifiRxBytes = u.getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, which); + long wifiTxBytes = u.getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, which); + long mobileRxPackets = u.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, which); + long mobileTxPackets = u.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, which); + long uidMobileActiveTime = u.getMobileRadioActiveTime(which); + int uidMobileActiveCount = u.getMobileRadioActiveCount(which); + long wifiRxPackets = u.getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, which); + long wifiTxPackets = u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which); + long fullWifiLockOnTime = u.getFullWifiLockTime(rawRealtime, which); + long wifiScanTime = u.getWifiScanTime(rawRealtime, which); + long uidWifiRunningTime = u.getWifiRunningTime(rawRealtime, which); + + if (mobileRxBytes > 0 || mobileTxBytes > 0 + || mobileRxPackets > 0 || mobileTxPackets > 0) { pw.print(prefix); pw.print(" Mobile network: "); pw.print(formatBytesLocked(mobileRxBytes)); pw.print(" received, "); - pw.print(formatBytesLocked(mobileTxBytes)); pw.println(" sent"); + pw.print(formatBytesLocked(mobileTxBytes)); + pw.print(" sent (packets "); pw.print(mobileRxPackets); + pw.print(" received, "); pw.print(mobileTxPackets); pw.println(" sent)"); } - if (wifiRxBytes > 0 || wifiTxBytes > 0) { + if (uidMobileActiveTime > 0 || uidMobileActiveCount > 0) { + sb.setLength(0); + sb.append(prefix); sb.append(" Mobile radio active: "); + formatTimeMs(sb, uidMobileActiveTime / 1000); + sb.append("("); + sb.append(formatRatioLocked(uidMobileActiveTime, mobileActiveTime)); + sb.append(") "); sb.append(uidMobileActiveCount); sb.append("x"); + long packets = mobileRxPackets + mobileTxPackets; + if (packets == 0) { + packets = 1; + } + sb.append(" @ "); + sb.append(BatteryStatsHelper.makemAh(uidMobileActiveTime / 1000 / (double)packets)); + sb.append(" mspp"); + pw.println(sb.toString()); + } + + if (wifiRxBytes > 0 || wifiTxBytes > 0 || wifiRxPackets > 0 || wifiTxPackets > 0) { pw.print(prefix); pw.print(" Wi-Fi network: "); pw.print(formatBytesLocked(wifiRxBytes)); pw.print(" received, "); - pw.print(formatBytesLocked(wifiTxBytes)); pw.println(" sent"); + pw.print(formatBytesLocked(wifiTxBytes)); + pw.print(" sent (packets "); pw.print(wifiRxPackets); + pw.print(" received, "); pw.print(wifiTxPackets); pw.println(" sent)"); + } + + if (fullWifiLockOnTime != 0 || wifiScanTime != 0 + || uidWifiRunningTime != 0) { + sb.setLength(0); + sb.append(prefix); sb.append(" Wifi Running: "); + formatTimeMs(sb, uidWifiRunningTime / 1000); + sb.append("("); sb.append(formatRatioLocked(uidWifiRunningTime, + whichBatteryRealtime)); sb.append(")\n"); + sb.append(prefix); sb.append(" Full Wifi Lock: "); + formatTimeMs(sb, fullWifiLockOnTime / 1000); + sb.append("("); sb.append(formatRatioLocked(fullWifiLockOnTime, + whichBatteryRealtime)); sb.append(")\n"); + sb.append(prefix); sb.append(" Wifi Scan: "); + formatTimeMs(sb, wifiScanTime / 1000); + sb.append("("); sb.append(formatRatioLocked(wifiScanTime, + whichBatteryRealtime)); sb.append(")"); + pw.println(sb.toString()); } if (u.hasUserActivity()) { @@ -1881,24 +2459,6 @@ public abstract class BatteryStats implements Parcelable { pw.println(sb.toString()); } } - - if (fullWifiLockOnTime != 0 || wifiScanTime != 0 - || uidWifiRunningTime != 0) { - sb.setLength(0); - sb.append(prefix); sb.append(" Wifi Running: "); - formatTimeMs(sb, uidWifiRunningTime / 1000); - sb.append("("); sb.append(formatRatioLocked(uidWifiRunningTime, - whichBatteryRealtime)); sb.append(")\n"); - sb.append(prefix); sb.append(" Full Wifi Lock: "); - formatTimeMs(sb, fullWifiLockOnTime / 1000); - sb.append("("); sb.append(formatRatioLocked(fullWifiLockOnTime, - whichBatteryRealtime)); sb.append(")\n"); - sb.append(prefix); sb.append(" Wifi Scan: "); - formatTimeMs(sb, wifiScanTime / 1000); - sb.append("("); sb.append(formatRatioLocked(wifiScanTime, - whichBatteryRealtime)); sb.append(")"); - pw.println(sb.toString()); - } Map<String, ? extends BatteryStats.Uid.Wakelock> wakelocks = u.getWakelockStats(); if (wakelocks.size() > 0) { @@ -1912,11 +2472,11 @@ public abstract class BatteryStats implements Parcelable { sb.append(prefix); sb.append(" Wake lock "); sb.append(ent.getKey()); - linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_FULL), batteryRealtime, + linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_FULL), rawRealtime, "full", which, linePrefix); - linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_PARTIAL), batteryRealtime, + linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_PARTIAL), rawRealtime, "partial", which, linePrefix); - linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_WINDOW), batteryRealtime, + linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_WINDOW), rawRealtime, "window", which, linePrefix); if (!linePrefix.equals(": ")) { sb.append(" realtime"); @@ -1926,11 +2486,11 @@ public abstract class BatteryStats implements Parcelable { count++; } totalFull += computeWakeLock(wl.getWakeTime(WAKE_TYPE_FULL), - batteryRealtime, which); + rawRealtime, which); totalPartial += computeWakeLock(wl.getWakeTime(WAKE_TYPE_PARTIAL), - batteryRealtime, which); + rawRealtime, which); totalWindow += computeWakeLock(wl.getWakeTime(WAKE_TYPE_WINDOW), - batteryRealtime, which); + rawRealtime, which); } if (count > 1) { if (totalFull != 0 || totalPartial != 0 || totalWindow != 0) { @@ -1986,7 +2546,7 @@ public abstract class BatteryStats implements Parcelable { if (timer != null) { // Convert from microseconds to milliseconds with rounding long totalTime = (timer.getTotalTimeLocked( - batteryRealtime, which) + 500) / 1000; + rawRealtime, which) + 500) / 1000; int count = timer.getCountLocked(which); //timer.logState(); if (totalTime != 0) { @@ -2010,7 +2570,7 @@ public abstract class BatteryStats implements Parcelable { if (vibTimer != null) { // Convert from microseconds to milliseconds with rounding long totalTime = (vibTimer.getTotalTimeLocked( - batteryRealtime, which) + 500) / 1000; + rawRealtime, which) + 500) / 1000; int count = vibTimer.getCountLocked(which); //timer.logState(); if (totalTime != 0) { @@ -2029,7 +2589,7 @@ public abstract class BatteryStats implements Parcelable { Timer fgTimer = u.getForegroundActivityTimer(); if (fgTimer != null) { // Convert from microseconds to milliseconds with rounding - long totalTime = (fgTimer.getTotalTimeLocked(batteryRealtime, which) + 500) / 1000; + long totalTime = (fgTimer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000; int count = fgTimer.getCountLocked(which); if (totalTime != 0) { sb.setLength(0); @@ -2151,28 +2711,53 @@ public abstract class BatteryStats implements Parcelable { } } - static void printBitDescriptions(PrintWriter pw, int oldval, int newval, BitDescription[] descriptions) { + static void printBitDescriptions(PrintWriter pw, int oldval, int newval, HistoryTag wakelockTag, + BitDescription[] descriptions, boolean longNames) { int diff = oldval ^ newval; if (diff == 0) return; + boolean didWake = false; for (int i=0; i<descriptions.length; i++) { BitDescription bd = descriptions[i]; if ((diff&bd.mask) != 0) { + pw.print(longNames ? " " : ","); if (bd.shift < 0) { - pw.print((newval&bd.mask) != 0 ? " +" : " -"); - pw.print(bd.name); + pw.print((newval&bd.mask) != 0 ? "+" : "-"); + pw.print(longNames ? bd.name : bd.shortName); + if (bd.mask == HistoryItem.STATE_WAKE_LOCK_FLAG && wakelockTag != null) { + didWake = true; + pw.print("="); + if (longNames) { + UserHandle.formatUid(pw, wakelockTag.uid); + pw.print(":\""); + pw.print(wakelockTag.string); + pw.print("\""); + } else { + pw.print(wakelockTag.poolIdx); + } + } } else { - pw.print(" "); - pw.print(bd.name); + pw.print(longNames ? bd.name : bd.shortName); pw.print("="); int val = (newval&bd.mask)>>bd.shift; if (bd.values != null && val >= 0 && val < bd.values.length) { - pw.print(bd.values[val]); + pw.print(longNames? bd.values[val] : bd.shortValues[val]); } else { pw.print(val); } } } } + if (!didWake && wakelockTag != null) { + pw.print(longNames ? "wake_lock=" : "w="); + if (longNames) { + UserHandle.formatUid(pw, wakelockTag.uid); + pw.print(":\""); + pw.print(wakelockTag.string); + pw.print("\""); + } else { + pw.print(wakelockTag.poolIdx); + } + } } public void prepareForDumpLocked() { @@ -2180,51 +2765,99 @@ public abstract class BatteryStats implements Parcelable { public static class HistoryPrinter { int oldState = 0; + int oldLevel = -1; int oldStatus = -1; int oldHealth = -1; int oldPlug = -1; int oldTemp = -1; int oldVolt = -1; + long lastTime = -1; - public void printNextItem(PrintWriter pw, HistoryItem rec, long now) { - pw.print(" "); - TimeUtils.formatDuration(rec.time-now, pw, TimeUtils.HUNDRED_DAY_FIELD_LEN); - pw.print(" "); + public void printNextItem(PrintWriter pw, HistoryItem rec, long now, boolean checkin) { + if (!checkin) { + pw.print(" "); + if (now >= 0) { + TimeUtils.formatDuration(rec.time-now, pw, TimeUtils.HUNDRED_DAY_FIELD_LEN); + } else { + TimeUtils.formatDuration(rec.time, pw, TimeUtils.HUNDRED_DAY_FIELD_LEN); + } + pw.print(" ("); + pw.print(rec.numReadInts); + pw.print(") "); + } else { + if (lastTime < 0) { + if (now >= 0) { + pw.print("@"); + pw.print(rec.time-now); + } else { + pw.print(rec.time); + } + } else { + pw.print(rec.time-lastTime); + } + lastTime = rec.time; + } if (rec.cmd == HistoryItem.CMD_START) { - pw.println(" START"); + if (checkin) { + pw.print(":"); + } + pw.println("START"); + } else if (rec.cmd == HistoryItem.CMD_CURRENT_TIME) { + if (checkin) { + pw.print(":"); + } + pw.print("TIME:"); + if (checkin) { + pw.println(rec.currentTime); + } else { + pw.print(" "); + pw.println(DateFormat.format("yyyy-MM-dd-HH-mm-ss", + rec.currentTime).toString()); + } } else if (rec.cmd == HistoryItem.CMD_OVERFLOW) { - pw.println(" *OVERFLOW*"); + if (checkin) { + pw.print(":"); + } + pw.println("*OVERFLOW*"); } else { - if (rec.batteryLevel < 10) pw.print("00"); - else if (rec.batteryLevel < 100) pw.print("0"); - pw.print(rec.batteryLevel); - pw.print(" "); - if (rec.states < 0x10) pw.print("0000000"); - else if (rec.states < 0x100) pw.print("000000"); - else if (rec.states < 0x1000) pw.print("00000"); - else if (rec.states < 0x10000) pw.print("0000"); - else if (rec.states < 0x100000) pw.print("000"); - else if (rec.states < 0x1000000) pw.print("00"); - else if (rec.states < 0x10000000) pw.print("0"); - pw.print(Integer.toHexString(rec.states)); + if (!checkin) { + if (rec.batteryLevel < 10) pw.print("00"); + else if (rec.batteryLevel < 100) pw.print("0"); + pw.print(rec.batteryLevel); + pw.print(" "); + if (rec.states < 0) ; + else if (rec.states < 0x10) pw.print("0000000"); + else if (rec.states < 0x100) pw.print("000000"); + else if (rec.states < 0x1000) pw.print("00000"); + else if (rec.states < 0x10000) pw.print("0000"); + else if (rec.states < 0x100000) pw.print("000"); + else if (rec.states < 0x1000000) pw.print("00"); + else if (rec.states < 0x10000000) pw.print("0"); + pw.print(Integer.toHexString(rec.states)); + } else { + if (oldLevel != rec.batteryLevel) { + oldLevel = rec.batteryLevel; + pw.print(",Bl="); pw.print(rec.batteryLevel); + } + } if (oldStatus != rec.batteryStatus) { oldStatus = rec.batteryStatus; - pw.print(" status="); + pw.print(checkin ? ",Bs=" : " status="); switch (oldStatus) { case BatteryManager.BATTERY_STATUS_UNKNOWN: - pw.print("unknown"); + pw.print(checkin ? "?" : "unknown"); break; case BatteryManager.BATTERY_STATUS_CHARGING: - pw.print("charging"); + pw.print(checkin ? "c" : "charging"); break; case BatteryManager.BATTERY_STATUS_DISCHARGING: - pw.print("discharging"); + pw.print(checkin ? "d" : "discharging"); break; case BatteryManager.BATTERY_STATUS_NOT_CHARGING: - pw.print("not-charging"); + pw.print(checkin ? "n" : "not-charging"); break; case BatteryManager.BATTERY_STATUS_FULL: - pw.print("full"); + pw.print(checkin ? "f" : "full"); break; default: pw.print(oldStatus); @@ -2233,25 +2866,28 @@ public abstract class BatteryStats implements Parcelable { } if (oldHealth != rec.batteryHealth) { oldHealth = rec.batteryHealth; - pw.print(" health="); + pw.print(checkin ? ",Bh=" : " health="); switch (oldHealth) { case BatteryManager.BATTERY_HEALTH_UNKNOWN: - pw.print("unknown"); + pw.print(checkin ? "?" : "unknown"); break; case BatteryManager.BATTERY_HEALTH_GOOD: - pw.print("good"); + pw.print(checkin ? "g" : "good"); break; case BatteryManager.BATTERY_HEALTH_OVERHEAT: - pw.print("overheat"); + pw.print(checkin ? "h" : "overheat"); break; case BatteryManager.BATTERY_HEALTH_DEAD: - pw.print("dead"); + pw.print(checkin ? "d" : "dead"); break; case BatteryManager.BATTERY_HEALTH_OVER_VOLTAGE: - pw.print("over-voltage"); + pw.print(checkin ? "v" : "over-voltage"); break; case BatteryManager.BATTERY_HEALTH_UNSPECIFIED_FAILURE: - pw.print("failure"); + pw.print(checkin ? "f" : "failure"); + break; + case BatteryManager.BATTERY_HEALTH_COLD: + pw.print(checkin ? "c" : "cold"); break; default: pw.print(oldHealth); @@ -2260,19 +2896,19 @@ public abstract class BatteryStats implements Parcelable { } if (oldPlug != rec.batteryPlugType) { oldPlug = rec.batteryPlugType; - pw.print(" plug="); + pw.print(checkin ? ",Bp=" : " plug="); switch (oldPlug) { case 0: - pw.print("none"); + pw.print(checkin ? "n" : "none"); break; case BatteryManager.BATTERY_PLUGGED_AC: - pw.print("ac"); + pw.print(checkin ? "a" : "ac"); break; case BatteryManager.BATTERY_PLUGGED_USB: - pw.print("usb"); + pw.print(checkin ? "u" : "usb"); break; case BatteryManager.BATTERY_PLUGGED_WIRELESS: - pw.print("wireless"); + pw.print(checkin ? "w" : "wireless"); break; default: pw.print(oldPlug); @@ -2281,139 +2917,248 @@ public abstract class BatteryStats implements Parcelable { } if (oldTemp != rec.batteryTemperature) { oldTemp = rec.batteryTemperature; - pw.print(" temp="); + pw.print(checkin ? ",Bt=" : " temp="); pw.print(oldTemp); } if (oldVolt != rec.batteryVoltage) { oldVolt = rec.batteryVoltage; - pw.print(" volt="); + pw.print(checkin ? ",Bv=" : " volt="); pw.print(oldVolt); } - printBitDescriptions(pw, oldState, rec.states, - HISTORY_STATE_DESCRIPTIONS); + printBitDescriptions(pw, oldState, rec.states, rec.wakelockTag, + HISTORY_STATE_DESCRIPTIONS, !checkin); + if (rec.wakeReasonTag != null) { + if (checkin) { + pw.print(",Wr="); + pw.print(rec.wakeReasonTag.poolIdx); + } else { + pw.print(" wake_reason="); + pw.print(rec.wakeReasonTag.uid); + pw.print(":\""); + pw.print(rec.wakeReasonTag.string); + pw.print("\""); + } + } + if (rec.eventCode != HistoryItem.EVENT_NONE) { + pw.print(checkin ? "," : " "); + if ((rec.eventCode&HistoryItem.EVENT_FLAG_START) != 0) { + pw.print("+"); + } else if ((rec.eventCode&HistoryItem.EVENT_FLAG_FINISH) != 0) { + pw.print("-"); + } + String[] eventNames = checkin ? HISTORY_EVENT_CHECKIN_NAMES + : HISTORY_EVENT_NAMES; + int idx = rec.eventCode & ~(HistoryItem.EVENT_FLAG_START + | HistoryItem.EVENT_FLAG_FINISH); + if (idx >= 0 && idx < eventNames.length) { + pw.print(eventNames[idx]); + } else { + pw.print(checkin ? "Ev" : "event"); + pw.print(idx); + } + pw.print("="); + if (checkin) { + pw.print(rec.eventTag.poolIdx); + } else { + UserHandle.formatUid(pw, rec.eventTag.uid); + pw.print(":\""); + pw.print(rec.eventTag.string); + pw.print("\""); + } + } pw.println(); + oldState = rec.states; } - oldState = rec.states; } + } - public void printNextItemCheckin(PrintWriter pw, HistoryItem rec, long now) { - pw.print(rec.time-now); - pw.print(","); - if (rec.cmd == HistoryItem.CMD_START) { - pw.print("start"); - } else if (rec.cmd == HistoryItem.CMD_OVERFLOW) { - pw.print("overflow"); - } else { - pw.print(rec.batteryLevel); - pw.print(","); - pw.print(rec.states); - pw.print(","); - pw.print(rec.batteryStatus); - pw.print(","); - pw.print(rec.batteryHealth); - pw.print(","); - pw.print(rec.batteryPlugType); - pw.print(","); - pw.print((int)rec.batteryTemperature); - pw.print(","); - pw.print((int)rec.batteryVoltage); - } + private void printSizeValue(PrintWriter pw, long size) { + float result = size; + String suffix = ""; + if (result >= 10*1024) { + suffix = "KB"; + result = result / 1024; + } + if (result >= 10*1024) { + suffix = "MB"; + result = result / 1024; } + if (result >= 10*1024) { + suffix = "GB"; + result = result / 1024; + } + if (result >= 10*1024) { + suffix = "TB"; + result = result / 1024; + } + if (result >= 10*1024) { + suffix = "PB"; + result = result / 1024; + } + pw.print((int)result); + pw.print(suffix); } + public static final int DUMP_UNPLUGGED_ONLY = 1<<0; + public static final int DUMP_CHARGED_ONLY = 1<<1; + public static final int DUMP_HISTORY_ONLY = 1<<2; + public static final int DUMP_INCLUDE_HISTORY = 1<<3; + /** * Dumps a human-readable summary of the battery statistics to the given PrintWriter. * * @param pw a Printer to receive the dump output. */ @SuppressWarnings("unused") - public void dumpLocked(PrintWriter pw, boolean isUnpluggedOnly, int reqUid) { + public void dumpLocked(Context context, PrintWriter pw, int flags, int reqUid, long histStart) { prepareForDumpLocked(); - long now = getHistoryBaseTime() + SystemClock.elapsedRealtime(); + final boolean filtering = + (flags&(DUMP_HISTORY_ONLY|DUMP_UNPLUGGED_ONLY|DUMP_CHARGED_ONLY)) != 0; + + if ((flags&DUMP_HISTORY_ONLY) != 0 || !filtering) { + long now = getHistoryBaseTime() + SystemClock.elapsedRealtime(); + + final HistoryItem rec = new HistoryItem(); + final long historyTotalSize = getHistoryTotalSize(); + final long historyUsedSize = getHistoryUsedSize(); + if (startIteratingHistoryLocked()) { + try { + pw.print("Battery History ("); + pw.print((100*historyUsedSize)/historyTotalSize); + pw.print("% used, "); + printSizeValue(pw, historyUsedSize); + pw.print(" used of "); + printSizeValue(pw, historyTotalSize); + pw.print(", "); + pw.print(getHistoryStringPoolSize()); + pw.print(" strings using "); + printSizeValue(pw, getHistoryStringPoolBytes()); + pw.println("):"); + HistoryPrinter hprinter = new HistoryPrinter(); + long lastTime = -1; + while (getNextHistoryLocked(rec)) { + lastTime = rec.time; + if (rec.time >= histStart) { + hprinter.printNextItem(pw, rec, histStart >= 0 ? -1 : now, false); + } + } + if (histStart >= 0) { + pw.print(" NEXT: "); pw.println(lastTime+1); + } + pw.println(); + } finally { + finishIteratingHistoryLocked(); + } + } - final HistoryItem rec = new HistoryItem(); - if (startIteratingHistoryLocked()) { - pw.println("Battery History:"); - HistoryPrinter hprinter = new HistoryPrinter(); - while (getNextHistoryLocked(rec)) { - hprinter.printNextItem(pw, rec, now); + if (startIteratingOldHistoryLocked()) { + try { + pw.println("Old battery History:"); + HistoryPrinter hprinter = new HistoryPrinter(); + while (getNextOldHistoryLocked(rec)) { + hprinter.printNextItem(pw, rec, now, false); + } + pw.println(); + } finally { + finishIteratingOldHistoryLocked(); + } } - finishIteratingHistoryLocked(); - pw.println(""); } - if (startIteratingOldHistoryLocked()) { - pw.println("Old battery History:"); - HistoryPrinter hprinter = new HistoryPrinter(); - while (getNextOldHistoryLocked(rec)) { - hprinter.printNextItem(pw, rec, now); - } - finishIteratingOldHistoryLocked(); - pw.println(""); + if (filtering && (flags&(DUMP_UNPLUGGED_ONLY|DUMP_CHARGED_ONLY)) == 0) { + return; } - - SparseArray<? extends Uid> uidStats = getUidStats(); - final int NU = uidStats.size(); - boolean didPid = false; - long nowRealtime = SystemClock.elapsedRealtime(); - for (int i=0; i<NU; i++) { - Uid uid = uidStats.valueAt(i); - SparseArray<? extends Uid.Pid> pids = uid.getPidStats(); - if (pids != null) { - for (int j=0; j<pids.size(); j++) { - Uid.Pid pid = pids.valueAt(j); - if (!didPid) { - pw.println("Per-PID Stats:"); - didPid = true; + + if (!filtering) { + SparseArray<? extends Uid> uidStats = getUidStats(); + final int NU = uidStats.size(); + boolean didPid = false; + long nowRealtime = SystemClock.elapsedRealtime(); + for (int i=0; i<NU; i++) { + Uid uid = uidStats.valueAt(i); + SparseArray<? extends Uid.Pid> pids = uid.getPidStats(); + if (pids != null) { + for (int j=0; j<pids.size(); j++) { + Uid.Pid pid = pids.valueAt(j); + if (!didPid) { + pw.println("Per-PID Stats:"); + didPid = true; + } + long time = pid.mWakeSumMs + (pid.mWakeNesting > 0 + ? (nowRealtime - pid.mWakeStartMs) : 0); + pw.print(" PID "); pw.print(pids.keyAt(j)); + pw.print(" wake time: "); + TimeUtils.formatDuration(time, pw); + pw.println(""); } - long time = pid.mWakeSum + (pid.mWakeStart != 0 - ? (nowRealtime - pid.mWakeStart) : 0); - pw.print(" PID "); pw.print(pids.keyAt(j)); - pw.print(" wake time: "); - TimeUtils.formatDuration(time, pw); - pw.println(""); } } - } - if (didPid) { - pw.println(""); + if (didPid) { + pw.println(""); + } } - if (!isUnpluggedOnly) { + if (!filtering || (flags&DUMP_CHARGED_ONLY) != 0) { pw.println("Statistics since last charge:"); pw.println(" System starts: " + getStartCount() + ", currently on battery: " + getIsOnBattery()); - dumpLocked(pw, "", STATS_SINCE_CHARGED, reqUid); + dumpLocked(context, pw, "", STATS_SINCE_CHARGED, reqUid); pw.println(""); } - pw.println("Statistics since last unplugged:"); - dumpLocked(pw, "", STATS_SINCE_UNPLUGGED, reqUid); + if (!filtering || (flags&DUMP_UNPLUGGED_ONLY) != 0) { + pw.println("Statistics since last unplugged:"); + dumpLocked(context, pw, "", STATS_SINCE_UNPLUGGED, reqUid); + } } @SuppressWarnings("unused") - public void dumpCheckinLocked( - PrintWriter pw, List<ApplicationInfo> apps, boolean isUnpluggedOnly, - boolean includeHistory) { + public void dumpCheckinLocked(Context context, PrintWriter pw, + List<ApplicationInfo> apps, int flags, long histStart) { prepareForDumpLocked(); long now = getHistoryBaseTime() + SystemClock.elapsedRealtime(); - if (includeHistory) { + final boolean filtering = + (flags&(DUMP_HISTORY_ONLY|DUMP_UNPLUGGED_ONLY|DUMP_CHARGED_ONLY)) != 0; + + if ((flags&DUMP_INCLUDE_HISTORY) != 0 || (flags&DUMP_HISTORY_ONLY) != 0) { final HistoryItem rec = new HistoryItem(); if (startIteratingHistoryLocked()) { - HistoryPrinter hprinter = new HistoryPrinter(); - while (getNextHistoryLocked(rec)) { - pw.print(BATTERY_STATS_CHECKIN_VERSION); pw.print(','); - pw.print(0); pw.print(','); - pw.print(HISTORY_DATA); pw.print(','); - hprinter.printNextItemCheckin(pw, rec, now); - pw.println(); + try { + for (int i=0; i<getHistoryStringPoolSize(); i++) { + pw.print(BATTERY_STATS_CHECKIN_VERSION); pw.print(','); + pw.print(HISTORY_STRING_POOL); pw.print(','); + pw.print(i); + pw.print(','); + pw.print(getHistoryTagPoolString(i)); + pw.print(','); + pw.print(getHistoryTagPoolUid(i)); + pw.println(); + } + HistoryPrinter hprinter = new HistoryPrinter(); + long lastTime = -1; + while (getNextHistoryLocked(rec)) { + lastTime = rec.time; + if (rec.time >= histStart) { + pw.print(BATTERY_STATS_CHECKIN_VERSION); pw.print(','); + pw.print(HISTORY_DATA); pw.print(','); + hprinter.printNextItem(pw, rec, histStart >= 0 ? -1 : now, true); + } + } + if (histStart >= 0) { + pw.print("NEXT: "); pw.println(lastTime+1); + } + } finally { + finishIteratingHistoryLocked(); } - finishIteratingHistoryLocked(); } } + if (filtering && (flags&(DUMP_UNPLUGGED_ONLY|DUMP_CHARGED_ONLY)) == 0) { + return; + } + if (apps != null) { SparseArray<ArrayList<String>> uids = new SparseArray<ArrayList<String>>(); for (int i=0; i<apps.size(); i++) { @@ -2441,12 +3186,11 @@ public abstract class BatteryStats implements Parcelable { } } } - if (isUnpluggedOnly) { - dumpCheckinLocked(pw, STATS_SINCE_UNPLUGGED, -1); + if (!filtering || (flags&DUMP_CHARGED_ONLY) != 0) { + dumpCheckinLocked(context, pw, STATS_SINCE_CHARGED, -1); } - else { - dumpCheckinLocked(pw, STATS_SINCE_CHARGED, -1); - dumpCheckinLocked(pw, STATS_SINCE_UNPLUGGED, -1); + if (!filtering || (flags&DUMP_UNPLUGGED_ONLY) != 0) { + dumpCheckinLocked(context, pw, STATS_SINCE_UNPLUGGED, -1); } } } diff --git a/core/java/android/os/Broadcaster.java b/core/java/android/os/Broadcaster.java index 96dc61a..70dcdd8 100644 --- a/core/java/android/os/Broadcaster.java +++ b/core/java/android/os/Broadcaster.java @@ -171,10 +171,10 @@ public class Broadcaster public void broadcast(Message msg) { synchronized (this) { - if (mReg == null) { - return; - } - + if (mReg == null) { + return; + } + int senderWhat = msg.what; Registration start = mReg; Registration r = start; diff --git a/core/java/android/os/CommonClock.java b/core/java/android/os/CommonClock.java index 3a1da97..2ecf317 100644 --- a/core/java/android/os/CommonClock.java +++ b/core/java/android/os/CommonClock.java @@ -15,17 +15,8 @@ */ package android.os; -import java.net.InetAddress; -import java.net.Inet4Address; -import java.net.Inet6Address; import java.net.InetSocketAddress; import java.util.NoSuchElementException; -import static libcore.io.OsConstants.*; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; import android.os.Binder; import android.os.CommonTimeUtils; import android.os.IBinder; diff --git a/core/java/android/os/CommonTimeConfig.java b/core/java/android/os/CommonTimeConfig.java index 3355ee3..1f9fab5 100644 --- a/core/java/android/os/CommonTimeConfig.java +++ b/core/java/android/os/CommonTimeConfig.java @@ -15,7 +15,6 @@ */ package android.os; -import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.NoSuchElementException; diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java index 2de1204..18730b6 100644 --- a/core/java/android/os/Debug.java +++ b/core/java/android/os/Debug.java @@ -26,7 +26,6 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; -import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.Reader; import java.lang.reflect.Field; @@ -41,7 +40,6 @@ import org.apache.harmony.dalvik.ddmc.ChunkHandler; import org.apache.harmony.dalvik.ddmc.DdmServer; import dalvik.bytecode.OpcodeInfo; -import dalvik.bytecode.Opcodes; import dalvik.system.VMDebug; diff --git a/core/java/android/os/DropBoxManager.java b/core/java/android/os/DropBoxManager.java index e1c1678..27001dc 100644 --- a/core/java/android/os/DropBoxManager.java +++ b/core/java/android/os/DropBoxManager.java @@ -16,14 +16,11 @@ package android.os; -import android.util.Log; - import com.android.internal.os.IDropBoxManagerService; import java.io.ByteArrayInputStream; import java.io.Closeable; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.zip.GZIPInputStream; diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index b5413db..54e2c0b 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -16,14 +16,13 @@ package android.os; +import android.app.admin.DevicePolicyManager; import android.content.Context; import android.os.storage.IMountService; -import android.os.storage.StorageManager; import android.os.storage.StorageVolume; import android.text.TextUtils; import android.util.Log; -import com.android.internal.annotations.GuardedBy; import com.google.android.collect.Lists; import java.io.File; @@ -66,33 +65,6 @@ public class Environment { private static UserEnvironment sCurrentUser; private static boolean sUserRequired; - private static final Object sLock = new Object(); - - @GuardedBy("sLock") - private static volatile StorageVolume sPrimaryVolume; - - private static StorageVolume getPrimaryVolume() { - if (SystemProperties.getBoolean("config.disable_storage", false)) { - return null; - } - - if (sPrimaryVolume == null) { - synchronized (sLock) { - if (sPrimaryVolume == null) { - try { - IMountService mountService = IMountService.Stub.asInterface(ServiceManager - .getService("mount")); - final StorageVolume[] volumes = mountService.getVolumeList(); - sPrimaryVolume = StorageManager.getPrimaryVolume(volumes); - } catch (Exception e) { - Log.e(TAG, "couldn't talk to MountService", e); - } - } - } - } - return sPrimaryVolume; - } - static { initForCurrentUser(); } @@ -101,10 +73,6 @@ public class Environment { public static void initForCurrentUser() { final int userId = UserHandle.myUserId(); sCurrentUser = new UserEnvironment(userId); - - synchronized (sLock) { - sPrimaryVolume = null; - } } /** {@hide} */ @@ -603,28 +571,28 @@ public class Environment { * Unknown storage state, such as when a path isn't backed by known storage * media. * - * @see #getStorageState(File) + * @see #getExternalStorageState(File) */ public static final String MEDIA_UNKNOWN = "unknown"; /** * Storage state if the media is not present. * - * @see #getStorageState(File) + * @see #getExternalStorageState(File) */ public static final String MEDIA_REMOVED = "removed"; /** * Storage state if the media is present but not mounted. * - * @see #getStorageState(File) + * @see #getExternalStorageState(File) */ public static final String MEDIA_UNMOUNTED = "unmounted"; /** * Storage state if the media is present and being disk-checked. * - * @see #getStorageState(File) + * @see #getExternalStorageState(File) */ public static final String MEDIA_CHECKING = "checking"; @@ -632,7 +600,7 @@ public class Environment { * Storage state if the media is present but is blank or is using an * unsupported filesystem. * - * @see #getStorageState(File) + * @see #getExternalStorageState(File) */ public static final String MEDIA_NOFS = "nofs"; @@ -640,7 +608,7 @@ public class Environment { * Storage state if the media is present and mounted at its mount point with * read/write access. * - * @see #getStorageState(File) + * @see #getExternalStorageState(File) */ public static final String MEDIA_MOUNTED = "mounted"; @@ -648,7 +616,7 @@ public class Environment { * Storage state if the media is present and mounted at its mount point with * read-only access. * - * @see #getStorageState(File) + * @see #getExternalStorageState(File) */ public static final String MEDIA_MOUNTED_READ_ONLY = "mounted_ro"; @@ -656,14 +624,14 @@ public class Environment { * Storage state if the media is present not mounted, and shared via USB * mass storage. * - * @see #getStorageState(File) + * @see #getExternalStorageState(File) */ public static final String MEDIA_SHARED = "shared"; /** * Storage state if the media was removed before it was unmounted. * - * @see #getStorageState(File) + * @see #getExternalStorageState(File) */ public static final String MEDIA_BAD_REMOVAL = "bad_removal"; @@ -671,7 +639,7 @@ public class Environment { * Storage state if the media is present but cannot be mounted. Typically * this happens if the file system on the media is corrupted. * - * @see #getStorageState(File) + * @see #getExternalStorageState(File) */ public static final String MEDIA_UNMOUNTABLE = "unmountable"; @@ -687,7 +655,15 @@ public class Environment { */ public static String getExternalStorageState() { final File externalDir = sCurrentUser.getExternalDirsForApp()[0]; - return getStorageState(externalDir); + return getExternalStorageState(externalDir); + } + + /** + * @deprecated use {@link #getExternalStorageState(File)} + */ + @Deprecated + public static String getStorageState(File path) { + return getExternalStorageState(path); } /** @@ -700,59 +676,81 @@ public class Environment { * {@link #MEDIA_MOUNTED_READ_ONLY}, {@link #MEDIA_SHARED}, * {@link #MEDIA_BAD_REMOVAL}, or {@link #MEDIA_UNMOUNTABLE}. */ - public static String getStorageState(File path) { - final String rawPath; - try { - rawPath = path.getCanonicalPath(); - } catch (IOException e) { - Log.w(TAG, "Failed to resolve target path: " + e); - return Environment.MEDIA_UNKNOWN; - } - - try { + public static String getExternalStorageState(File path) { + final StorageVolume volume = getStorageVolume(path); + if (volume != null) { final IMountService mountService = IMountService.Stub.asInterface( ServiceManager.getService("mount")); - final StorageVolume[] volumes = mountService.getVolumeList(); - for (StorageVolume volume : volumes) { - if (rawPath.startsWith(volume.getPath())) { - return mountService.getVolumeState(volume.getPath()); - } + try { + return mountService.getVolumeState(volume.getPath()); + } catch (RemoteException e) { } - } catch (RemoteException e) { - Log.w(TAG, "Failed to find external storage state: " + e); } + return Environment.MEDIA_UNKNOWN; } /** * Returns whether the primary "external" storage device is removable. - * If true is returned, this device is for example an SD card that the - * user can remove. If false is returned, the storage is built into - * the device and can not be physically removed. * - * <p>See {@link #getExternalStorageDirectory()} for more information. + * @return true if the storage device can be removed (such as an SD card), + * or false if the storage device is built in and cannot be + * physically removed. */ public static boolean isExternalStorageRemovable() { - final StorageVolume primary = getPrimaryVolume(); - return (primary != null && primary.isRemovable()); + if (isStorageDisabled()) return false; + final File externalDir = sCurrentUser.getExternalDirsForApp()[0]; + return isExternalStorageRemovable(externalDir); } /** - * Returns whether the device has an external storage device which is - * emulated. If true, the device does not have real external storage, and the directory - * returned by {@link #getExternalStorageDirectory()} will be allocated using a portion of - * the internal storage system. + * Returns whether the storage device that provides the given path is + * removable. * - * <p>Certain system services, such as the package manager, use this - * to determine where to install an application. + * @return true if the storage device can be removed (such as an SD card), + * or false if the storage device is built in and cannot be + * physically removed. + * @throws IllegalArgumentException if the path is not a valid storage + * device. + */ + public static boolean isExternalStorageRemovable(File path) { + final StorageVolume volume = getStorageVolume(path); + if (volume != null) { + return volume.isRemovable(); + } else { + throw new IllegalArgumentException("Failed to find storage device at " + path); + } + } + + /** + * Returns whether the primary "external" storage device is emulated. If + * true, data stored on this device will be stored on a portion of the + * internal storage system. * - * <p>Emulated external storage may also be encrypted - see - * {@link android.app.admin.DevicePolicyManager#setStorageEncryption( - * android.content.ComponentName, boolean)} for additional details. + * @see DevicePolicyManager#setStorageEncryption(android.content.ComponentName, + * boolean) */ public static boolean isExternalStorageEmulated() { - final StorageVolume primary = getPrimaryVolume(); - return (primary != null && primary.isEmulated()); + if (isStorageDisabled()) return false; + final File externalDir = sCurrentUser.getExternalDirsForApp()[0]; + return isExternalStorageEmulated(externalDir); + } + + /** + * Returns whether the storage device that provides the given path is + * emulated. If true, data stored on this device will be stored on a portion + * of the internal storage system. + * + * @throws IllegalArgumentException if the path is not a valid storage + * device. + */ + public static boolean isExternalStorageEmulated(File path) { + final StorageVolume volume = getStorageVolume(path); + if (volume != null) { + return volume.isEmulated(); + } else { + throw new IllegalArgumentException("Failed to find storage device at " + path); + } } static File getDirectory(String variableName, String defaultPath) { @@ -815,6 +813,32 @@ public class Environment { return cur; } + private static boolean isStorageDisabled() { + return SystemProperties.getBoolean("config.disable_storage", false); + } + + private static StorageVolume getStorageVolume(File path) { + try { + path = path.getCanonicalFile(); + } catch (IOException e) { + return null; + } + + try { + final IMountService mountService = IMountService.Stub.asInterface( + ServiceManager.getService("mount")); + final StorageVolume[] volumes = mountService.getVolumeList(); + for (StorageVolume volume : volumes) { + if (FileUtils.contains(volume.getPathFile(), path)) { + return volume; + } + } + } catch (RemoteException e) { + } + + return null; + } + /** * If the given path exists on emulated external storage, return the * translated backing path hosted on internal storage. This bypasses any diff --git a/core/java/android/os/FileObserver.java b/core/java/android/os/FileObserver.java index d633486..4e705e0 100644 --- a/core/java/android/os/FileObserver.java +++ b/core/java/android/os/FileObserver.java @@ -18,10 +18,7 @@ package android.os; import android.util.Log; -import com.android.internal.os.RuntimeInit; - import java.lang.ref.WeakReference; -import java.util.ArrayList; import java.util.HashMap; /** diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java index ff3e277..dc18dee 100644 --- a/core/java/android/os/FileUtils.java +++ b/core/java/android/os/FileUtils.java @@ -20,9 +20,7 @@ import android.util.Log; import android.util.Slog; import libcore.io.ErrnoException; -import libcore.io.IoUtils; import libcore.io.Libcore; -import libcore.io.OsConstants; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; @@ -328,14 +326,15 @@ public class FileUtils { * * @param minCount Always keep at least this many files. * @param minAge Always keep files younger than this age. + * @return if any files were deleted. */ - public static void deleteOlderFiles(File dir, int minCount, long minAge) { + public static boolean deleteOlderFiles(File dir, int minCount, long minAge) { if (minCount < 0 || minAge < 0) { throw new IllegalArgumentException("Constraints must be positive or 0"); } final File[] files = dir.listFiles(); - if (files == null) return; + if (files == null) return false; // Sort with newest files first Arrays.sort(files, new Comparator<File>() { @@ -346,15 +345,41 @@ public class FileUtils { }); // Keep at least minCount files + boolean deleted = false; for (int i = minCount; i < files.length; i++) { final File file = files[i]; // Keep files newer than minAge final long age = System.currentTimeMillis() - file.lastModified(); if (age > minAge) { - Log.d(TAG, "Deleting old file " + file); - file.delete(); + if (file.delete()) { + Log.d(TAG, "Deleted old file " + file); + deleted = true; + } } } + return deleted; + } + + /** + * Test if a file lives under the given directory, either as a direct child + * or a distant grandchild. + * <p> + * Both files <em>must</em> have been resolved using + * {@link File#getCanonicalFile()} to avoid symlink or path traversal + * attacks. + */ + public static boolean contains(File dir, File file) { + String dirPath = dir.getPath(); + String filePath = file.getPath(); + + if (dirPath.equals(filePath)) { + return true; + } + + if (!dirPath.endsWith("/")) { + dirPath += "/"; + } + return filePath.startsWith(dirPath); } } diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java index e6886c4..44367f3 100644 --- a/core/java/android/os/Handler.java +++ b/core/java/android/os/Handler.java @@ -330,6 +330,7 @@ public class Handler { * Causes the Runnable r to be added to the message queue, to be run * at a specific time given by <var>uptimeMillis</var>. * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b> + * Time spent in deep sleep will add an additional delay to execution. * The runnable will be run on the thread to which this handler is attached. * * @param r The Runnable that will be executed. @@ -352,6 +353,7 @@ public class Handler { * Causes the Runnable r to be added to the message queue, to be run * at a specific time given by <var>uptimeMillis</var>. * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b> + * Time spent in deep sleep will add an additional delay to execution. * The runnable will be run on the thread to which this handler is attached. * * @param r The Runnable that will be executed. @@ -377,6 +379,8 @@ public class Handler { * after the specified amount of time elapses. * The runnable will be run on the thread to which this handler * is attached. + * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b> + * Time spent in deep sleep will add an additional delay to execution. * * @param r The Runnable that will be executed. * @param delayMillis The delay (in milliseconds) until the Runnable @@ -570,6 +574,7 @@ public class Handler { * Enqueue a message into the message queue after all pending messages * before the absolute time (in milliseconds) <var>uptimeMillis</var>. * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b> + * Time spent in deep sleep will add an additional delay to execution. * You will receive it in {@link #handleMessage}, in the thread attached * to this handler. * diff --git a/core/java/android/os/IBatteryPropertiesRegistrar.aidl b/core/java/android/os/IBatteryPropertiesRegistrar.aidl index 376f6c9..fd01802 100644 --- a/core/java/android/os/IBatteryPropertiesRegistrar.aidl +++ b/core/java/android/os/IBatteryPropertiesRegistrar.aidl @@ -17,6 +17,7 @@ package android.os; import android.os.IBatteryPropertiesListener; +import android.os.BatteryProperty; /** * {@hide} @@ -25,4 +26,5 @@ import android.os.IBatteryPropertiesListener; interface IBatteryPropertiesRegistrar { void registerListener(IBatteryPropertiesListener listener); void unregisterListener(IBatteryPropertiesListener listener); + int getProperty(in int id, out BatteryProperty prop); } diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java index a2432d6..73a0f65 100644 --- a/core/java/android/os/IBinder.java +++ b/core/java/android/os/IBinder.java @@ -17,7 +17,6 @@ package android.os; import java.io.FileDescriptor; -import java.io.PrintWriter; /** * Base interface for a remotable object, the core part of a lightweight diff --git a/core/java/android/os/INetworkActivityListener.aidl b/core/java/android/os/INetworkActivityListener.aidl new file mode 100644 index 0000000..24e6e55 --- /dev/null +++ b/core/java/android/os/INetworkActivityListener.aidl @@ -0,0 +1,24 @@ +/* Copyright 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.os; + +/** + * @hide + */ +oneway interface INetworkActivityListener +{ + void onNetworkActive(); +} diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl index 21b8ae5..e6a4c5a 100644 --- a/core/java/android/os/INetworkManagementService.aidl +++ b/core/java/android/os/INetworkManagementService.aidl @@ -23,6 +23,7 @@ import android.net.LinkAddress; import android.net.NetworkStats; import android.net.RouteInfo; import android.net.wifi.WifiConfiguration; +import android.os.INetworkActivityListener; /** * @hide @@ -306,14 +307,12 @@ interface INetworkManagementService * reference-counting if an idletimer already exists for given * {@code iface}. * - * {@code label} usually represents the network type of {@code iface}. - * Caller should ensure that {@code label} for an {@code iface} remains the - * same for all calls to addIdleTimer. + * {@code type} is the type of the interface, such as TYPE_MOBILE. * * Every {@code addIdleTimer} should be paired with a * {@link removeIdleTimer} to cleanup when the network disconnects. */ - void addIdleTimer(String iface, int timeout, String label); + void addIdleTimer(String iface, int timeout, int type); /** * Removes idletimer for an interface. @@ -441,4 +440,19 @@ interface INetworkManagementService * Determine whether the clatd (464xlat) service has been started */ boolean isClatdStarted(); + + /** + * Start listening for mobile activity state changes. + */ + void registerNetworkActivityListener(INetworkActivityListener listener); + + /** + * Stop listening for mobile activity state changes. + */ + void unregisterNetworkActivityListener(INetworkActivityListener listener); + + /** + * Check whether the mobile radio is currently active. + */ + boolean isNetworkActive(); } diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl index 56176a4..069285a 100644 --- a/core/java/android/os/IPowerManager.aidl +++ b/core/java/android/os/IPowerManager.aidl @@ -25,8 +25,10 @@ interface IPowerManager { // WARNING: The first four methods must remain the first three methods because their // transaction numbers must not change unless IPowerManager.cpp is also updated. - void acquireWakeLock(IBinder lock, int flags, String tag, String packageName, in WorkSource ws); - void acquireWakeLockWithUid(IBinder lock, int flags, String tag, String packageName, int uidtoblame); + void acquireWakeLock(IBinder lock, int flags, String tag, String packageName, in WorkSource ws, + String historyTag); + void acquireWakeLockWithUid(IBinder lock, int flags, String tag, String packageName, + int uidtoblame); void releaseWakeLock(IBinder lock, int flags); void updateWakeLockUids(IBinder lock, in int[] uids); diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index 3c9d0d9..6e6c06d 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -28,11 +28,13 @@ import android.graphics.Bitmap; */ interface IUserManager { UserInfo createUser(in String name, int flags); + UserInfo createRelatedUser(in String name, int flags, int relatedUserId); boolean removeUser(int userHandle); void setUserName(int userHandle, String name); void setUserIcon(int userHandle, in Bitmap icon); Bitmap getUserIcon(int userHandle); List<UserInfo> getUsers(boolean excludeDying); + List<UserInfo> getRelatedUsers(int userHandle); UserInfo getUserInfo(int userHandle); boolean isRestricted(); void setGuestEnabled(boolean enable); diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java index 21e9f6b..6d7c9cf 100644 --- a/core/java/android/os/Looper.java +++ b/core/java/android/os/Looper.java @@ -18,7 +18,6 @@ package android.os; import android.util.Log; import android.util.Printer; -import android.util.PrefixPrinter; /** * Class used to run a message loop for a thread. Threads by default do @@ -150,7 +149,7 @@ public final class Looper { + msg.callback + " what=" + msg.what); } - msg.recycle(); + msg.recycleUnchecked(); } } diff --git a/core/java/android/os/Message.java b/core/java/android/os/Message.java index 51203a4..d30bbc1 100644 --- a/core/java/android/os/Message.java +++ b/core/java/android/os/Message.java @@ -71,7 +71,14 @@ public final class Message implements Parcelable { */ public Messenger replyTo; - /** If set message is in use */ + /** If set message is in use. + * This flag is set when the message is enqueued and remains set while it + * is delivered and afterwards when it is recycled. The flag is only cleared + * when a new message is created or obtained since that is the only time that + * applications are allowed to modify the contents of the message. + * + * It is an error to attempt to enqueue or recycle a message that is already in use. + */ /*package*/ static final int FLAG_IN_USE = 1 << 0; /** If set message is asynchronous */ @@ -86,9 +93,9 @@ public final class Message implements Parcelable { /*package*/ Bundle data; - /*package*/ Handler target; + /*package*/ Handler target; - /*package*/ Runnable callback; + /*package*/ Runnable callback; // sometimes we store linked lists of these things /*package*/ Message next; @@ -109,6 +116,7 @@ public final class Message implements Parcelable { Message m = sPool; sPool = m.next; m.next = null; + m.flags = 0; // clear in-use flag sPoolSize--; return m; } @@ -241,12 +249,38 @@ public final class Message implements Parcelable { } /** - * Return a Message instance to the global pool. You MUST NOT touch - * the Message after calling this function -- it has effectively been - * freed. + * Return a Message instance to the global pool. + * <p> + * You MUST NOT touch the Message after calling this function because it has + * effectively been freed. It is an error to recycle a message that is currently + * enqueued or that is in the process of being delivered to a Handler. + * </p> */ public void recycle() { - clearForRecycle(); + if (isInUse()) { + throw new IllegalStateException("This message cannot be recycled because it " + + "is still in use."); + } + recycleUnchecked(); + } + + /** + * Recycles a Message that may be in-use. + * Used internally by the MessageQueue and Looper when disposing of queued Messages. + */ + void recycleUnchecked() { + // Mark the message as in use while it remains in the recycled object pool. + // Clear out all other details. + flags = FLAG_IN_USE; + what = 0; + arg1 = 0; + arg2 = 0; + obj = null; + replyTo = null; + when = 0; + target = null; + callback = null; + data = null; synchronized (sPoolSync) { if (sPoolSize < MAX_POOL_SIZE) { @@ -402,19 +436,6 @@ public final class Message implements Parcelable { } } - /*package*/ void clearForRecycle() { - flags = 0; - what = 0; - arg1 = 0; - arg2 = 0; - obj = null; - replyTo = null; - when = 0; - target = null; - callback = null; - data = null; - } - /*package*/ boolean isInUse() { return ((flags & FLAG_IN_USE) == FLAG_IN_USE); } diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java index 75f9813..01a23ce 100644 --- a/core/java/android/os/MessageQueue.java +++ b/core/java/android/os/MessageQueue.java @@ -16,7 +16,6 @@ package android.os; -import android.util.AndroidRuntimeException; import android.util.Log; import android.util.Printer; @@ -126,6 +125,14 @@ public final class MessageQueue { } Message next() { + // Return here if the message loop has already quit and been disposed. + // This can happen if the application tries to restart a looper after quit + // which is not supported. + final long ptr = mPtr; + if (ptr == 0) { + return null; + } + int pendingIdleHandlerCount = -1; // -1 only during first iteration int nextPollTimeoutMillis = 0; for (;;) { @@ -133,9 +140,7 @@ public final class MessageQueue { Binder.flushPendingCommands(); } - // We can assume mPtr != 0 because the loop is obviously still running. - // The looper will not call this method after the loop quits. - nativePollOnce(mPtr, nextPollTimeoutMillis); + nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { // Try to retrieve the next message. Return if found. @@ -163,7 +168,6 @@ public final class MessageQueue { } msg.next = null; if (false) Log.v("MessageQueue", "Returning message: " + msg); - msg.markInUse(); return msg; } } else { @@ -227,7 +231,7 @@ public final class MessageQueue { void quit(boolean safe) { if (!mQuitAllowed) { - throw new RuntimeException("Main thread not allowed to quit."); + throw new IllegalStateException("Main thread not allowed to quit."); } synchronized (this) { @@ -253,6 +257,7 @@ public final class MessageQueue { synchronized (this) { final int token = mNextBarrierToken++; final Message msg = Message.obtain(); + msg.markInUse(); msg.when = when; msg.arg1 = token; @@ -297,7 +302,7 @@ public final class MessageQueue { mMessages = p.next; needWake = mMessages == null || mMessages.target != null; } - p.recycle(); + p.recycleUnchecked(); // If the loop is quitting then it is already awake. // We can assume mPtr != 0 when mQuitting is false. @@ -308,21 +313,23 @@ public final class MessageQueue { } boolean enqueueMessage(Message msg, long when) { - if (msg.isInUse()) { - throw new AndroidRuntimeException(msg + " This message is already in use."); - } if (msg.target == null) { - throw new AndroidRuntimeException("Message must have a target."); + throw new IllegalArgumentException("Message must have a target."); + } + if (msg.isInUse()) { + throw new IllegalStateException(msg + " This message is already in use."); } synchronized (this) { if (mQuitting) { - RuntimeException e = new RuntimeException( + IllegalStateException e = new IllegalStateException( msg.target + " sending message to a Handler on a dead thread"); Log.w("MessageQueue", e.getMessage(), e); + msg.recycle(); return false; } + msg.markInUse(); msg.when = when; Message p = mMessages; boolean needWake; @@ -418,7 +425,7 @@ public final class MessageQueue { && (object == null || p.obj == object)) { Message n = p.next; mMessages = n; - p.recycle(); + p.recycleUnchecked(); p = n; } @@ -429,7 +436,7 @@ public final class MessageQueue { if (n.target == h && n.what == what && (object == null || n.obj == object)) { Message nn = n.next; - n.recycle(); + n.recycleUnchecked(); p.next = nn; continue; } @@ -452,7 +459,7 @@ public final class MessageQueue { && (object == null || p.obj == object)) { Message n = p.next; mMessages = n; - p.recycle(); + p.recycleUnchecked(); p = n; } @@ -463,7 +470,7 @@ public final class MessageQueue { if (n.target == h && n.callback == r && (object == null || n.obj == object)) { Message nn = n.next; - n.recycle(); + n.recycleUnchecked(); p.next = nn; continue; } @@ -486,7 +493,7 @@ public final class MessageQueue { && (object == null || p.obj == object)) { Message n = p.next; mMessages = n; - p.recycle(); + p.recycleUnchecked(); p = n; } @@ -496,7 +503,7 @@ public final class MessageQueue { if (n != null) { if (n.target == h && (object == null || n.obj == object)) { Message nn = n.next; - n.recycle(); + n.recycleUnchecked(); p.next = nn; continue; } @@ -510,7 +517,7 @@ public final class MessageQueue { Message p = mMessages; while (p != null) { Message n = p.next; - p.recycle(); + p.recycleUnchecked(); p = n; } mMessages = null; @@ -538,7 +545,7 @@ public final class MessageQueue { do { p = n; n = p.next; - p.recycle(); + p.recycleUnchecked(); } while (n != null); } } diff --git a/core/java/android/os/NullVibrator.java b/core/java/android/os/NullVibrator.java index ac6027f..af90bdb 100644 --- a/core/java/android/os/NullVibrator.java +++ b/core/java/android/os/NullVibrator.java @@ -16,8 +16,6 @@ package android.os; -import android.util.Log; - /** * Vibrator implementation that does nothing. * diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 7425f67..8e0ff08 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -29,6 +29,7 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; +import java.io.ObjectStreamClass; import java.io.Serializable; import java.lang.reflect.Field; import java.util.ArrayList; @@ -2067,7 +2068,7 @@ public final class Parcel { return readByte(); case VAL_SERIALIZABLE: - return readSerializable(); + return readSerializable(loader); case VAL_PARCELABLEARRAY: return readParcelableArray(loader); @@ -2204,6 +2205,10 @@ public final class Parcel { * wasn't found in the parcel. */ public final Serializable readSerializable() { + return readSerializable(null); + } + + private final Serializable readSerializable(final ClassLoader loader) { String name = readString(); if (name == null) { // For some reason we were unable to read the name of the Serializable (either there @@ -2215,14 +2220,27 @@ public final class Parcel { byte[] serializedData = createByteArray(); ByteArrayInputStream bais = new ByteArrayInputStream(serializedData); try { - ObjectInputStream ois = new ObjectInputStream(bais); + ObjectInputStream ois = new ObjectInputStream(bais) { + @Override + protected Class<?> resolveClass(ObjectStreamClass osClass) + throws IOException, ClassNotFoundException { + // try the custom classloader if provided + if (loader != null) { + Class<?> c = Class.forName(osClass.getName(), false, loader); + if (c != null) { + return c; + } + } + return super.resolveClass(osClass); + } + }; return (Serializable) ois.readObject(); } catch (IOException ioe) { throw new RuntimeException("Parcelable encountered " + "IOException reading a Serializable object (name = " + name + ")", ioe); } catch (ClassNotFoundException cnfe) { - throw new RuntimeException("Parcelable encountered" + + throw new RuntimeException("Parcelable encountered " + "ClassNotFoundException reading a Serializable object (name = " + name + ")", cnfe); } @@ -2234,6 +2252,7 @@ public final class Parcel { private static final HashMap<ClassLoader,HashMap<String,Parcelable.Creator>> mCreators = new HashMap<ClassLoader,HashMap<String,Parcelable.Creator>>(); + /** @hide for internal use only. */ static protected final Parcel obtain(int obj) { throw new UnsupportedOperationException(); } diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java index 5273c20..86dc8b4 100644 --- a/core/java/android/os/ParcelFileDescriptor.java +++ b/core/java/android/os/ParcelFileDescriptor.java @@ -872,6 +872,8 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { */ @Override public void writeToParcel(Parcel out, int flags) { + // WARNING: This must stay in sync with Parcel::readParcelFileDescriptor() + // in frameworks/native/libs/binder/Parcel.cpp if (mWrapped != null) { try { mWrapped.writeToParcel(out, flags); @@ -897,6 +899,8 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { = new Parcelable.Creator<ParcelFileDescriptor>() { @Override public ParcelFileDescriptor createFromParcel(Parcel in) { + // WARNING: This must stay in sync with Parcel::writeParcelFileDescriptor() + // in frameworks/native/libs/binder/Parcel.cpp final FileDescriptor fd = in.readRawFileDescriptor(); FileDescriptor commChannel = null; if (in.readInt() != 0) { diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 86ef479..74ca3bb 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -235,6 +235,13 @@ public final class PowerManager { public static final int ON_AFTER_RELEASE = 0x20000000; /** + * Wake lock flag: This wake lock is not important for logging events. If a later + * wake lock is acquired that is important, it will be considered the one to log. + * @hide + */ + public static final int UNIMPORTANT_FOR_LOGGING = 0x40000000; + + /** * Flag for {@link WakeLock#release release(int)} to defer releasing a * {@link #PROXIMITY_SCREEN_OFF_WAKE_LOCK} wake lock until the proximity sensor returns * a negative value. @@ -302,6 +309,18 @@ public final class PowerManager { */ public static final int GO_TO_SLEEP_REASON_TIMEOUT = 2; + /** + * The value to pass as the 'reason' argument to reboot() to + * reboot into recovery mode (for applying system updates, doing + * factory resets, etc.). + * <p> + * Requires the {@link android.Manifest.permission#RECOVERY} + * permission (in addition to + * {@link android.Manifest.permission#REBOOT}). + * </p> + */ + public static final String REBOOT_RECOVERY = "recovery"; + final Context mContext; final IPowerManager mService; final Handler mHandler; @@ -635,14 +654,15 @@ public final class PowerManager { * </p> */ public final class WakeLock { - private final int mFlags; - private final String mTag; + private int mFlags; + private String mTag; private final String mPackageName; private final IBinder mToken; private int mCount; private boolean mRefCounted = true; private boolean mHeld; private WorkSource mWorkSource; + private String mHistoryTag; private final Runnable mReleaser = new Runnable() { public void run() { @@ -729,7 +749,8 @@ public final class PowerManager { // been explicitly released by the keyguard. mHandler.removeCallbacks(mReleaser); try { - mService.acquireWakeLock(mToken, mFlags, mTag, mPackageName, mWorkSource); + mService.acquireWakeLock(mToken, mFlags, mTag, mPackageName, mWorkSource, + mHistoryTag); } catch (RemoteException e) { } mHeld = true; @@ -830,6 +851,22 @@ public final class PowerManager { } } + /** @hide */ + public void setTag(String tag) { + mTag = tag; + } + + /** @hide */ + public void setHistoryTag(String tag) { + mHistoryTag = tag; + } + + /** @hide */ + public void setUnimportantForLogging(boolean state) { + if (state) mFlags |= UNIMPORTANT_FOR_LOGGING; + else mFlags &= ~UNIMPORTANT_FOR_LOGGING; + } + @Override public String toString() { synchronized (mToken) { diff --git a/core/java/android/os/Registrant.java b/core/java/android/os/Registrant.java index c1780b9..705cc5d 100644 --- a/core/java/android/os/Registrant.java +++ b/core/java/android/os/Registrant.java @@ -20,7 +20,6 @@ import android.os.Handler; import android.os.Message; import java.lang.ref.WeakReference; -import java.util.HashMap; /** @hide */ public class Registrant diff --git a/core/java/android/os/RegistrantList.java b/core/java/android/os/RegistrantList.java index 56b9e2b..9ab61f5 100644 --- a/core/java/android/os/RegistrantList.java +++ b/core/java/android/os/RegistrantList.java @@ -17,10 +17,8 @@ package android.os; import android.os.Handler; -import android.os.Message; import java.util.ArrayList; -import java.util.HashMap; /** @hide */ public class RegistrantList diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java index d794ca6..ea71ad8 100644 --- a/core/java/android/os/StrictMode.java +++ b/core/java/android/os/StrictMode.java @@ -1449,7 +1449,11 @@ public final class StrictMode { if (policy.classInstanceLimit.size() == 0) { return; } - Runtime.getRuntime().gc(); + + System.gc(); + System.runFinalization(); + System.gc(); + // Note: classInstanceLimit is immutable, so this is lock-free for (Map.Entry<Class, Integer> entry : policy.classInstanceLimit.entrySet()) { Class klass = entry.getKey(); @@ -2005,7 +2009,10 @@ public final class StrictMode { // noticeably less responsive during orientation changes when activities are // being restarted. Granted, it is only a problem when StrictMode is enabled // but it is annoying. - Runtime.getRuntime().gc(); + + System.gc(); + System.runFinalization(); + System.gc(); long instances = VMDebug.countInstancesOfClass(klass, false); if (instances > limit) { diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java index 156600e..1479035 100644 --- a/core/java/android/os/SystemProperties.java +++ b/core/java/android/os/SystemProperties.java @@ -18,8 +18,6 @@ package android.os; import java.util.ArrayList; -import android.util.Log; - /** * Gives access to the system properties store. The system properties diff --git a/core/java/android/os/SystemService.java b/core/java/android/os/SystemService.java index f345271..41e7546 100644 --- a/core/java/android/os/SystemService.java +++ b/core/java/android/os/SystemService.java @@ -16,8 +16,6 @@ package android.os; -import android.util.Slog; - import com.google.android.collect.Maps; import java.util.HashMap; diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java index 3249bcb..57ed979 100644 --- a/core/java/android/os/Trace.java +++ b/core/java/android/os/Trace.java @@ -16,8 +16,6 @@ package android.os; -import android.util.Log; - /** * Writes trace events to the system trace buffer. These trace events can be * collected and visualized using the Systrace tool. diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index a3752a1..1ec5cd5 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -17,7 +17,6 @@ package android.os; import android.app.ActivityManagerNative; import android.content.Context; -import android.content.RestrictionEntry; import android.content.pm.UserInfo; import android.content.res.Resources; import android.graphics.Bitmap; @@ -165,11 +164,13 @@ public class UserManager { /** * Returns whether the system supports multiple users. - * @return true if multiple users can be created, false if it is a single user device. + * @return true if multiple users can be created by user, false if it is a single user device. * @hide */ public static boolean supportsMultipleUsers() { - return getMaxSupportedUsers() > 1; + return getMaxSupportedUsers() > 1 + && SystemProperties.getBoolean("fw.show_multiuserui", + Resources.getSystem().getBoolean(R.bool.config_enableMultiUserUI)); } /** @@ -409,6 +410,27 @@ public class UserManager { } /** + * Creates a user with the specified name and options. + * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. + * + * @param name the user's name + * @param flags flags that identify the type of user and other properties. + * @see UserInfo + * @param relatedUserId new user will be related to this user id. + * + * @return the UserInfo object for the created user, or null if the user could not be created. + * @hide + */ + public UserInfo createRelatedUser(String name, int flags, int relatedUserId) { + try { + return mService.createRelatedUser(name, flags, relatedUserId); + } catch (RemoteException re) { + Log.w(TAG, "Could not create a user", re); + return null; + } + } + + /** * Return the number of users currently created on the device. */ public int getUserCount() { @@ -432,6 +454,22 @@ public class UserManager { } /** + * Returns information for all users related to userId + * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. + * @param userHandle users related to this user id will be returned. + * @return the list of related users. + * @hide + */ + public List<UserInfo> getRelatedUsers(int userHandle) { + try { + return mService.getRelatedUsers(userHandle); + } catch (RemoteException re) { + Log.w(TAG, "Could not get user list", re); + return null; + } + } + + /** * Returns information for all users on this device. * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. * @param excludeDying specify if the list should exclude users being removed. @@ -565,6 +603,26 @@ public class UserManager { } /** + * Returns true if the user switcher should be shown, this will be if there + * are multiple users that aren't managed profiles. + * @hide + * @return true if user switcher should be shown. + */ + public boolean isUserSwitcherEnabled() { + List<UserInfo> users = getUsers(true); + if (users == null) { + return false; + } + int switchableUserCount = 0; + for (UserInfo user : users) { + if (user.supportsSwitchTo()) { + ++switchableUserCount; + } + } + return switchableUserCount > 1; + } + + /** * Returns a serial number on this device for a given userHandle. User handles can be recycled * when deleting and creating users, but serial numbers are not reused until the device is wiped. * @param userHandle diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java index 51ba2f6..b97734e 100644 --- a/core/java/android/os/storage/IMountService.java +++ b/core/java/android/os/storage/IMountService.java @@ -642,12 +642,13 @@ public interface IMountService extends IInterface { return _result; } - public int changeEncryptionPassword(String password) throws RemoteException { + public int changeEncryptionPassword(int type, String password) throws RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); int _result; try { _data.writeInterfaceToken(DESCRIPTOR); + _data.writeInt(type); _data.writeString(password); mRemote.transact(Stub.TRANSACTION_changeEncryptionPassword, _data, _reply, 0); _reply.readException(); @@ -677,6 +678,22 @@ public interface IMountService extends IInterface { return _result; } + public int getPasswordType() throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + int _result; + try { + _data.writeInterfaceToken(DESCRIPTOR); + mRemote.transact(Stub.TRANSACTION_getPasswordType, _data, _reply, 0); + _reply.readException(); + _result = _reply.readInt(); + } finally { + _reply.recycle(); + _data.recycle(); + } + return _result; + } + public StorageVolume[] getVolumeList() throws RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); @@ -829,6 +846,8 @@ public interface IMountService extends IInterface { static final int TRANSACTION_mkdirs = IBinder.FIRST_CALL_TRANSACTION + 34; + static final int TRANSACTION_getPasswordType = IBinder.FIRST_CALL_TRANSACTION + 36; + /** * Cast an IBinder object into an IMountService interface, generating a * proxy if needed. @@ -1130,8 +1149,9 @@ public interface IMountService extends IInterface { } case TRANSACTION_changeEncryptionPassword: { data.enforceInterface(DESCRIPTOR); + int type = data.readInt(); String password = data.readString(); - int result = changeEncryptionPassword(password); + int result = changeEncryptionPassword(type, password); reply.writeNoException(); reply.writeInt(result); return true; @@ -1181,6 +1201,13 @@ public interface IMountService extends IInterface { reply.writeInt(result); return true; } + case TRANSACTION_getPasswordType: { + data.enforceInterface(DESCRIPTOR); + int result = getPasswordType(); + reply.writeNoException(); + reply.writeInt(result); + return true; + } } return super.onTransact(code, data, reply, flags); } @@ -1375,7 +1402,8 @@ public interface IMountService extends IInterface { /** * Changes the encryption password. */ - public int changeEncryptionPassword(String password) throws RemoteException; + public int changeEncryptionPassword(int type, String password) + throws RemoteException; /** * Verify the encryption password against the stored volume. This method @@ -1412,4 +1440,10 @@ public interface IMountService extends IInterface { * external storage data or OBB directory belonging to calling app. */ public int mkdirs(String callingPkg, String path) throws RemoteException; + + /** + * Determines the type of the encryption password + * @return PasswordType + */ + public int getPasswordType() throws RemoteException; } diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index f5e728d..4963991 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -58,6 +58,24 @@ import java.util.concurrent.atomic.AtomicInteger; * argument of {@link android.content.Context#STORAGE_SERVICE}. */ public class StorageManager { + + /// Consts to match the password types in cryptfs.h + /** Master key is encrypted with a password. + */ + public static final int CRYPT_TYPE_PASSWORD = 0; + + /** Master key is encrypted with the default password. + */ + public static final int CRYPT_TYPE_DEFAULT = 1; + + /** Master key is encrypted with a pattern. + */ + public static final int CRYPT_TYPE_PATTERN = 2; + + /** Master key is encrypted with a pin. + */ + public static final int CRYPT_TYPE_PIN = 3; + private static final String TAG = "StorageManager"; private final ContentResolver mResolver; diff --git a/core/java/android/preference/CheckBoxPreference.java b/core/java/android/preference/CheckBoxPreference.java index 1536760..1ce98b8 100644 --- a/core/java/android/preference/CheckBoxPreference.java +++ b/core/java/android/preference/CheckBoxPreference.java @@ -34,11 +34,16 @@ import android.widget.Checkable; */ public class CheckBoxPreference extends TwoStatePreference { - public CheckBoxPreference(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - TypedArray a = context.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.CheckBoxPreference, defStyle, 0); + public CheckBoxPreference(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public CheckBoxPreference( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + final TypedArray a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.CheckBoxPreference, defStyleAttr, defStyleRes); setSummaryOn(a.getString(com.android.internal.R.styleable.CheckBoxPreference_summaryOn)); setSummaryOff(a.getString(com.android.internal.R.styleable.CheckBoxPreference_summaryOff)); setDisableDependentsState(a.getBoolean( diff --git a/core/java/android/preference/DialogPreference.java b/core/java/android/preference/DialogPreference.java index a643c8a..b65eac7 100644 --- a/core/java/android/preference/DialogPreference.java +++ b/core/java/android/preference/DialogPreference.java @@ -64,12 +64,13 @@ public abstract class DialogPreference extends Preference implements /** Which button was clicked. */ private int mWhichButtonClicked; - - public DialogPreference(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - TypedArray a = context.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.DialogPreference, defStyle, 0); + + public DialogPreference( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + final TypedArray a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.DialogPreference, defStyleAttr, defStyleRes); mDialogTitle = a.getString(com.android.internal.R.styleable.DialogPreference_dialogTitle); if (mDialogTitle == null) { // Fallback on the regular title of the preference @@ -83,13 +84,20 @@ public abstract class DialogPreference extends Preference implements mDialogLayoutResId = a.getResourceId(com.android.internal.R.styleable.DialogPreference_dialogLayout, mDialogLayoutResId); a.recycle(); - + } + + public DialogPreference(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); } public DialogPreference(Context context, AttributeSet attrs) { this(context, attrs, com.android.internal.R.attr.dialogPreferenceStyle); } - + + public DialogPreference(Context context) { + this(context, null); + } + /** * Sets the title of the dialog. This will be shown on subsequent dialogs. * @@ -161,7 +169,7 @@ public abstract class DialogPreference extends Preference implements * @param dialogIconRes The icon, as a resource ID. */ public void setDialogIcon(int dialogIconRes) { - mDialogIcon = getContext().getResources().getDrawable(dialogIconRes); + mDialogIcon = getContext().getDrawable(dialogIconRes); } /** diff --git a/core/java/android/preference/EditTextPreference.java b/core/java/android/preference/EditTextPreference.java index aa27627..ff37042 100644 --- a/core/java/android/preference/EditTextPreference.java +++ b/core/java/android/preference/EditTextPreference.java @@ -49,9 +49,9 @@ public class EditTextPreference extends DialogPreference { private EditText mEditText; private String mText; - - public EditTextPreference(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + + public EditTextPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); mEditText = new EditText(context, attrs); @@ -67,6 +67,10 @@ public class EditTextPreference extends DialogPreference { mEditText.setEnabled(true); } + public EditTextPreference(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + public EditTextPreference(Context context, AttributeSet attrs) { this(context, attrs, com.android.internal.R.attr.editTextPreferenceStyle); } diff --git a/core/java/android/preference/GenericInflater.java b/core/java/android/preference/GenericInflater.java index 3003290..7de7d1c 100644 --- a/core/java/android/preference/GenericInflater.java +++ b/core/java/android/preference/GenericInflater.java @@ -191,7 +191,7 @@ abstract class GenericInflater<T, P extends GenericInflater.Parent> { public void setFactory(Factory<T> factory) { if (mFactorySet) { throw new IllegalStateException("" + - "A factory has already been set on this inflater"); + "A factory has already been set on this inflater"); } if (factory == null) { throw new NullPointerException("Given factory can not be null"); diff --git a/core/java/android/preference/ListPreference.java b/core/java/android/preference/ListPreference.java index 9edf112..8081a54 100644 --- a/core/java/android/preference/ListPreference.java +++ b/core/java/android/preference/ListPreference.java @@ -42,12 +42,12 @@ public class ListPreference extends DialogPreference { private String mSummary; private int mClickedDialogEntryIndex; private boolean mValueSet; - - public ListPreference(Context context, AttributeSet attrs) { - super(context, attrs); - - TypedArray a = context.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.ListPreference, 0, 0); + + public ListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + TypedArray a = context.obtainStyledAttributes( + attrs, com.android.internal.R.styleable.ListPreference, defStyleAttr, defStyleRes); mEntries = a.getTextArray(com.android.internal.R.styleable.ListPreference_entries); mEntryValues = a.getTextArray(com.android.internal.R.styleable.ListPreference_entryValues); a.recycle(); @@ -56,11 +56,19 @@ public class ListPreference extends DialogPreference { * in the Preference class. */ a = context.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.Preference, 0, 0); + com.android.internal.R.styleable.Preference, defStyleAttr, defStyleRes); mSummary = a.getString(com.android.internal.R.styleable.Preference_summary); a.recycle(); } + public ListPreference(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public ListPreference(Context context, AttributeSet attrs) { + this(context, attrs, com.android.internal.R.attr.dialogPreferenceStyle); + } + public ListPreference(Context context) { this(context, null); } diff --git a/core/java/android/preference/MultiCheckPreference.java b/core/java/android/preference/MultiCheckPreference.java index 6953075..57c906d 100644 --- a/core/java/android/preference/MultiCheckPreference.java +++ b/core/java/android/preference/MultiCheckPreference.java @@ -40,12 +40,13 @@ public class MultiCheckPreference extends DialogPreference { private boolean[] mSetValues; private boolean[] mOrigValues; private String mSummary; - - public MultiCheckPreference(Context context, AttributeSet attrs) { - super(context, attrs); - - TypedArray a = context.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.ListPreference, 0, 0); + + public MultiCheckPreference( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + TypedArray a = context.obtainStyledAttributes( + attrs, com.android.internal.R.styleable.ListPreference, defStyleAttr, defStyleRes); mEntries = a.getTextArray(com.android.internal.R.styleable.ListPreference_entries); if (mEntries != null) { setEntries(mEntries); @@ -63,6 +64,14 @@ public class MultiCheckPreference extends DialogPreference { a.recycle(); } + public MultiCheckPreference(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public MultiCheckPreference(Context context, AttributeSet attrs) { + this(context, attrs, com.android.internal.R.attr.dialogPreferenceStyle); + } + public MultiCheckPreference(Context context) { this(context, null); } diff --git a/core/java/android/preference/MultiSelectListPreference.java b/core/java/android/preference/MultiSelectListPreference.java index 553ce80..6c4c20f 100644 --- a/core/java/android/preference/MultiSelectListPreference.java +++ b/core/java/android/preference/MultiSelectListPreference.java @@ -44,16 +44,26 @@ public class MultiSelectListPreference extends DialogPreference { private Set<String> mValues = new HashSet<String>(); private Set<String> mNewValues = new HashSet<String>(); private boolean mPreferenceChanged; - - public MultiSelectListPreference(Context context, AttributeSet attrs) { - super(context, attrs); - - TypedArray a = context.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.MultiSelectListPreference, 0, 0); + + public MultiSelectListPreference( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + final TypedArray a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.MultiSelectListPreference, defStyleAttr, + defStyleRes); mEntries = a.getTextArray(com.android.internal.R.styleable.MultiSelectListPreference_entries); mEntryValues = a.getTextArray(com.android.internal.R.styleable.MultiSelectListPreference_entryValues); a.recycle(); } + + public MultiSelectListPreference(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public MultiSelectListPreference(Context context, AttributeSet attrs) { + this(context, attrs, com.android.internal.R.attr.dialogPreferenceStyle); + } public MultiSelectListPreference(Context context) { this(context, null); diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java index f7d1eb7..144c909 100644 --- a/core/java/android/preference/Preference.java +++ b/core/java/android/preference/Preference.java @@ -188,30 +188,33 @@ public class Preference implements Comparable<Preference> { /** * Perform inflation from XML and apply a class-specific base style. This - * constructor of Preference allows subclasses to use their own base - * style when they are inflating. For example, a {@link CheckBoxPreference} + * constructor of Preference allows subclasses to use their own base style + * when they are inflating. For example, a {@link CheckBoxPreference} * constructor calls this version of the super class constructor and - * supplies {@code android.R.attr.checkBoxPreferenceStyle} for <var>defStyle</var>. - * This allows the theme's checkbox preference style to modify all of the base - * preference attributes as well as the {@link CheckBoxPreference} class's - * attributes. - * + * supplies {@code android.R.attr.checkBoxPreferenceStyle} for + * <var>defStyleAttr</var>. This allows the theme's checkbox preference + * style to modify all of the base preference attributes as well as the + * {@link CheckBoxPreference} class's attributes. + * * @param context The Context this is associated with, through which it can - * access the current theme, resources, {@link SharedPreferences}, - * etc. - * @param attrs The attributes of the XML tag that is inflating the preference. - * @param defStyle The default style to apply to this preference. If 0, no style - * will be applied (beyond what is included in the theme). This - * may either be an attribute resource, whose value will be - * retrieved from the current theme, or an explicit style - * resource. + * access the current theme, resources, + * {@link SharedPreferences}, etc. + * @param attrs The attributes of the XML tag that is inflating the + * preference. + * @param defStyleAttr An attribute in the current theme that contains a + * reference to a style resource that supplies default values for + * the view. Can be 0 to not look for defaults. + * @param defStyleRes A resource identifier of a style resource that + * supplies default values for the view, used only if + * defStyleAttr is 0 or can not be found in the theme. Can be 0 + * to not look for defaults. * @see #Preference(Context, AttributeSet) */ - public Preference(Context context, AttributeSet attrs, int defStyle) { + public Preference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { mContext = context; - TypedArray a = context.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.Preference, defStyle, 0); + final TypedArray a = context.obtainStyledAttributes( + attrs, com.android.internal.R.styleable.Preference, defStyleAttr, defStyleRes); for (int i = a.getIndexCount(); i >= 0; i--) { int attr = a.getIndex(i); switch (attr) { @@ -281,6 +284,30 @@ public class Preference implements Comparable<Preference> { mCanRecycleLayout = false; } } + + /** + * Perform inflation from XML and apply a class-specific base style. This + * constructor of Preference allows subclasses to use their own base style + * when they are inflating. For example, a {@link CheckBoxPreference} + * constructor calls this version of the super class constructor and + * supplies {@code android.R.attr.checkBoxPreferenceStyle} for + * <var>defStyleAttr</var>. This allows the theme's checkbox preference + * style to modify all of the base preference attributes as well as the + * {@link CheckBoxPreference} class's attributes. + * + * @param context The Context this is associated with, through which it can + * access the current theme, resources, + * {@link SharedPreferences}, etc. + * @param attrs The attributes of the XML tag that is inflating the + * preference. + * @param defStyleAttr An attribute in the current theme that contains a + * reference to a style resource that supplies default values for + * the view. Can be 0 to not look for defaults. + * @see #Preference(Context, AttributeSet) + */ + public Preference(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } /** * Constructor that is called when inflating a Preference from XML. This is @@ -534,7 +561,7 @@ public class Preference implements Comparable<Preference> { if (imageView != null) { if (mIconResId != 0 || mIcon != null) { if (mIcon == null) { - mIcon = getContext().getResources().getDrawable(mIconResId); + mIcon = getContext().getDrawable(mIconResId); } if (mIcon != null) { imageView.setImageDrawable(mIcon); @@ -667,7 +694,7 @@ public class Preference implements Comparable<Preference> { */ public void setIcon(int iconResId) { mIconResId = iconResId; - setIcon(mContext.getResources().getDrawable(iconResId)); + setIcon(mContext.getDrawable(iconResId)); } /** diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java index 2ab5a91..0418049 100644 --- a/core/java/android/preference/PreferenceActivity.java +++ b/core/java/android/preference/PreferenceActivity.java @@ -33,7 +33,6 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import android.util.AttributeSet; -import android.util.Log; import android.util.TypedValue; import android.util.Xml; import android.view.LayoutInflater; @@ -794,8 +793,8 @@ public abstract class PreferenceActivity extends ListActivity implements if ("header".equals(nodeName)) { Header header = new Header(); - TypedArray sa = getResources().obtainAttributes(attrs, - com.android.internal.R.styleable.PreferenceHeader); + TypedArray sa = obtainStyledAttributes( + attrs, com.android.internal.R.styleable.PreferenceHeader); header.id = sa.getResourceId( com.android.internal.R.styleable.PreferenceHeader_id, (int)HEADER_ID_UNDEFINED); @@ -1173,7 +1172,7 @@ public abstract class PreferenceActivity extends ListActivity implements } } - private void switchToHeaderInner(String fragmentName, Bundle args, int direction) { + private void switchToHeaderInner(String fragmentName, Bundle args) { getFragmentManager().popBackStack(BACK_STACK_PREFS, FragmentManager.POP_BACK_STACK_INCLUSIVE); if (!isValidFragment(fragmentName)) { @@ -1196,7 +1195,7 @@ public abstract class PreferenceActivity extends ListActivity implements */ public void switchToHeader(String fragmentName, Bundle args) { setSelectedHeader(null); - switchToHeaderInner(fragmentName, args, 0); + switchToHeaderInner(fragmentName, args); } /** @@ -1215,8 +1214,7 @@ public abstract class PreferenceActivity extends ListActivity implements if (header.fragment == null) { throw new IllegalStateException("can't switch to header that has no fragment"); } - int direction = mHeaders.indexOf(header) - mHeaders.indexOf(mCurHeader); - switchToHeaderInner(header.fragment, header.fragmentArguments, direction); + switchToHeaderInner(header.fragment, header.fragmentArguments); setSelectedHeader(header); } } diff --git a/core/java/android/preference/PreferenceCategory.java b/core/java/android/preference/PreferenceCategory.java index 229a96a..253481b 100644 --- a/core/java/android/preference/PreferenceCategory.java +++ b/core/java/android/preference/PreferenceCategory.java @@ -16,8 +16,6 @@ package android.preference; -import java.util.Map; - import android.content.Context; import android.util.AttributeSet; @@ -34,9 +32,14 @@ import android.util.AttributeSet; */ public class PreferenceCategory extends PreferenceGroup { private static final String TAG = "PreferenceCategory"; - - public PreferenceCategory(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + + public PreferenceCategory( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public PreferenceCategory(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); } public PreferenceCategory(Context context, AttributeSet attrs) { diff --git a/core/java/android/preference/PreferenceFrameLayout.java b/core/java/android/preference/PreferenceFrameLayout.java index 75372aa..886338f 100644 --- a/core/java/android/preference/PreferenceFrameLayout.java +++ b/core/java/android/preference/PreferenceFrameLayout.java @@ -16,7 +16,6 @@ package android.preference; -import android.app.FragmentBreadCrumbs; import android.content.Context; import android.content.res.TypedArray; import android.util.AttributeSet; @@ -45,10 +44,15 @@ public class PreferenceFrameLayout extends FrameLayout { this(context, attrs, com.android.internal.R.attr.preferenceFrameLayoutStyle); } - public PreferenceFrameLayout(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - TypedArray a = context.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.PreferenceFrameLayout, defStyle, 0); + public PreferenceFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public PreferenceFrameLayout( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + final TypedArray a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.PreferenceFrameLayout, defStyleAttr, defStyleRes); float density = context.getResources().getDisplayMetrics().density; int defaultBorderTop = (int) (density * DEFAULT_BORDER_TOP + 0.5f); diff --git a/core/java/android/preference/PreferenceGroup.java b/core/java/android/preference/PreferenceGroup.java index 5f8c78d..2d35b1b 100644 --- a/core/java/android/preference/PreferenceGroup.java +++ b/core/java/android/preference/PreferenceGroup.java @@ -55,19 +55,23 @@ public abstract class PreferenceGroup extends Preference implements GenericInfla private int mCurrentPreferenceOrder = 0; private boolean mAttachedToActivity = false; - - public PreferenceGroup(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + + public PreferenceGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); mPreferenceList = new ArrayList<Preference>(); - TypedArray a = context.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.PreferenceGroup, defStyle, 0); + final TypedArray a = context.obtainStyledAttributes( + attrs, com.android.internal.R.styleable.PreferenceGroup, defStyleAttr, defStyleRes); mOrderingAsAdded = a.getBoolean(com.android.internal.R.styleable.PreferenceGroup_orderingFromXml, mOrderingAsAdded); a.recycle(); } + public PreferenceGroup(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + public PreferenceGroup(Context context, AttributeSet attrs) { this(context, attrs, 0); } diff --git a/core/java/android/preference/PreferenceInflater.java b/core/java/android/preference/PreferenceInflater.java index c21aa18..727fbca 100644 --- a/core/java/android/preference/PreferenceInflater.java +++ b/core/java/android/preference/PreferenceInflater.java @@ -19,16 +19,13 @@ package android.preference; import com.android.internal.util.XmlUtils; import java.io.IOException; -import java.util.Map; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; -import android.app.AliasActivity; import android.content.Context; import android.content.Intent; import android.util.AttributeSet; -import android.util.Log; /** * The {@link PreferenceInflater} is used to inflate preference hierarchies from diff --git a/core/java/android/preference/PreferenceManager.java b/core/java/android/preference/PreferenceManager.java index 17f88f1..5c8c8e9 100644 --- a/core/java/android/preference/PreferenceManager.java +++ b/core/java/android/preference/PreferenceManager.java @@ -800,8 +800,10 @@ public class PreferenceManager { * Interface definition for a callback to be invoked when a * {@link Preference} in the hierarchy rooted at this {@link PreferenceScreen} is * clicked. + * + * @hide */ - interface OnPreferenceTreeClickListener { + public interface OnPreferenceTreeClickListener { /** * Called when a preference in the tree rooted at this * {@link PreferenceScreen} has been clicked. diff --git a/core/java/android/preference/PreferenceScreen.java b/core/java/android/preference/PreferenceScreen.java index db80676..b1317e6 100644 --- a/core/java/android/preference/PreferenceScreen.java +++ b/core/java/android/preference/PreferenceScreen.java @@ -27,7 +27,6 @@ import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.Window; -import android.widget.AbsListView; import android.widget.Adapter; import android.widget.AdapterView; import android.widget.ListAdapter; diff --git a/core/java/android/preference/RingtonePreference.java b/core/java/android/preference/RingtonePreference.java index 2ebf294..488a0c4 100644 --- a/core/java/android/preference/RingtonePreference.java +++ b/core/java/android/preference/RingtonePreference.java @@ -50,11 +50,11 @@ public class RingtonePreference extends Preference implements private int mRequestCode; - public RingtonePreference(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - TypedArray a = context.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.RingtonePreference, defStyle, 0); + public RingtonePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + final TypedArray a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.RingtonePreference, defStyleAttr, defStyleRes); mRingtoneType = a.getInt(com.android.internal.R.styleable.RingtonePreference_ringtoneType, RingtoneManager.TYPE_RINGTONE); mShowDefault = a.getBoolean(com.android.internal.R.styleable.RingtonePreference_showDefault, @@ -64,6 +64,10 @@ public class RingtonePreference extends Preference implements a.recycle(); } + public RingtonePreference(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + public RingtonePreference(Context context, AttributeSet attrs) { this(context, attrs, com.android.internal.R.attr.ringtonePreferenceStyle); } diff --git a/core/java/android/preference/SeekBarDialogPreference.java b/core/java/android/preference/SeekBarDialogPreference.java index 0e89b16..9a08827 100644 --- a/core/java/android/preference/SeekBarDialogPreference.java +++ b/core/java/android/preference/SeekBarDialogPreference.java @@ -32,8 +32,9 @@ public class SeekBarDialogPreference extends DialogPreference { private Drawable mMyIcon; - public SeekBarDialogPreference(Context context, AttributeSet attrs) { - super(context, attrs); + public SeekBarDialogPreference( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); setDialogLayoutResource(com.android.internal.R.layout.seekbar_dialog); createActionButtons(); @@ -43,6 +44,18 @@ public class SeekBarDialogPreference extends DialogPreference { setDialogIcon(null); } + public SeekBarDialogPreference(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public SeekBarDialogPreference(Context context, AttributeSet attrs) { + this(context, attrs, com.android.internal.R.attr.dialogPreferenceStyle); + } + + public SeekBarDialogPreference(Context context) { + this(context, null); + } + // Allow subclasses to override the action buttons public void createActionButtons() { setPositiveButtonText(android.R.string.ok); diff --git a/core/java/android/preference/SeekBarPreference.java b/core/java/android/preference/SeekBarPreference.java index 7133d3a..e32890d 100644 --- a/core/java/android/preference/SeekBarPreference.java +++ b/core/java/android/preference/SeekBarPreference.java @@ -37,15 +37,20 @@ public class SeekBarPreference extends Preference private boolean mTrackingTouch; public SeekBarPreference( - Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - TypedArray a = context.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.ProgressBar, defStyle, 0); + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + final TypedArray a = context.obtainStyledAttributes( + attrs, com.android.internal.R.styleable.ProgressBar, defStyleAttr, defStyleRes); setMax(a.getInt(com.android.internal.R.styleable.ProgressBar_max, mMax)); a.recycle(); setLayoutResource(com.android.internal.R.layout.preference_widget_seekbar); } + public SeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + public SeekBarPreference(Context context, AttributeSet attrs) { this(context, attrs, 0); } diff --git a/core/java/android/preference/SwitchPreference.java b/core/java/android/preference/SwitchPreference.java index 8bac6bd..76ef544 100644 --- a/core/java/android/preference/SwitchPreference.java +++ b/core/java/android/preference/SwitchPreference.java @@ -60,13 +60,19 @@ public class SwitchPreference extends TwoStatePreference { * * @param context The Context that will style this preference * @param attrs Style attributes that differ from the default - * @param defStyle Theme attribute defining the default style options + * @param defStyleAttr An attribute in the current theme that contains a + * reference to a style resource that supplies default values for + * the view. Can be 0 to not look for defaults. + * @param defStyleRes A resource identifier of a style resource that + * supplies default values for the view, used only if + * defStyleAttr is 0 or can not be found in the theme. Can be 0 + * to not look for defaults. */ - public SwitchPreference(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public SwitchPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); TypedArray a = context.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.SwitchPreference, defStyle, 0); + com.android.internal.R.styleable.SwitchPreference, defStyleAttr, defStyleRes); setSummaryOn(a.getString(com.android.internal.R.styleable.SwitchPreference_summaryOn)); setSummaryOff(a.getString(com.android.internal.R.styleable.SwitchPreference_summaryOff)); setSwitchTextOn(a.getString( @@ -83,6 +89,19 @@ public class SwitchPreference extends TwoStatePreference { * * @param context The Context that will style this preference * @param attrs Style attributes that differ from the default + * @param defStyleAttr An attribute in the current theme that contains a + * reference to a style resource that supplies default values for + * the view. Can be 0 to not look for defaults. + */ + public SwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + /** + * Construct a new SwitchPreference with the given style options. + * + * @param context The Context that will style this preference + * @param attrs Style attributes that differ from the default */ public SwitchPreference(Context context, AttributeSet attrs) { this(context, attrs, com.android.internal.R.attr.switchPreferenceStyle); diff --git a/core/java/android/preference/TwoStatePreference.java b/core/java/android/preference/TwoStatePreference.java index af83953..6f8be1f 100644 --- a/core/java/android/preference/TwoStatePreference.java +++ b/core/java/android/preference/TwoStatePreference.java @@ -42,9 +42,13 @@ public abstract class TwoStatePreference extends Preference { private boolean mSendClickAccessibilityEvent; private boolean mDisableDependentsState; + public TwoStatePreference( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } - public TwoStatePreference(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public TwoStatePreference(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); } public TwoStatePreference(Context context, AttributeSet attrs) { diff --git a/core/java/android/preference/VolumePreference.java b/core/java/android/preference/VolumePreference.java index dc683a6..29f2545 100644 --- a/core/java/android/preference/VolumePreference.java +++ b/core/java/android/preference/VolumePreference.java @@ -51,15 +51,24 @@ public class VolumePreference extends SeekBarDialogPreference implements /** May be null if the dialog isn't visible. */ private SeekBarVolumizer mSeekBarVolumizer; - public VolumePreference(Context context, AttributeSet attrs) { - super(context, attrs); + public VolumePreference( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); - TypedArray a = context.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.VolumePreference, 0, 0); + final TypedArray a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.VolumePreference, defStyleAttr, defStyleRes); mStreamType = a.getInt(android.R.styleable.VolumePreference_streamType, 0); a.recycle(); } + public VolumePreference(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public VolumePreference(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + public void setStreamType(int streamType) { mStreamType = streamType; } diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java index a6f23a8..3b0d7ff 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -86,10 +86,8 @@ public class CallLog { public static final String ALLOW_VOICEMAILS_PARAM_KEY = "allow_voicemails"; /** - * Content uri with {@link #ALLOW_VOICEMAILS_PARAM_KEY} set. This can directly be used to - * access call log entries that includes voicemail records. - * - * @hide + * Content uri used to access call log entries, including voicemail records. You must have + * the READ_CALL_LOG and WRITE_CALL_LOG permissions to read and write to the call log. */ public static final Uri CONTENT_URI_WITH_VOICEMAIL = CONTENT_URI.buildUpon() .appendQueryParameter(ALLOW_VOICEMAILS_PARAM_KEY, "true") @@ -124,10 +122,7 @@ public class CallLog { public static final int OUTGOING_TYPE = 2; /** Call log type for missed calls. */ public static final int MISSED_TYPE = 3; - /** - * Call log type for voicemails. - * @hide - */ + /** Call log type for voicemails. */ public static final int VOICEMAIL_TYPE = 4; /** @@ -168,8 +163,6 @@ public class CallLog { * <P> * Type: TEXT * </P> - * - * @hide */ public static final String COUNTRY_ISO = "countryiso"; @@ -220,7 +213,6 @@ public class CallLog { /** * URI of the voicemail entry. Populated only for {@link #VOICEMAIL_TYPE}. * <P>Type: TEXT</P> - * @hide */ public static final String VOICEMAIL_URI = "voicemail_uri"; @@ -238,51 +230,48 @@ public class CallLog { * <p> * The string represents a city, state, or country associated with the number. * <P>Type: TEXT</P> - * @hide */ public static final String GEOCODED_LOCATION = "geocoded_location"; /** * The cached URI to look up the contact associated with the phone number, if it exists. - * This value is not guaranteed to be current, if the contact information - * associated with this number has changed. + * This value may not be current if the contact information associated with this number + * has changed. * <P>Type: TEXT</P> - * @hide */ public static final String CACHED_LOOKUP_URI = "lookup_uri"; /** * The cached phone number of the contact which matches this entry, if it exists. - * This value is not guaranteed to be current, if the contact information - * associated with this number has changed. + * This value may not be current if the contact information associated with this number + * has changed. * <P>Type: TEXT</P> - * @hide */ public static final String CACHED_MATCHED_NUMBER = "matched_number"; /** - * The cached normalized version of the phone number, if it exists. - * This value is not guaranteed to be current, if the contact information - * associated with this number has changed. + * The cached normalized(E164) version of the phone number, if it exists. + * This value may not be current if the contact information associated with this number + * has changed. * <P>Type: TEXT</P> - * @hide */ public static final String CACHED_NORMALIZED_NUMBER = "normalized_number"; /** * The cached photo id of the picture associated with the phone number, if it exists. - * This value is not guaranteed to be current, if the contact information - * associated with this number has changed. + * This value may not be current if the contact information associated with this number + * has changed. * <P>Type: INTEGER (long)</P> - * @hide */ public static final String CACHED_PHOTO_ID = "photo_id"; /** - * The cached formatted phone number. - * This value is not guaranteed to be present. + * The cached phone number, formatted with formatting rules based on the country the + * user was in when the call was made or received. + * This value is not guaranteed to be present, and may not be current if the contact + * information associated with this number + * has changed. * <P>Type: TEXT</P> - * @hide */ public static final String CACHED_FORMATTED_NUMBER = "formatted_number"; diff --git a/core/java/android/provider/Contacts.java b/core/java/android/provider/Contacts.java index c7e3c08..9e2aacd 100644 --- a/core/java/android/provider/Contacts.java +++ b/core/java/android/provider/Contacts.java @@ -26,7 +26,6 @@ import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; -import android.os.Build; import android.text.TextUtils; import android.util.Log; import android.widget.ImageView; diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index 0863368..11678a6 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -47,9 +47,6 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * <p> @@ -167,8 +164,6 @@ public final class ContactsContract { * {@link Contacts#CONTENT_STREQUENT_FILTER_URI}, which requires the ContactsProvider to * return only phone-related results. For example, frequently contacted person list should * include persons contacted via phone (not email, sms, etc.) - * - * @hide */ public static final String STREQUENT_PHONE_ONLY = "strequent_phone_only"; @@ -193,8 +188,6 @@ public final class ContactsContract { * {@link CommonDataKinds.Email#CONTENT_URI}, and * {@link CommonDataKinds.StructuredPostal#CONTENT_URI}. * This enables a content provider to remove duplicate entries in results. - * - * @hide */ public static final String REMOVE_DUPLICATE_ENTRIES = "remove_duplicate_entries"; @@ -251,30 +244,21 @@ public final class ContactsContract { public static final String KEY_AUTHORIZED_URI = "authorized_uri"; } - /** - * @hide - */ public static final class Preferences { /** * A key in the {@link android.provider.Settings android.provider.Settings} provider * that stores the preferred sorting order for contacts (by given name vs. by family name). - * - * @hide */ public static final String SORT_ORDER = "android.contacts.SORT_ORDER"; /** * The value for the SORT_ORDER key corresponding to sorting by given name first. - * - * @hide */ public static final int SORT_ORDER_PRIMARY = 1; /** * The value for the SORT_ORDER key corresponding to sorting by family name first. - * - * @hide */ public static final int SORT_ORDER_ALTERNATIVE = 2; @@ -282,22 +266,16 @@ public final class ContactsContract { * A key in the {@link android.provider.Settings android.provider.Settings} provider * that stores the preferred display order for contacts (given name first vs. family * name first). - * - * @hide */ public static final String DISPLAY_ORDER = "android.contacts.DISPLAY_ORDER"; /** * The value for the DISPLAY_ORDER key corresponding to showing the given name first. - * - * @hide */ public static final int DISPLAY_ORDER_PRIMARY = 1; /** * The value for the DISPLAY_ORDER key corresponding to showing the family name first. - * - * @hide */ public static final int DISPLAY_ORDER_ALTERNATIVE = 2; } @@ -827,10 +805,9 @@ public final class ContactsContract { public static final String STARRED = "starred"; /** - * The position at which the contact is pinned. If {@link PinnedPositions.UNPINNED}, + * The position at which the contact is pinned. If {@link PinnedPositions#UNPINNED}, * the contact is not pinned. Also see {@link PinnedPositions}. * <P>Type: INTEGER </P> - * @hide */ public static final String PINNED = "pinned"; @@ -924,6 +901,14 @@ public final class ContactsContract { public static final String PHOTO_THUMBNAIL_URI = "photo_thumb_uri"; /** + * Flag that reflects whether the contact exists inside the default directory. + * Ie, whether the contact is designed to only be visible outside search. + * + * @hide + */ + public static final String IN_DEFAULT_DIRECTORY = "in_default_directory"; + + /** * Flag that reflects the {@link Groups#GROUP_VISIBLE} state of any * {@link CommonDataKinds.GroupMembership} for this contact. */ @@ -1470,17 +1455,43 @@ public final class ContactsContract { * Base {@link Uri} for referencing multiple {@link Contacts} entry, * created by appending {@link #LOOKUP_KEY} using * {@link Uri#withAppendedPath(Uri, String)}. The lookup keys have to be - * encoded and joined with the colon (":") separator. The resulting string - * has to be encoded again. Provides - * {@link OpenableColumns} columns when queried, or returns the + * joined with the colon (":") separator, and the resulting string encoded. + * + * Provides {@link OpenableColumns} columns when queried, or returns the * referenced contact formatted as a vCard when opened through * {@link ContentResolver#openAssetFileDescriptor(Uri, String)}. * - * This is private API because we do not have a well-defined way to - * specify several entities yet. The format of this Uri might change in the future - * or the Uri might be completely removed. + * <p> + * Usage example: + * <dl> + * <dt>The following code snippet creates a multi-vcard URI that references all the + * contacts in a user's database.</dt> + * <dd> * - * @hide + * <pre> + * public Uri getAllContactsVcardUri() { + * Cursor cursor = getActivity().getContentResolver().query(Contacts.CONTENT_URI, + * new String[] {Contacts.LOOKUP_KEY}, null, null, null); + * if (cursor == null) { + * return null; + * } + * try { + * StringBuilder uriListBuilder = new StringBuilder(); + * int index = 0; + * while (cursor.moveToNext()) { + * if (index != 0) uriListBuilder.append(':'); + * uriListBuilder.append(cursor.getString(0)); + * index++; + * } + * return Uri.withAppendedPath(Contacts.CONTENT_MULTI_VCARD_URI, + * Uri.encode(uriListBuilder.toString())); + * } finally { + * cursor.close(); + * } + * } + * </pre> + * + * </p> */ public static final Uri CONTENT_MULTI_VCARD_URI = Uri.withAppendedPath(CONTENT_URI, "as_multi_vcard"); @@ -4794,11 +4805,11 @@ public final class ContactsContract { */ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/phone_lookup"; - /** - * Boolean parameter that is used to look up a SIP address. - * - * @hide - */ + /** + * If this boolean parameter is set to true, then the appended query is treated as a + * SIP address and the lookup will be performed against SIP addresses in the user's + * contacts. + */ public static final String QUERY_PARAMETER_SIP_ADDRESS = "sip"; } @@ -5307,8 +5318,6 @@ public final class ContactsContract { /** * The style used for combining given/middle/family name into a full name. * See {@link ContactsContract.FullNameStyle}. - * - * @hide */ public static final String FULL_NAME_STYLE = DATA10; @@ -6900,8 +6909,6 @@ public final class ContactsContract { * each column. For example the meaning for {@link Phone}'s type is different than * {@link SipAddress}'s. * </p> - * - * @hide */ public static final class Callable implements DataColumnsWithJoins, CommonColumns { /** @@ -7759,7 +7766,6 @@ public final class ContactsContract { * {@link PinnedPositions#STAR_WHEN_PINNING} to true to force all pinned and unpinned * contacts to be automatically starred and unstarred. * </p> - * @hide */ public static final class PinnedPositions { diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index f69cad0..bd576af 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -169,6 +169,14 @@ public final class MediaStore { */ public static final String EXTRA_MEDIA_TITLE = "android.intent.extra.title"; /** + * The name of the Intent-extra used to define the genre. + */ + public static final String EXTRA_MEDIA_GENRE = "android.intent.extra.genre"; + /** + * The name of the Intent-extra used to define the radio channel. + */ + public static final String EXTRA_MEDIA_RADIO_CHANNEL = "android.intent.extra.radio_channel"; + /** * The name of the Intent-extra used to define the search focus. The search focus * indicates whether the search should be for things related to the artist, album * or song that is identified by the other extras. @@ -1389,6 +1397,11 @@ public final class MediaStore { public static final String CONTENT_TYPE = "vnd.android.cursor.dir/audio"; /** + * The MIME type for an audio track. + */ + public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/audio"; + + /** * The default sort order for this table */ public static final String DEFAULT_SORT_ORDER = TITLE_KEY; @@ -1859,6 +1872,13 @@ public final class MediaStore { */ public static final String DEFAULT_SORT_ORDER = ALBUM_KEY; } + + public static final class Radio { + /** + * The MIME type for entries in this table. + */ + public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/radio"; + } } public static final class Video { diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 04f3f0a..7777334 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -749,6 +749,14 @@ public final class Settings { public static final String ACTION_PRINT_SETTINGS = "android.settings.ACTION_PRINT_SETTINGS"; + /** + * Activity Action: Show Zen Mode configuration settings. + * + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_ZEN_MODE_SETTINGS = "android.settings.ZEN_MODE_SETTINGS"; + // End of Intent actions for Settings /** @@ -3352,21 +3360,29 @@ public final class Settings { public static final String INSTALL_NON_MARKET_APPS = Global.INSTALL_NON_MARKET_APPS; /** - * Comma-separated list of location providers that activities may access. + * Comma-separated list of location providers that activities may access. Do not rely on + * this value being present in settings.db or on ContentObserver notifications on the + * corresponding Uri. * - * @deprecated use {@link #LOCATION_MODE} + * @deprecated use {@link #LOCATION_MODE} and + * {@link LocationManager#MODE_CHANGED_ACTION} (or + * {@link LocationManager#PROVIDERS_CHANGED_ACTION}) */ @Deprecated public static final String LOCATION_PROVIDERS_ALLOWED = "location_providers_allowed"; /** * The degree of location access enabled by the user. - * <p/> + * <p> * When used with {@link #putInt(ContentResolver, String, int)}, must be one of {@link * #LOCATION_MODE_HIGH_ACCURACY}, {@link #LOCATION_MODE_SENSORS_ONLY}, {@link * #LOCATION_MODE_BATTERY_SAVING}, or {@link #LOCATION_MODE_OFF}. When used with {@link * #getInt(ContentResolver, String)}, the caller must gracefully handle additional location * modes that might be added in the future. + * <p> + * Note: do not rely on this value being present in settings.db or on ContentObserver + * notifications for the corresponding Uri. Use {@link LocationManager#MODE_CHANGED_ACTION} + * to receive changes in this value. */ public static final String LOCATION_MODE = "location_mode"; @@ -3407,6 +3423,11 @@ public final class Settings { public static final String LOCK_PATTERN_VISIBLE = "lock_pattern_visible_pattern"; /** + * Whether the NFC unlock feature is enabled (0 = false, 1 = true) + */ + public static final String NFC_UNLOCK_ENABLED = "nfc_unlock_enabled"; + + /** * Whether lock pattern will vibrate as user enters (0 = false, 1 = * true) * @@ -3463,6 +3484,14 @@ public final class Settings { "lock_screen_owner_info_enabled"; /** + * When set by a user, allows notifications to be shown atop a securely locked screen + * in their full "private" form (same as when the device is unlocked). + * @hide + */ + public static final String LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS = + "lock_screen_allow_private_notifications"; + + /** * The Logging ID (a unique 64-bit value) as a hex string. * Used as a pseudonymous identifier for logging. * @deprecated This identifier is poorly initialized and has @@ -3741,6 +3770,16 @@ public final class Settings { "accessibility_captioning_edge_color"; /** + * Integer property that specifes the window color for captions as a + * packed 32-bit color. + * + * @see android.graphics.Color#argb + * @hide + */ + public static final String ACCESSIBILITY_CAPTIONING_WINDOW_COLOR = + "accessibility_captioning_window_color"; + + /** * String property that specifies the typeface for captions, one of: * <ul> * <li>DEFAULT @@ -3764,6 +3803,97 @@ public final class Settings { "accessibility_captioning_font_scale"; /** + * Setting that specifies whether the quick setting tile for display + * color inversion is enabled. + * + * @hide + */ + public static final String ACCESSIBILITY_DISPLAY_INVERSION_QUICK_SETTING_ENABLED = + "accessibility_display_inversion_quick_setting_enabled"; + + /** + * Setting that specifies whether display color inversion is enabled. + * + * @hide + */ + public static final String ACCESSIBILITY_DISPLAY_INVERSION_ENABLED = + "accessibility_display_inversion_enabled"; + + /** + * Integer property that specifies the type of color inversion to + * perform. Valid values are defined in AccessibilityManager. + * + * @hide + */ + public static final String ACCESSIBILITY_DISPLAY_INVERSION = + "accessibility_display_inversion"; + + /** + * Setting that specifies whether the quick setting tile for display + * color space adjustment is enabled. + * + * @hide + */ + public static final String ACCESSIBILITY_DISPLAY_DALTONIZER_QUICK_SETTING_ENABLED = + "accessibility_display_daltonizer_quick_setting_enabled"; + + /** + * Setting that specifies whether display color space adjustment is + * enabled. + * + * @hide + */ + public static final String ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED = + "accessibility_display_daltonizer_enabled"; + + /** + * Integer property that specifies the type of color space adjustment to + * perform. Valid values are defined in AccessibilityManager. + * + * @hide + */ + public static final String ACCESSIBILITY_DISPLAY_DALTONIZER = + "accessibility_display_daltonizer"; + + /** + * Setting that specifies whether the quick setting tile for display + * contrast enhancement is enabled. + * + * @hide + */ + public static final String ACCESSIBILITY_DISPLAY_CONTRAST_QUICK_SETTING_ENABLED = + "accessibility_display_contrast_quick_setting_enabled"; + + /** + * Setting that specifies whether display contrast enhancement is + * enabled. + * + * @hide + */ + public static final String ACCESSIBILITY_DISPLAY_CONTRAST_ENABLED = + "accessibility_display_contrast_enabled"; + + /** + * Floating point property that specifies display contrast adjustment. + * Valid range is [0, ...] where 0 is gray, 1 is normal, and higher + * values indicate enhanced contrast. + * + * @hide + */ + public static final String ACCESSIBILITY_DISPLAY_CONTRAST = + "accessibility_display_contrast"; + + /** + * Floating point property that specifies display brightness adjustment. + * Valid range is [-1, 1] where -1 is black, 0 is default, and 1 is + * white. + * + * @hide + */ + public static final String ACCESSIBILITY_DISPLAY_BRIGHTNESS = + "accessibility_display_brightness"; + + /** * The timout for considering a press to be a long press in milliseconds. * @hide */ @@ -5140,6 +5270,12 @@ public final class Settings { public static final String SMS_SHORT_CODE_RULE = "sms_short_code_rule"; /** + * Used to select TCP's default initial receiver window size in segments - defaults to a build config value + * @hide + */ + public static final String TCP_DEFAULT_INIT_RWND = "tcp_default_init_rwnd"; + + /** * Used to disable Tethering on a device - defaults to true * @hide */ @@ -5935,6 +6071,62 @@ public final class Settings { public static final String LOW_BATTERY_SOUND_TIMEOUT = "low_battery_sound_timeout"; /** + * Milliseconds to wait before bouncing Wi-Fi after settings is restored. Note that after + * the caller is done with this, they should call {@link ContentResolver#delete(Uri)} to + * clean up any value that they may have written. + * + * @hide + */ + public static final String WIFI_BOUNCE_DELAY_OVERRIDE_MS = "wifi_bounce_delay_override_ms"; + + /** + * Defines global runtime overrides to window policy. + * + * See {@link com.android.internal.policy.impl.PolicyControl} for value format. + * + * @hide + */ + public static final String POLICY_CONTROL = "policy_control"; + + + /** + * This preference enables notification display even over a securely + * locked screen. + * @hide + */ + public static final String LOCK_SCREEN_SHOW_NOTIFICATIONS = + "lock_screen_show_notifications"; + + /** + * Defines global zen mode. One of ZEN_MODE_OFF, ZEN_MODE_LIMITED, ZEN_MODE_FULL. + * + * @hide + */ + public static final String ZEN_MODE = "zen_mode"; + + /** @hide */ public static final int ZEN_MODE_OFF = 0; + /** @hide */ public static final int ZEN_MODE_LIMITED = 1; + /** @hide */ public static final int ZEN_MODE_FULL = 2; + + /** @hide */ public static String zenModeToString(int mode) { + if (mode == ZEN_MODE_OFF) return "ZEN_MODE_OFF"; + if (mode == ZEN_MODE_LIMITED) return "ZEN_MODE_LIMITED"; + if (mode == ZEN_MODE_FULL) return "ZEN_MODE_FULL"; + throw new IllegalArgumentException("Invalid zen mode: " + mode); + } + + /** + * Defines global heads up toggle. One of HEADS_UP_OFF, HEADS_UP_ON. + * + * @hide + */ + public static final String HEADS_UP_NOTIFICATIONS_ENABLED = + "heads_up_notifications_enabled"; + + /** @hide */ public static final int HEADS_UP_OFF = 0; + /** @hide */ public static final int HEADS_UP_ON = 1; + + /** * Settings to backup. This is here so that it's in the same place as the settings * keys and easy to update. * diff --git a/core/java/android/service/textservice/SpellCheckerService.java b/core/java/android/service/textservice/SpellCheckerService.java index 77b22ed..acfef82 100644 --- a/core/java/android/service/textservice/SpellCheckerService.java +++ b/core/java/android/service/textservice/SpellCheckerService.java @@ -32,7 +32,6 @@ import android.util.Log; import android.view.textservice.SentenceSuggestionsInfo; import android.view.textservice.SuggestionsInfo; import android.view.textservice.TextInfo; -import android.widget.SpellChecker; import java.lang.ref.WeakReference; import java.text.BreakIterator; diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 5db8168..03ce4e0 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -38,7 +38,6 @@ import android.os.Message; import android.os.PowerManager; import android.os.RemoteException; import android.util.Log; -import android.util.LogPrinter; import android.view.Display; import android.view.Gravity; import android.view.IWindowSession; diff --git a/core/java/android/speech/srec/Recognizer.java b/core/java/android/speech/srec/Recognizer.java index 1396204..6c491a0 100644 --- a/core/java/android/speech/srec/Recognizer.java +++ b/core/java/android/speech/srec/Recognizer.java @@ -22,8 +22,6 @@ package android.speech.srec; -import android.util.Log; - import java.io.File; import java.io.InputStream; import java.io.IOException; diff --git a/core/java/android/speech/tts/AbstractEventLogger.java b/core/java/android/speech/tts/AbstractEventLogger.java new file mode 100644 index 0000000..37f8656 --- /dev/null +++ b/core/java/android/speech/tts/AbstractEventLogger.java @@ -0,0 +1,124 @@ +/* + * 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.speech.tts; + +import android.os.SystemClock; + +/** + * Base class for storing data about a given speech synthesis request to the + * event logs. The data that is logged depends on actual implementation. Note + * that {@link AbstractEventLogger#onAudioDataWritten()} and + * {@link AbstractEventLogger#onEngineComplete()} must be called from a single + * thread (usually the audio playback thread}. + */ +abstract class AbstractEventLogger { + protected final String mServiceApp; + protected final int mCallerUid; + protected final int mCallerPid; + protected final long mReceivedTime; + protected long mPlaybackStartTime = -1; + + private volatile long mRequestProcessingStartTime = -1; + private volatile long mEngineStartTime = -1; + private volatile long mEngineCompleteTime = -1; + + private boolean mLogWritten = false; + + AbstractEventLogger(int callerUid, int callerPid, String serviceApp) { + mCallerUid = callerUid; + mCallerPid = callerPid; + mServiceApp = serviceApp; + mReceivedTime = SystemClock.elapsedRealtime(); + } + + /** + * Notifies the logger that this request has been selected from + * the processing queue for processing. Engine latency / total time + * is measured from this baseline. + */ + public void onRequestProcessingStart() { + mRequestProcessingStartTime = SystemClock.elapsedRealtime(); + } + + /** + * Notifies the logger that a chunk of data has been received from + * the engine. Might be called multiple times. + */ + public void onEngineDataReceived() { + if (mEngineStartTime == -1) { + mEngineStartTime = SystemClock.elapsedRealtime(); + } + } + + /** + * Notifies the logger that the engine has finished processing data. + * Will be called exactly once. + */ + public void onEngineComplete() { + mEngineCompleteTime = SystemClock.elapsedRealtime(); + } + + /** + * Notifies the logger that audio playback has started for some section + * of the synthesis. This is normally some amount of time after the engine + * has synthesized data and varies depending on utterances and + * other audio currently in the queue. + */ + public void onAudioDataWritten() { + // For now, keep track of only the first chunk of audio + // that was played. + if (mPlaybackStartTime == -1) { + mPlaybackStartTime = SystemClock.elapsedRealtime(); + } + } + + /** + * Notifies the logger that the current synthesis has completed. + * All available data is not logged. + */ + public void onCompleted(int statusCode) { + if (mLogWritten) { + return; + } else { + mLogWritten = true; + } + + long completionTime = SystemClock.elapsedRealtime(); + + // We don't report latency for stopped syntheses because their overall + // total time spent will be inaccurate (will not correlate with + // the length of the utterance). + + // onAudioDataWritten() should normally always be called, and hence mPlaybackStartTime + // should be set, if an error does not occur. + if (statusCode != TextToSpeechClient.Status.SUCCESS + || mPlaybackStartTime == -1 || mEngineCompleteTime == -1) { + logFailure(statusCode); + return; + } + + final long audioLatency = mPlaybackStartTime - mReceivedTime; + final long engineLatency = mEngineStartTime - mRequestProcessingStartTime; + final long engineTotal = mEngineCompleteTime - mRequestProcessingStartTime; + logSuccess(audioLatency, engineLatency, engineTotal); + } + + protected abstract void logFailure(int statusCode); + protected abstract void logSuccess(long audioLatency, long engineLatency, + long engineTotal); + + +} diff --git a/core/java/android/speech/tts/AbstractSynthesisCallback.java b/core/java/android/speech/tts/AbstractSynthesisCallback.java index c7a4af0..91e119b 100644 --- a/core/java/android/speech/tts/AbstractSynthesisCallback.java +++ b/core/java/android/speech/tts/AbstractSynthesisCallback.java @@ -15,15 +15,28 @@ */ package android.speech.tts; + /** * Defines additional methods the synthesis callback must implement that * are private to the TTS service implementation. + * + * All of these class methods (with the exception of {@link #stop()}) can be only called on the + * synthesis thread, while inside + * {@link TextToSpeechService#onSynthesizeText} or {@link TextToSpeechService#onSynthesizeTextV2}. + * {@link #stop()} is the exception, it may be called from multiple threads. */ abstract class AbstractSynthesisCallback implements SynthesisCallback { + /** If true, request comes from V2 TTS interface */ + protected final boolean mClientIsUsingV2; + /** - * Checks whether the synthesis request completed successfully. + * Constructor. + * @param clientIsUsingV2 If true, this callback will be used inside + * {@link TextToSpeechService#onSynthesizeTextV2} method. */ - abstract boolean isDone(); + AbstractSynthesisCallback(boolean clientIsUsingV2) { + mClientIsUsingV2 = clientIsUsingV2; + } /** * Aborts the speech request. @@ -31,4 +44,16 @@ abstract class AbstractSynthesisCallback implements SynthesisCallback { * Can be called from multiple threads. */ abstract void stop(); + + /** + * Get status code for a "stop". + * + * V2 Clients will receive special status, V1 clients will receive standard error. + * + * This method should only be called on the synthesis thread, + * while in {@link TextToSpeechService#onSynthesizeText}. + */ + int errorCodeOnStop() { + return mClientIsUsingV2 ? TextToSpeechClient.Status.STOPPED : TextToSpeech.ERROR; + } } diff --git a/core/java/android/speech/tts/AudioPlaybackHandler.java b/core/java/android/speech/tts/AudioPlaybackHandler.java index d63f605..dcf49b0 100644 --- a/core/java/android/speech/tts/AudioPlaybackHandler.java +++ b/core/java/android/speech/tts/AudioPlaybackHandler.java @@ -43,7 +43,7 @@ class AudioPlaybackHandler { return; } - item.stop(false); + item.stop(TextToSpeechClient.Status.STOPPED); } public void enqueue(PlaybackQueueItem item) { diff --git a/core/java/android/speech/tts/AudioPlaybackQueueItem.java b/core/java/android/speech/tts/AudioPlaybackQueueItem.java index 1a1fda8..c514639 100644 --- a/core/java/android/speech/tts/AudioPlaybackQueueItem.java +++ b/core/java/android/speech/tts/AudioPlaybackQueueItem.java @@ -53,7 +53,7 @@ class AudioPlaybackQueueItem extends PlaybackQueueItem { dispatcher.dispatchOnStart(); mPlayer = MediaPlayer.create(mContext, mUri); if (mPlayer == null) { - dispatcher.dispatchOnError(); + dispatcher.dispatchOnError(TextToSpeechClient.Status.ERROR_OUTPUT); return; } @@ -83,9 +83,9 @@ class AudioPlaybackQueueItem extends PlaybackQueueItem { } if (mFinished) { - dispatcher.dispatchOnDone(); + dispatcher.dispatchOnSuccess(); } else { - dispatcher.dispatchOnError(); + dispatcher.dispatchOnStop(); } } @@ -99,7 +99,7 @@ class AudioPlaybackQueueItem extends PlaybackQueueItem { } @Override - void stop(boolean isError) { + void stop(int errorCode) { mDone.open(); } } diff --git a/core/java/android/speech/tts/EventLogTags.logtags b/core/java/android/speech/tts/EventLogTags.logtags index f8654ad..e209a28 100644 --- a/core/java/android/speech/tts/EventLogTags.logtags +++ b/core/java/android/speech/tts/EventLogTags.logtags @@ -4,3 +4,6 @@ option java_package android.speech.tts; 76001 tts_speak_success (engine|3),(caller_uid|1),(caller_pid|1),(length|1),(locale|3),(rate|1),(pitch|1),(engine_latency|2|3),(engine_total|2|3),(audio_latency|2|3) 76002 tts_speak_failure (engine|3),(caller_uid|1),(caller_pid|1),(length|1),(locale|3),(rate|1),(pitch|1) + +76003 tts_v2_speak_success (engine|3),(caller_uid|1),(caller_pid|1),(length|1),(request_config|3),(engine_latency|2|3),(engine_total|2|3),(audio_latency|2|3) +76004 tts_v2_speak_failure (engine|3),(caller_uid|1),(caller_pid|1),(length|1),(request_config|3), (statusCode|1) diff --git a/core/java/android/speech/tts/EventLogger.java b/core/java/android/speech/tts/EventLogger.java deleted file mode 100644 index 82ed4dd..0000000 --- a/core/java/android/speech/tts/EventLogger.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package android.speech.tts; - -import android.os.SystemClock; -import android.text.TextUtils; -import android.util.Log; - -/** - * Writes data about a given speech synthesis request to the event logs. - * The data that is logged includes the calling app, length of the utterance, - * speech rate / pitch and the latency and overall time taken. - * - * Note that {@link EventLogger#onStopped()} and {@link EventLogger#onError()} - * might be called from any thread, but on {@link EventLogger#onAudioDataWritten()} and - * {@link EventLogger#onComplete()} must be called from a single thread - * (usually the audio playback thread} - */ -class EventLogger { - private final SynthesisRequest mRequest; - private final String mServiceApp; - private final int mCallerUid; - private final int mCallerPid; - private final long mReceivedTime; - private long mPlaybackStartTime = -1; - private volatile long mRequestProcessingStartTime = -1; - private volatile long mEngineStartTime = -1; - private volatile long mEngineCompleteTime = -1; - - private volatile boolean mError = false; - private volatile boolean mStopped = false; - private boolean mLogWritten = false; - - EventLogger(SynthesisRequest request, int callerUid, int callerPid, String serviceApp) { - mRequest = request; - mCallerUid = callerUid; - mCallerPid = callerPid; - mServiceApp = serviceApp; - mReceivedTime = SystemClock.elapsedRealtime(); - } - - /** - * Notifies the logger that this request has been selected from - * the processing queue for processing. Engine latency / total time - * is measured from this baseline. - */ - public void onRequestProcessingStart() { - mRequestProcessingStartTime = SystemClock.elapsedRealtime(); - } - - /** - * Notifies the logger that a chunk of data has been received from - * the engine. Might be called multiple times. - */ - public void onEngineDataReceived() { - if (mEngineStartTime == -1) { - mEngineStartTime = SystemClock.elapsedRealtime(); - } - } - - /** - * Notifies the logger that the engine has finished processing data. - * Will be called exactly once. - */ - public void onEngineComplete() { - mEngineCompleteTime = SystemClock.elapsedRealtime(); - } - - /** - * Notifies the logger that audio playback has started for some section - * of the synthesis. This is normally some amount of time after the engine - * has synthesized data and varies depending on utterances and - * other audio currently in the queue. - */ - public void onAudioDataWritten() { - // For now, keep track of only the first chunk of audio - // that was played. - if (mPlaybackStartTime == -1) { - mPlaybackStartTime = SystemClock.elapsedRealtime(); - } - } - - /** - * Notifies the logger that the current synthesis was stopped. - * Latency numbers are not reported for stopped syntheses. - */ - public void onStopped() { - mStopped = false; - } - - /** - * Notifies the logger that the current synthesis resulted in - * an error. This is logged using {@link EventLogTags#writeTtsSpeakFailure}. - */ - public void onError() { - mError = true; - } - - /** - * Notifies the logger that the current synthesis has completed. - * All available data is not logged. - */ - public void onWriteData() { - if (mLogWritten) { - return; - } else { - mLogWritten = true; - } - - long completionTime = SystemClock.elapsedRealtime(); - // onAudioDataWritten() should normally always be called if an - // error does not occur. - if (mError || mPlaybackStartTime == -1 || mEngineCompleteTime == -1) { - EventLogTags.writeTtsSpeakFailure(mServiceApp, mCallerUid, mCallerPid, - getUtteranceLength(), getLocaleString(), - mRequest.getSpeechRate(), mRequest.getPitch()); - return; - } - - // We don't report stopped syntheses because their overall - // total time spent will be innacurate (will not correlate with - // the length of the utterance). - if (mStopped) { - return; - } - - final long audioLatency = mPlaybackStartTime - mReceivedTime; - final long engineLatency = mEngineStartTime - mRequestProcessingStartTime; - final long engineTotal = mEngineCompleteTime - mRequestProcessingStartTime; - - EventLogTags.writeTtsSpeakSuccess(mServiceApp, mCallerUid, mCallerPid, - getUtteranceLength(), getLocaleString(), - mRequest.getSpeechRate(), mRequest.getPitch(), - engineLatency, engineTotal, audioLatency); - } - - /** - * @return the length of the utterance for the given synthesis, 0 - * if the utterance was {@code null}. - */ - private int getUtteranceLength() { - final String utterance = mRequest.getText(); - return utterance == null ? 0 : utterance.length(); - } - - /** - * Returns a formatted locale string from the synthesis params of the - * form lang-country-variant. - */ - private String getLocaleString() { - StringBuilder sb = new StringBuilder(mRequest.getLanguage()); - if (!TextUtils.isEmpty(mRequest.getCountry())) { - sb.append('-'); - sb.append(mRequest.getCountry()); - - if (!TextUtils.isEmpty(mRequest.getVariant())) { - sb.append('-'); - sb.append(mRequest.getVariant()); - } - } - - return sb.toString(); - } - -} diff --git a/core/java/android/speech/tts/EventLoggerV1.java b/core/java/android/speech/tts/EventLoggerV1.java new file mode 100644 index 0000000..f484347 --- /dev/null +++ b/core/java/android/speech/tts/EventLoggerV1.java @@ -0,0 +1,80 @@ +/* + * 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.speech.tts; + +import android.text.TextUtils; + +/** + * Writes data about a given speech synthesis request for V1 API to the event + * logs. The data that is logged includes the calling app, length of the + * utterance, speech rate / pitch, the latency, and overall time taken. + */ +class EventLoggerV1 extends AbstractEventLogger { + private final SynthesisRequest mRequest; + + EventLoggerV1(SynthesisRequest request, int callerUid, int callerPid, String serviceApp) { + super(callerUid, callerPid, serviceApp); + mRequest = request; + } + + @Override + protected void logFailure(int statusCode) { + // We don't report stopped syntheses because their overall + // total time spent will be inaccurate (will not correlate with + // the length of the utterance). + if (statusCode != TextToSpeechClient.Status.STOPPED) { + EventLogTags.writeTtsSpeakFailure(mServiceApp, mCallerUid, mCallerPid, + getUtteranceLength(), getLocaleString(), + mRequest.getSpeechRate(), mRequest.getPitch()); + } + } + + @Override + protected void logSuccess(long audioLatency, long engineLatency, long engineTotal) { + EventLogTags.writeTtsSpeakSuccess(mServiceApp, mCallerUid, mCallerPid, + getUtteranceLength(), getLocaleString(), + mRequest.getSpeechRate(), mRequest.getPitch(), + engineLatency, engineTotal, audioLatency); + } + + /** + * @return the length of the utterance for the given synthesis, 0 + * if the utterance was {@code null}. + */ + private int getUtteranceLength() { + final String utterance = mRequest.getText(); + return utterance == null ? 0 : utterance.length(); + } + + /** + * Returns a formatted locale string from the synthesis params of the + * form lang-country-variant. + */ + private String getLocaleString() { + StringBuilder sb = new StringBuilder(mRequest.getLanguage()); + if (!TextUtils.isEmpty(mRequest.getCountry())) { + sb.append('-'); + sb.append(mRequest.getCountry()); + + if (!TextUtils.isEmpty(mRequest.getVariant())) { + sb.append('-'); + sb.append(mRequest.getVariant()); + } + } + + return sb.toString(); + } +} diff --git a/core/java/android/speech/tts/EventLoggerV2.java b/core/java/android/speech/tts/EventLoggerV2.java new file mode 100644 index 0000000..b8e4dae --- /dev/null +++ b/core/java/android/speech/tts/EventLoggerV2.java @@ -0,0 +1,73 @@ +/* + * 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.speech.tts; + + + +/** + * Writes data about a given speech synthesis request for V2 API to the event logs. + * The data that is logged includes the calling app, length of the utterance, + * synthesis request configuration and the latency and overall time taken. + */ +class EventLoggerV2 extends AbstractEventLogger { + private final SynthesisRequestV2 mRequest; + + EventLoggerV2(SynthesisRequestV2 request, int callerUid, int callerPid, String serviceApp) { + super(callerUid, callerPid, serviceApp); + mRequest = request; + } + + @Override + protected void logFailure(int statusCode) { + // We don't report stopped syntheses because their overall + // total time spent will be inaccurate (will not correlate with + // the length of the utterance). + if (statusCode != TextToSpeechClient.Status.STOPPED) { + EventLogTags.writeTtsV2SpeakFailure(mServiceApp, + mCallerUid, mCallerPid, getUtteranceLength(), getRequestConfigString(), statusCode); + } + } + + @Override + protected void logSuccess(long audioLatency, long engineLatency, long engineTotal) { + EventLogTags.writeTtsV2SpeakSuccess(mServiceApp, + mCallerUid, mCallerPid, getUtteranceLength(), getRequestConfigString(), + engineLatency, engineTotal, audioLatency); + } + + /** + * @return the length of the utterance for the given synthesis, 0 + * if the utterance was {@code null}. + */ + private int getUtteranceLength() { + final String utterance = mRequest.getText(); + return utterance == null ? 0 : utterance.length(); + } + + /** + * Returns a string representation of the synthesis request configuration. + */ + private String getRequestConfigString() { + // Ensure the bundles are unparceled. + mRequest.getVoiceParams().size(); + mRequest.getAudioParams().size(); + + return new StringBuilder(64).append("VoiceName: ").append(mRequest.getVoiceName()) + .append(" ,VoiceParams: ").append(mRequest.getVoiceParams()) + .append(" ,SystemParams: ").append(mRequest.getAudioParams()) + .append("]").toString(); + } +} diff --git a/core/java/android/speech/tts/FileSynthesisCallback.java b/core/java/android/speech/tts/FileSynthesisCallback.java index ab8f82f..717aeb6 100644 --- a/core/java/android/speech/tts/FileSynthesisCallback.java +++ b/core/java/android/speech/tts/FileSynthesisCallback.java @@ -16,13 +16,10 @@ package android.speech.tts; import android.media.AudioFormat; -import android.os.FileUtils; +import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher; import android.util.Log; -import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.channels.FileChannel; @@ -48,19 +45,39 @@ class FileSynthesisCallback extends AbstractSynthesisCallback { private FileChannel mFileChannel; + private final UtteranceProgressDispatcher mDispatcher; + private final Object mCallerIdentity; + private boolean mStarted = false; - private boolean mStopped = false; private boolean mDone = false; - FileSynthesisCallback(FileChannel fileChannel) { + /** Status code of synthesis */ + protected int mStatusCode; + + FileSynthesisCallback(FileChannel fileChannel, UtteranceProgressDispatcher dispatcher, + Object callerIdentity, boolean clientIsUsingV2) { + super(clientIsUsingV2); mFileChannel = fileChannel; + mDispatcher = dispatcher; + mCallerIdentity = callerIdentity; + mStatusCode = TextToSpeechClient.Status.SUCCESS; } @Override void stop() { synchronized (mStateLock) { - mStopped = true; + if (mDone) { + return; + } + if (mStatusCode == TextToSpeechClient.Status.STOPPED) { + return; + } + + mStatusCode = TextToSpeechClient.Status.STOPPED; cleanUp(); + if (mDispatcher != null) { + mDispatcher.dispatchOnStop(); + } } } @@ -75,14 +92,8 @@ class FileSynthesisCallback extends AbstractSynthesisCallback { * Must be called while holding the monitor on {@link #mStateLock}. */ private void closeFile() { - try { - if (mFileChannel != null) { - mFileChannel.close(); - mFileChannel = null; - } - } catch (IOException ex) { - Log.e(TAG, "Failed to close output file descriptor", ex); - } + // File will be closed by the SpeechItem in the speech service. + mFileChannel = null; } @Override @@ -91,38 +102,46 @@ class FileSynthesisCallback extends AbstractSynthesisCallback { } @Override - boolean isDone() { - return mDone; - } - - @Override public int start(int sampleRateInHz, int audioFormat, int channelCount) { if (DBG) { Log.d(TAG, "FileSynthesisRequest.start(" + sampleRateInHz + "," + audioFormat + "," + channelCount + ")"); } + FileChannel fileChannel = null; synchronized (mStateLock) { - if (mStopped) { + if (mStatusCode == TextToSpeechClient.Status.STOPPED) { if (DBG) Log.d(TAG, "Request has been aborted."); + return errorCodeOnStop(); + } + if (mStatusCode != TextToSpeechClient.Status.SUCCESS) { + if (DBG) Log.d(TAG, "Error was raised"); return TextToSpeech.ERROR; } if (mStarted) { - cleanUp(); - throw new IllegalArgumentException("FileSynthesisRequest.start() called twice"); + Log.e(TAG, "Start called twice"); + return TextToSpeech.ERROR; } mStarted = true; mSampleRateInHz = sampleRateInHz; mAudioFormat = audioFormat; mChannelCount = channelCount; - try { - mFileChannel.write(ByteBuffer.allocate(WAV_HEADER_LENGTH)); + if (mDispatcher != null) { + mDispatcher.dispatchOnStart(); + } + fileChannel = mFileChannel; + } + + try { + fileChannel.write(ByteBuffer.allocate(WAV_HEADER_LENGTH)); return TextToSpeech.SUCCESS; - } catch (IOException ex) { - Log.e(TAG, "Failed to write wav header to output file descriptor" + ex); + } catch (IOException ex) { + Log.e(TAG, "Failed to write wav header to output file descriptor", ex); + synchronized (mStateLock) { cleanUp(); - return TextToSpeech.ERROR; + mStatusCode = TextToSpeechClient.Status.ERROR_OUTPUT; } + return TextToSpeech.ERROR; } } @@ -132,66 +151,128 @@ class FileSynthesisCallback extends AbstractSynthesisCallback { Log.d(TAG, "FileSynthesisRequest.audioAvailable(" + buffer + "," + offset + "," + length + ")"); } + FileChannel fileChannel = null; synchronized (mStateLock) { - if (mStopped) { + if (mStatusCode == TextToSpeechClient.Status.STOPPED) { if (DBG) Log.d(TAG, "Request has been aborted."); + return errorCodeOnStop(); + } + if (mStatusCode != TextToSpeechClient.Status.SUCCESS) { + if (DBG) Log.d(TAG, "Error was raised"); return TextToSpeech.ERROR; } if (mFileChannel == null) { Log.e(TAG, "File not open"); + mStatusCode = TextToSpeechClient.Status.ERROR_OUTPUT; return TextToSpeech.ERROR; } - try { - mFileChannel.write(ByteBuffer.wrap(buffer, offset, length)); - return TextToSpeech.SUCCESS; - } catch (IOException ex) { - Log.e(TAG, "Failed to write to output file descriptor", ex); - cleanUp(); + if (!mStarted) { + Log.e(TAG, "Start method was not called"); return TextToSpeech.ERROR; } + fileChannel = mFileChannel; + } + + try { + fileChannel.write(ByteBuffer.wrap(buffer, offset, length)); + return TextToSpeech.SUCCESS; + } catch (IOException ex) { + Log.e(TAG, "Failed to write to output file descriptor", ex); + synchronized (mStateLock) { + cleanUp(); + mStatusCode = TextToSpeechClient.Status.ERROR_OUTPUT; + } + return TextToSpeech.ERROR; } } @Override public int done() { if (DBG) Log.d(TAG, "FileSynthesisRequest.done()"); + FileChannel fileChannel = null; + + int sampleRateInHz = 0; + int audioFormat = 0; + int channelCount = 0; + synchronized (mStateLock) { if (mDone) { - if (DBG) Log.d(TAG, "Duplicate call to done()"); - // This preserves existing behaviour. Earlier, if done was called twice - // we'd return ERROR because mFile == null and we'd add to logspam. + Log.w(TAG, "Duplicate call to done()"); + // This is not an error that would prevent synthesis. Hence no + // setStatusCode is set. return TextToSpeech.ERROR; } - if (mStopped) { + if (mStatusCode == TextToSpeechClient.Status.STOPPED) { if (DBG) Log.d(TAG, "Request has been aborted."); + return errorCodeOnStop(); + } + if (mDispatcher != null && mStatusCode != TextToSpeechClient.Status.SUCCESS && + mStatusCode != TextToSpeechClient.Status.STOPPED) { + mDispatcher.dispatchOnError(mStatusCode); return TextToSpeech.ERROR; } if (mFileChannel == null) { Log.e(TAG, "File not open"); return TextToSpeech.ERROR; } - try { - // Write WAV header at start of file - mFileChannel.position(0); - int dataLength = (int) (mFileChannel.size() - WAV_HEADER_LENGTH); - mFileChannel.write( - makeWavHeader(mSampleRateInHz, mAudioFormat, mChannelCount, dataLength)); + mDone = true; + fileChannel = mFileChannel; + sampleRateInHz = mSampleRateInHz; + audioFormat = mAudioFormat; + channelCount = mChannelCount; + } + + try { + // Write WAV header at start of file + fileChannel.position(0); + int dataLength = (int) (fileChannel.size() - WAV_HEADER_LENGTH); + fileChannel.write( + makeWavHeader(sampleRateInHz, audioFormat, channelCount, dataLength)); + + synchronized (mStateLock) { closeFile(); - mDone = true; + if (mDispatcher != null) { + mDispatcher.dispatchOnSuccess(); + } return TextToSpeech.SUCCESS; - } catch (IOException ex) { - Log.e(TAG, "Failed to write to output file descriptor", ex); + } + } catch (IOException ex) { + Log.e(TAG, "Failed to write to output file descriptor", ex); + synchronized (mStateLock) { cleanUp(); - return TextToSpeech.ERROR; } + return TextToSpeech.ERROR; } } @Override public void error() { + error(TextToSpeechClient.Status.ERROR_SYNTHESIS); + } + + @Override + public void error(int errorCode) { if (DBG) Log.d(TAG, "FileSynthesisRequest.error()"); synchronized (mStateLock) { + if (mDone) { + return; + } cleanUp(); + mStatusCode = errorCode; + } + } + + @Override + public boolean hasStarted() { + synchronized (mStateLock) { + return mStarted; + } + } + + @Override + public boolean hasFinished() { + synchronized (mStateLock) { + return mDone; } } @@ -225,4 +306,16 @@ class FileSynthesisCallback extends AbstractSynthesisCallback { return header; } + @Override + public int fallback() { + synchronized (mStateLock) { + if (hasStarted() || hasFinished()) { + return TextToSpeech.ERROR; + } + + mDispatcher.dispatchOnFallback(); + mStatusCode = TextToSpeechClient.Status.SUCCESS; + return TextToSpeechClient.Status.SUCCESS; + } + } } diff --git a/core/java/android/speech/tts/ITextToSpeechCallback.aidl b/core/java/android/speech/tts/ITextToSpeechCallback.aidl index f0287d4..3c808ff 100644 --- a/core/java/android/speech/tts/ITextToSpeechCallback.aidl +++ b/core/java/android/speech/tts/ITextToSpeechCallback.aidl @@ -15,13 +15,53 @@ */ package android.speech.tts; +import android.speech.tts.VoiceInfo; + /** * Interface for callbacks from TextToSpeechService * * {@hide} */ oneway interface ITextToSpeechCallback { + /** + * Tells the client that the synthesis has started. + * + * @param utteranceId Unique id identifying synthesis request. + */ void onStart(String utteranceId); - void onDone(String utteranceId); - void onError(String utteranceId); + + /** + * Tells the client that the synthesis has finished. + * + * @param utteranceId Unique id identifying synthesis request. + */ + void onSuccess(String utteranceId); + + /** + * Tells the client that the synthesis was stopped. + * + * @param utteranceId Unique id identifying synthesis request. + */ + void onStop(String utteranceId); + + /** + * Tells the client that the synthesis failed, and fallback synthesis will be attempted. + * + * @param utteranceId Unique id identifying synthesis request. + */ + void onFallback(String utteranceId); + + /** + * Tells the client that the synthesis has failed. + * + * @param utteranceId Unique id identifying synthesis request. + * @param errorCode One of the values from + * {@link android.speech.tts.v2.TextToSpeechClient.Status}. + */ + void onError(String utteranceId, int errorCode); + + /** + * Inform the client that set of available voices changed. + */ + void onVoicesInfoChange(in List<VoiceInfo> voices); } diff --git a/core/java/android/speech/tts/ITextToSpeechService.aidl b/core/java/android/speech/tts/ITextToSpeechService.aidl index b7bc70c..9cf49ff 100644 --- a/core/java/android/speech/tts/ITextToSpeechService.aidl +++ b/core/java/android/speech/tts/ITextToSpeechService.aidl @@ -20,6 +20,8 @@ import android.net.Uri; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.speech.tts.ITextToSpeechCallback; +import android.speech.tts.VoiceInfo; +import android.speech.tts.SynthesisRequestV2; /** * Interface for TextToSpeech to talk to TextToSpeechService. @@ -70,9 +72,10 @@ interface ITextToSpeechService { * TextToSpeech object. * @param duration Number of milliseconds of silence to play. * @param queueMode Determines what to do to requests already in the queue. - * @param param Request parameters. + * @param utteranceId Unique id used to identify this request in callbacks. */ - int playSilence(in IBinder callingInstance, in long duration, in int queueMode, in Bundle params); + int playSilence(in IBinder callingInstance, in long duration, in int queueMode, + in String utteranceId); /** * Checks whether the service is currently playing some audio. @@ -90,7 +93,6 @@ interface ITextToSpeechService { /** * Returns the language, country and variant currently being used by the TTS engine. - * * Can be called from multiple threads. * * @return A 3-element array, containing language (ISO 3-letter code), @@ -99,7 +101,7 @@ interface ITextToSpeechService { * be empty too. */ String[] getLanguage(); - + /** * Returns a default TTS language, country and variant as set by the user. * @@ -111,7 +113,7 @@ interface ITextToSpeechService { * be empty too. */ String[] getClientDefaultLanguage(); - + /** * Checks whether the engine supports a given language. * @@ -137,7 +139,7 @@ interface ITextToSpeechService { * @param country ISO-3 country code. May be empty or null. * @param variant Language variant. May be empty or null. * @return An array of strings containing the set of features supported for - * the supplied locale. The array of strings must not contain + * the supplied locale. The array of strings must not contain * duplicates. */ String[] getFeaturesForLanguage(in String lang, in String country, in String variant); @@ -169,4 +171,44 @@ interface ITextToSpeechService { */ void setCallback(in IBinder caller, ITextToSpeechCallback cb); + /** + * Tells the engine to synthesize some speech and play it back. + * + * @param callingInstance a binder representing the identity of the calling + * TextToSpeech object. + * @param text The text to synthesize. + * @param queueMode Determines what to do to requests already in the queue. + * @param request Request parameters. + */ + int speakV2(in IBinder callingInstance, in SynthesisRequestV2 request); + + /** + * Tells the engine to synthesize some speech and write it to a file. + * + * @param callingInstance a binder representing the identity of the calling + * TextToSpeech object. + * @param text The text to synthesize. + * @param fileDescriptor The file descriptor to write the synthesized audio to. Has to be + writable. + * @param request Request parameters. + */ + int synthesizeToFileDescriptorV2(in IBinder callingInstance, + in ParcelFileDescriptor fileDescriptor, in SynthesisRequestV2 request); + + /** + * Plays an existing audio resource. V2 version + * + * @param callingInstance a binder representing the identity of the calling + * TextToSpeech object. + * @param audioUri URI for the audio resource (a file or android.resource URI) + * @param utteranceId Unique identifier. + * @param audioParameters Parameters for audio playback (from {@link SynthesisRequestV2}). + */ + int playAudioV2(in IBinder callingInstance, in Uri audioUri, in String utteranceId, + in Bundle audioParameters); + + /** + * Request the list of available voices from the service. + */ + List<VoiceInfo> getVoicesInfo(); } diff --git a/core/java/android/speech/tts/PlaybackQueueItem.java b/core/java/android/speech/tts/PlaybackQueueItem.java index d0957ff..b2e323e 100644 --- a/core/java/android/speech/tts/PlaybackQueueItem.java +++ b/core/java/android/speech/tts/PlaybackQueueItem.java @@ -22,6 +22,16 @@ abstract class PlaybackQueueItem implements Runnable { return mDispatcher; } + @Override public abstract void run(); - abstract void stop(boolean isError); + + /** + * Stop the playback. + * + * @param errorCode Cause of the stop. Can be either one of the error codes from + * {@link android.speech.tts.TextToSpeechClient.Status} or + * {@link android.speech.tts.TextToSpeechClient.Status#STOPPED} + * if stopped on a client request. + */ + abstract void stop(int errorCode); } diff --git a/core/java/android/speech/tts/PlaybackSynthesisCallback.java b/core/java/android/speech/tts/PlaybackSynthesisCallback.java index c99f201..e345e89 100644 --- a/core/java/android/speech/tts/PlaybackSynthesisCallback.java +++ b/core/java/android/speech/tts/PlaybackSynthesisCallback.java @@ -55,20 +55,20 @@ class PlaybackSynthesisCallback extends AbstractSynthesisCallback { private final AudioPlaybackHandler mAudioTrackHandler; // A request "token", which will be non null after start() has been called. private SynthesisPlaybackQueueItem mItem = null; - // Whether this request has been stopped. This is useful for keeping - // track whether stop() has been called before start(). In all other cases, - // a non-null value of mItem will provide the same information. - private boolean mStopped = false; private volatile boolean mDone = false; + /** Status code of synthesis */ + protected int mStatusCode; + private final UtteranceProgressDispatcher mDispatcher; private final Object mCallerIdentity; - private final EventLogger mLogger; + private final AbstractEventLogger mLogger; PlaybackSynthesisCallback(int streamType, float volume, float pan, AudioPlaybackHandler audioTrackHandler, UtteranceProgressDispatcher dispatcher, - Object callerIdentity, EventLogger logger) { + Object callerIdentity, AbstractEventLogger logger, boolean clientIsUsingV2) { + super(clientIsUsingV2); mStreamType = streamType; mVolume = volume; mPan = pan; @@ -76,28 +76,25 @@ class PlaybackSynthesisCallback extends AbstractSynthesisCallback { mDispatcher = dispatcher; mCallerIdentity = callerIdentity; mLogger = logger; + mStatusCode = TextToSpeechClient.Status.SUCCESS; } @Override void stop() { - stopImpl(false); - } - - void stopImpl(boolean wasError) { if (DBG) Log.d(TAG, "stop()"); - // Note that mLogger.mError might be true too at this point. - mLogger.onStopped(); - SynthesisPlaybackQueueItem item; synchronized (mStateLock) { - if (mStopped) { + if (mDone) { + return; + } + if (mStatusCode == TextToSpeechClient.Status.STOPPED) { Log.w(TAG, "stop() called twice"); return; } item = mItem; - mStopped = true; + mStatusCode = TextToSpeechClient.Status.STOPPED; } if (item != null) { @@ -105,19 +102,15 @@ class PlaybackSynthesisCallback extends AbstractSynthesisCallback { // point it will write an additional buffer to the item - but we // won't worry about that because the audio playback queue will be cleared // soon after (see SynthHandler#stop(String). - item.stop(wasError); + item.stop(TextToSpeechClient.Status.STOPPED); } else { // This happens when stop() or error() were called before start() was. // In all other cases, mAudioTrackHandler.stop() will // result in onSynthesisDone being called, and we will // write data there. - mLogger.onWriteData(); - - if (wasError) { - // We have to dispatch the error ourselves. - mDispatcher.dispatchOnError(); - } + mLogger.onCompleted(TextToSpeechClient.Status.STOPPED); + mDispatcher.dispatchOnStop(); } } @@ -129,26 +122,42 @@ class PlaybackSynthesisCallback extends AbstractSynthesisCallback { } @Override - boolean isDone() { - return mDone; + public boolean hasStarted() { + synchronized (mStateLock) { + return mItem != null; + } } @Override - public int start(int sampleRateInHz, int audioFormat, int channelCount) { - if (DBG) { - Log.d(TAG, "start(" + sampleRateInHz + "," + audioFormat - + "," + channelCount + ")"); + public boolean hasFinished() { + synchronized (mStateLock) { + return mDone; } + } + + @Override + public int start(int sampleRateInHz, int audioFormat, int channelCount) { + if (DBG) Log.d(TAG, "start(" + sampleRateInHz + "," + audioFormat + "," + channelCount + + ")"); int channelConfig = BlockingAudioTrack.getChannelConfig(channelCount); - if (channelConfig == 0) { - Log.e(TAG, "Unsupported number of channels :" + channelCount); - return TextToSpeech.ERROR; - } synchronized (mStateLock) { - if (mStopped) { + if (channelConfig == 0) { + Log.e(TAG, "Unsupported number of channels :" + channelCount); + mStatusCode = TextToSpeechClient.Status.ERROR_OUTPUT; + return TextToSpeech.ERROR; + } + if (mStatusCode == TextToSpeechClient.Status.STOPPED) { if (DBG) Log.d(TAG, "stop() called before start(), returning."); + return errorCodeOnStop(); + } + if (mStatusCode != TextToSpeechClient.Status.SUCCESS) { + if (DBG) Log.d(TAG, "Error was raised"); + return TextToSpeech.ERROR; + } + if (mItem != null) { + Log.e(TAG, "Start called twice"); return TextToSpeech.ERROR; } SynthesisPlaybackQueueItem item = new SynthesisPlaybackQueueItem( @@ -161,13 +170,11 @@ class PlaybackSynthesisCallback extends AbstractSynthesisCallback { return TextToSpeech.SUCCESS; } - @Override public int audioAvailable(byte[] buffer, int offset, int length) { - if (DBG) { - Log.d(TAG, "audioAvailable(byte[" + buffer.length + "]," - + offset + "," + length + ")"); - } + if (DBG) Log.d(TAG, "audioAvailable(byte[" + buffer.length + "]," + offset + "," + length + + ")"); + if (length > getMaxBufferSize() || length <= 0) { throw new IllegalArgumentException("buffer is too large or of zero length (" + + length + " bytes)"); @@ -175,9 +182,17 @@ class PlaybackSynthesisCallback extends AbstractSynthesisCallback { SynthesisPlaybackQueueItem item = null; synchronized (mStateLock) { - if (mItem == null || mStopped) { + if (mItem == null) { + mStatusCode = TextToSpeechClient.Status.ERROR_OUTPUT; return TextToSpeech.ERROR; } + if (mStatusCode != TextToSpeechClient.Status.SUCCESS) { + if (DBG) Log.d(TAG, "Error was raised"); + return TextToSpeech.ERROR; + } + if (mStatusCode == TextToSpeechClient.Status.STOPPED) { + return errorCodeOnStop(); + } item = mItem; } @@ -190,11 +205,13 @@ class PlaybackSynthesisCallback extends AbstractSynthesisCallback { try { item.put(bufferCopy); } catch (InterruptedException ie) { - return TextToSpeech.ERROR; + synchronized (mStateLock) { + mStatusCode = TextToSpeechClient.Status.ERROR_OUTPUT; + return TextToSpeech.ERROR; + } } mLogger.onEngineDataReceived(); - return TextToSpeech.SUCCESS; } @@ -202,35 +219,74 @@ class PlaybackSynthesisCallback extends AbstractSynthesisCallback { public int done() { if (DBG) Log.d(TAG, "done()"); + int statusCode = 0; SynthesisPlaybackQueueItem item = null; synchronized (mStateLock) { if (mDone) { Log.w(TAG, "Duplicate call to done()"); + // Not an error that would prevent synthesis. Hence no + // setStatusCode return TextToSpeech.ERROR; } - + if (mStatusCode == TextToSpeechClient.Status.STOPPED) { + if (DBG) Log.d(TAG, "Request has been aborted."); + return errorCodeOnStop(); + } mDone = true; if (mItem == null) { + // .done() was called before .start. Treat it as successful synthesis + // for a client, despite service bad implementation. + Log.w(TAG, "done() was called before start() call"); + if (mStatusCode == TextToSpeechClient.Status.SUCCESS) { + mDispatcher.dispatchOnSuccess(); + } else { + mDispatcher.dispatchOnError(mStatusCode); + } + mLogger.onEngineComplete(); return TextToSpeech.ERROR; } item = mItem; + statusCode = mStatusCode; } - item.done(); + // Signal done or error to item + if (statusCode == TextToSpeechClient.Status.SUCCESS) { + item.done(); + } else { + item.stop(statusCode); + } mLogger.onEngineComplete(); - return TextToSpeech.SUCCESS; } @Override public void error() { + error(TextToSpeechClient.Status.ERROR_SYNTHESIS); + } + + @Override + public void error(int errorCode) { if (DBG) Log.d(TAG, "error() [will call stop]"); - // Currently, this call will not be logged if error( ) is called - // before start. - mLogger.onError(); - stopImpl(true); + synchronized (mStateLock) { + if (mDone) { + return; + } + mStatusCode = errorCode; + } } + @Override + public int fallback() { + synchronized (mStateLock) { + if (hasStarted() || hasFinished()) { + return TextToSpeech.ERROR; + } + + mDispatcher.dispatchOnFallback(); + mStatusCode = TextToSpeechClient.Status.SUCCESS; + return TextToSpeechClient.Status.SUCCESS; + } + } } diff --git a/core/java/android/speech/tts/RequestConfig.java b/core/java/android/speech/tts/RequestConfig.java new file mode 100644 index 0000000..4b5385f --- /dev/null +++ b/core/java/android/speech/tts/RequestConfig.java @@ -0,0 +1,213 @@ +package android.speech.tts; + +import android.media.AudioManager; +import android.os.Bundle; + +/** + * Synthesis request configuration. + * + * This class is immutable, and can only be constructed using + * {@link RequestConfig.Builder}. + */ +public final class RequestConfig { + + /** Builder for constructing RequestConfig objects. */ + public static final class Builder { + private VoiceInfo mCurrentVoiceInfo; + private Bundle mVoiceParams; + private Bundle mAudioParams; + + Builder(VoiceInfo currentVoiceInfo, Bundle voiceParams, Bundle audioParams) { + mCurrentVoiceInfo = currentVoiceInfo; + mVoiceParams = voiceParams; + mAudioParams = audioParams; + } + + /** + * Create new RequestConfig builder. + */ + public static Builder newBuilder() { + return new Builder(null, new Bundle(), new Bundle()); + } + + /** + * Create new RequestConfig builder. + * @param prototype + * Prototype of new RequestConfig. Copies all fields of the + * prototype to the constructed object. + */ + public static Builder newBuilder(RequestConfig prototype) { + return new Builder(prototype.mCurrentVoiceInfo, + (Bundle)prototype.mVoiceParams.clone(), + (Bundle)prototype.mAudioParams.clone()); + } + + /** Set voice for request. Will reset voice parameters to the defaults. */ + public Builder setVoice(VoiceInfo voice) { + mCurrentVoiceInfo = voice; + mVoiceParams = (Bundle)voice.getParamsWithDefaults().clone(); + return this; + } + + /** + * Set request voice parameter. + * + * @param paramName + * The name of the parameter. It has to be one of the keys + * from {@link VoiceInfo#getParamsWithDefaults()} + * @param value + * Value of the parameter. Its type can be one of: Integer, Float, + * Boolean, String, VoiceInfo (will be set as a String, result of a call to + * the {@link VoiceInfo#getName()}) or byte[]. It has to be of the same type + * as the default value from {@link VoiceInfo#getParamsWithDefaults()} + * for that parameter. + * @throws IllegalArgumentException + * If paramName is not a valid parameter name or its value is of a wrong + * type. + * @throws IllegalStateException + * If no voice is set. + */ + public Builder setVoiceParam(String paramName, Object value){ + if (mCurrentVoiceInfo == null) { + throw new IllegalStateException( + "Couldn't set voice parameter, no voice is set"); + } + Object defaultValue = mCurrentVoiceInfo.getParamsWithDefaults().get(paramName); + if (defaultValue == null) { + throw new IllegalArgumentException( + "Parameter \"" + paramName + "\" is not available in set voice with " + + "name: " + mCurrentVoiceInfo.getName()); + } + + // If it's VoiceInfo, get its name + if (value instanceof VoiceInfo) { + value = ((VoiceInfo)value).getName(); + } + + // Check type information + if (!defaultValue.getClass().equals(value.getClass())) { + throw new IllegalArgumentException( + "Parameter \"" + paramName +"\" is of different type. Value passed has " + + "type " + value.getClass().getSimpleName() + " but should have " + + "type " + defaultValue.getClass().getSimpleName()); + } + + setParam(mVoiceParams, paramName, value); + return this; + } + + /** + * Set request audio parameter. + * + * Doesn't requires a set voice. + * + * @param paramName + * Name of parameter. + * @param value + * Value of parameter. Its type can be one of: Integer, Float, Boolean, String + * or byte[]. + */ + public Builder setAudioParam(String paramName, Object value) { + setParam(mAudioParams, paramName, value); + return this; + } + + /** + * Set the {@link TextToSpeechClient.Params#AUDIO_PARAM_STREAM} audio parameter. + * + * @param streamId One of the STREAM_ constants defined in {@link AudioManager}. + */ + public void setAudioParamStream(int streamId) { + setAudioParam(TextToSpeechClient.Params.AUDIO_PARAM_STREAM, streamId); + } + + /** + * Set the {@link TextToSpeechClient.Params#AUDIO_PARAM_VOLUME} audio parameter. + * + * @param volume Float in range of 0.0 to 1.0. + */ + public void setAudioParamVolume(float volume) { + setAudioParam(TextToSpeechClient.Params.AUDIO_PARAM_VOLUME, volume); + } + + /** + * Set the {@link TextToSpeechClient.Params#AUDIO_PARAM_PAN} audio parameter. + * + * @param pan Float in range of -1.0 to +1.0. + */ + public void setAudioParamPan(float pan) { + setAudioParam(TextToSpeechClient.Params.AUDIO_PARAM_PAN, pan); + } + + private void setParam(Bundle bundle, String featureName, Object value) { + if (value instanceof String) { + bundle.putString(featureName, (String)value); + } else if(value instanceof byte[]) { + bundle.putByteArray(featureName, (byte[])value); + } else if(value instanceof Integer) { + bundle.putInt(featureName, (Integer)value); + } else if(value instanceof Float) { + bundle.putFloat(featureName, (Float)value); + } else if(value instanceof Double) { + bundle.putFloat(featureName, (Float)value); + } else if(value instanceof Boolean) { + bundle.putBoolean(featureName, (Boolean)value); + } else { + throw new IllegalArgumentException("Illegal type of object"); + } + return; + } + + /** + * Build new RequestConfig instance. + */ + public RequestConfig build() { + RequestConfig config = + new RequestConfig(mCurrentVoiceInfo, mVoiceParams, mAudioParams); + return config; + } + } + + private RequestConfig(VoiceInfo voiceInfo, Bundle voiceParams, Bundle audioParams) { + mCurrentVoiceInfo = voiceInfo; + mVoiceParams = voiceParams; + mAudioParams = audioParams; + } + + /** + * Currently set voice. + */ + private final VoiceInfo mCurrentVoiceInfo; + + /** + * Voice parameters bundle. + */ + private final Bundle mVoiceParams; + + /** + * Audio parameters bundle. + */ + private final Bundle mAudioParams; + + /** + * @return Currently set request voice. + */ + public VoiceInfo getVoice() { + return mCurrentVoiceInfo; + } + + /** + * @return Request audio parameters. + */ + public Bundle getAudioParams() { + return mAudioParams; + } + + /** + * @return Request voice parameters. + */ + public Bundle getVoiceParams() { + return mVoiceParams; + } + +} diff --git a/core/java/android/speech/tts/RequestConfigHelper.java b/core/java/android/speech/tts/RequestConfigHelper.java new file mode 100644 index 0000000..b25c985 --- /dev/null +++ b/core/java/android/speech/tts/RequestConfigHelper.java @@ -0,0 +1,170 @@ +package android.speech.tts; + +import android.speech.tts.TextToSpeechClient.EngineStatus; + +import java.util.Locale; + +/** + * Set of common heuristics for selecting {@link VoiceInfo} from + * {@link TextToSpeechClient#getEngineStatus()} output. + */ +public final class RequestConfigHelper { + private RequestConfigHelper() {} + + /** + * Interface for scoring VoiceInfo object. + */ + public static interface VoiceScorer { + /** + * Score VoiceInfo. If the score is less than or equal to zero, that voice is discarded. + * If two voices have same desired primary characteristics (highest quality, lowest + * latency or others), the one with the higher score is selected. + */ + public int scoreVoice(VoiceInfo voiceInfo); + } + + /** + * Score positively voices that exactly match the locale supplied to the constructor. + */ + public static final class ExactLocaleMatcher implements VoiceScorer { + private final Locale mLocale; + + /** + * Score positively voices that exactly match the given locale + * @param locale Reference locale. If null, the default locale will be used. + */ + public ExactLocaleMatcher(Locale locale) { + if (locale == null) { + mLocale = Locale.getDefault(); + } else { + mLocale = locale; + } + } + @Override + public int scoreVoice(VoiceInfo voiceInfo) { + return mLocale.equals(voiceInfo.getLocale()) ? 1 : 0; + } + } + + /** + * Score positively voices that match exactly the given locale (score 3) + * or that share same language and country (score 2), or that share just a language (score 1). + */ + public static final class LanguageMatcher implements VoiceScorer { + private final Locale mLocale; + + /** + * Score positively voices with similar locale. + * @param locale Reference locale. If null, default will be used. + */ + public LanguageMatcher(Locale locale) { + if (locale == null) { + mLocale = Locale.getDefault(); + } else { + mLocale = locale; + } + } + + @Override + public int scoreVoice(VoiceInfo voiceInfo) { + final Locale voiceLocale = voiceInfo.getLocale(); + if (mLocale.equals(voiceLocale)) { + return 3; + } else { + if (mLocale.getLanguage().equals(voiceLocale.getLanguage())) { + if (mLocale.getCountry().equals(voiceLocale.getCountry())) { + return 2; + } + return 1; + } + return 0; + } + } + } + + /** + * Get the highest quality voice from voices that score more than zero from the passed scorer. + * If there is more than one voice with the same highest quality, then this method returns one + * with the highest score. If they share same score as well, one with the lower index in the + * voices list is returned. + * + * @param engineStatus + * Voices status received from a {@link TextToSpeechClient#getEngineStatus()} call. + * @param voiceScorer + * Used to discard unsuitable voices and help settle cases where more than + * one voice has the desired characteristic. + * @param hasToBeEmbedded + * If true, require the voice to be an embedded voice (no network + * access will be required for synthesis). + */ + private static VoiceInfo getHighestQualityVoice(EngineStatus engineStatus, + VoiceScorer voiceScorer, boolean hasToBeEmbedded) { + VoiceInfo bestVoice = null; + int bestScoreMatch = 1; + int bestVoiceQuality = 0; + + for (VoiceInfo voice : engineStatus.getVoices()) { + int score = voiceScorer.scoreVoice(voice); + if (score <= 0 || hasToBeEmbedded && voice.getRequiresNetworkConnection() + || voice.getQuality() < bestVoiceQuality) { + continue; + } + + if (bestVoice == null || + voice.getQuality() > bestVoiceQuality || + score > bestScoreMatch) { + bestVoice = voice; + bestScoreMatch = score; + bestVoiceQuality = voice.getQuality(); + } + } + return bestVoice; + } + + /** + * Get highest quality voice. + * + * Highest quality voice is selected from voices that score more than zero from the passed + * scorer. If there is more than one voice with the same highest quality, then this method + * will return one with the highest score. If they share same score as well, one with the lower + * index in the voices list is returned. + + * @param engineStatus + * Voices status received from a {@link TextToSpeechClient#getEngineStatus()} call. + * @param hasToBeEmbedded + * If true, require the voice to be an embedded voice (no network + * access will be required for synthesis). + * @param voiceScorer + * Scorer is used to discard unsuitable voices and help settle cases where more than + * one voice has highest quality. + * @return RequestConfig with selected voice or null if suitable voice was not found. + */ + public static RequestConfig highestQuality(EngineStatus engineStatus, + boolean hasToBeEmbedded, VoiceScorer voiceScorer) { + VoiceInfo voice = getHighestQualityVoice(engineStatus, voiceScorer, hasToBeEmbedded); + if (voice == null) { + return null; + } + return RequestConfig.Builder.newBuilder().setVoice(voice).build(); + } + + /** + * Get highest quality voice for the default locale. + * + * Call {@link #highestQuality(EngineStatus, boolean, VoiceScorer)} with + * {@link LanguageMatcher} set to device default locale. + * + * @param engineStatus + * Voices status received from a {@link TextToSpeechClient#getEngineStatus()} call. + * @param hasToBeEmbedded + * If true, require the voice to be an embedded voice (no network + * access will be required for synthesis). + * @return RequestConfig with selected voice or null if suitable voice was not found. + */ + public static RequestConfig highestQuality(EngineStatus engineStatus, + boolean hasToBeEmbedded) { + return highestQuality(engineStatus, hasToBeEmbedded, + new LanguageMatcher(Locale.getDefault())); + } + +} diff --git a/core/java/android/speech/tts/SilencePlaybackQueueItem.java b/core/java/android/speech/tts/SilencePlaybackQueueItem.java index a5e47ae..88b7c70 100644 --- a/core/java/android/speech/tts/SilencePlaybackQueueItem.java +++ b/core/java/android/speech/tts/SilencePlaybackQueueItem.java @@ -17,7 +17,6 @@ package android.speech.tts; import android.os.ConditionVariable; import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher; -import android.util.Log; class SilencePlaybackQueueItem extends PlaybackQueueItem { private final ConditionVariable mCondVar = new ConditionVariable(); @@ -32,14 +31,20 @@ class SilencePlaybackQueueItem extends PlaybackQueueItem { @Override public void run() { getDispatcher().dispatchOnStart(); + boolean wasStopped = false; if (mSilenceDurationMs > 0) { - mCondVar.block(mSilenceDurationMs); + wasStopped = mCondVar.block(mSilenceDurationMs); } - getDispatcher().dispatchOnDone(); + if (wasStopped) { + getDispatcher().dispatchOnStop(); + } else { + getDispatcher().dispatchOnSuccess(); + } + } @Override - void stop(boolean isError) { + void stop(int errorCode) { mCondVar.open(); } } diff --git a/core/java/android/speech/tts/SynthesisCallback.java b/core/java/android/speech/tts/SynthesisCallback.java index f98bb09..bc2f239 100644 --- a/core/java/android/speech/tts/SynthesisCallback.java +++ b/core/java/android/speech/tts/SynthesisCallback.java @@ -26,7 +26,9 @@ package android.speech.tts; * indicate that an error has occurred, but if the call is made after a call * to {@link #done}, it might be discarded. * - * After {@link #start} been called, {@link #done} must be called regardless of errors. + * {@link #done} must be called at the end of synthesis, regardless of errors. + * + * All methods can be only called on the synthesis thread. */ public interface SynthesisCallback { /** @@ -41,13 +43,16 @@ public interface SynthesisCallback { * request. * * This method should only be called on the synthesis thread, - * while in {@link TextToSpeechService#onSynthesizeText}. + * while in {@link TextToSpeechService#onSynthesizeText} or + * {@link TextToSpeechService#onSynthesizeTextV2}. * * @param sampleRateInHz Sample rate in HZ of the generated audio. * @param audioFormat Audio format of the generated audio. Must be one of * the ENCODING_ constants defined in {@link android.media.AudioFormat}. * @param channelCount The number of channels. Must be {@code 1} or {@code 2}. - * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}. + * @return {@link TextToSpeech#SUCCESS}, {@link TextToSpeech#ERROR}. + * {@link TextToSpeechClient.Status#STOPPED} is also possible if called in context of + * {@link TextToSpeechService#onSynthesizeTextV2}. */ public int start(int sampleRateInHz, int audioFormat, int channelCount); @@ -55,7 +60,8 @@ public interface SynthesisCallback { * The service should call this method when synthesized audio is ready for consumption. * * This method should only be called on the synthesis thread, - * while in {@link TextToSpeechService#onSynthesizeText}. + * while in {@link TextToSpeechService#onSynthesizeText} or + * {@link TextToSpeechService#onSynthesizeTextV2}. * * @param buffer The generated audio data. This method will not hold on to {@code buffer}, * so the caller is free to modify it after this method returns. @@ -63,6 +69,8 @@ public interface SynthesisCallback { * @param length The number of bytes of audio data in {@code buffer}. This must be * less than or equal to the return value of {@link #getMaxBufferSize}. * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}. + * {@link TextToSpeechClient.Status#STOPPED} is also possible if called in context of + * {@link TextToSpeechService#onSynthesizeTextV2}. */ public int audioAvailable(byte[] buffer, int offset, int length); @@ -71,11 +79,14 @@ public interface SynthesisCallback { * been passed to {@link #audioAvailable}. * * This method should only be called on the synthesis thread, - * while in {@link TextToSpeechService#onSynthesizeText}. + * while in {@link TextToSpeechService#onSynthesizeText} or + * {@link TextToSpeechService#onSynthesizeTextV2}. * - * This method has to be called if {@link #start} was called. + * This method has to be called if {@link #start} and/or {@link #error} was called. * * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}. + * {@link TextToSpeechClient.Status#STOPPED} is also possible if called in context of + * {@link TextToSpeechService#onSynthesizeTextV2}. */ public int done(); @@ -87,4 +98,58 @@ public interface SynthesisCallback { */ public void error(); -}
\ No newline at end of file + + /** + * The service should call this method if the speech synthesis fails. + * + * This method should only be called on the synthesis thread, + * while in {@link TextToSpeechService#onSynthesizeText} or + * {@link TextToSpeechService#onSynthesizeTextV2}. + * + * @param errorCode Error code to pass to the client. One of the ERROR_ values from + * {@link TextToSpeechClient.Status} + */ + public void error(int errorCode); + + /** + * Communicate to client that the original request can't be done and client-requested + * fallback is happening. + * + * Fallback can be requested by the client by setting + * {@link TextToSpeechClient.Params#FALLBACK_VOICE_NAME} voice parameter with a id of + * the voice that is expected to be used for the fallback. + * + * This method will fail if user called {@link #start(int, int, int)} and/or + * {@link #done()}. + * + * This method should only be called on the synthesis thread, + * while in {@link TextToSpeechService#onSynthesizeTextV2}. + * + * @return {@link TextToSpeech#SUCCESS}, {@link TextToSpeech#ERROR} if client already + * called {@link #start(int, int, int)}, {@link TextToSpeechClient.Status#STOPPED} + * if stop was requested. + */ + public int fallback(); + + /** + * Check if {@link #start} was called or not. + * + * This method should only be called on the synthesis thread, + * while in {@link TextToSpeechService#onSynthesizeText} or + * {@link TextToSpeechService#onSynthesizeTextV2}. + * + * Useful for checking if a fallback from network request is possible. + */ + public boolean hasStarted(); + + /** + * Check if {@link #done} was called or not. + * + * This method should only be called on the synthesis thread, + * while in {@link TextToSpeechService#onSynthesizeText} or + * {@link TextToSpeechService#onSynthesizeTextV2}. + * + * Useful for checking if a fallback from network request is possible. + */ + public boolean hasFinished(); +} diff --git a/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java b/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java index e853c9e..b424356 100644 --- a/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java +++ b/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java @@ -57,23 +57,22 @@ final class SynthesisPlaybackQueueItem extends PlaybackQueueItem { */ private volatile boolean mStopped; private volatile boolean mDone; - private volatile boolean mIsError; + private volatile int mStatusCode; private final BlockingAudioTrack mAudioTrack; - private final EventLogger mLogger; - + private final AbstractEventLogger mLogger; SynthesisPlaybackQueueItem(int streamType, int sampleRate, int audioFormat, int channelCount, float volume, float pan, UtteranceProgressDispatcher dispatcher, - Object callerIdentity, EventLogger logger) { + Object callerIdentity, AbstractEventLogger logger) { super(dispatcher, callerIdentity); mUnconsumedBytes = 0; mStopped = false; mDone = false; - mIsError = false; + mStatusCode = TextToSpeechClient.Status.SUCCESS; mAudioTrack = new BlockingAudioTrack(streamType, sampleRate, audioFormat, channelCount, volume, pan); @@ -86,9 +85,8 @@ final class SynthesisPlaybackQueueItem extends PlaybackQueueItem { final UtteranceProgressDispatcher dispatcher = getDispatcher(); dispatcher.dispatchOnStart(); - if (!mAudioTrack.init()) { - dispatcher.dispatchOnError(); + dispatcher.dispatchOnError(TextToSpeechClient.Status.ERROR_OUTPUT); return; } @@ -112,23 +110,25 @@ final class SynthesisPlaybackQueueItem extends PlaybackQueueItem { mAudioTrack.waitAndRelease(); - if (mIsError) { - dispatcher.dispatchOnError(); + if (mStatusCode == TextToSpeechClient.Status.SUCCESS) { + dispatcher.dispatchOnSuccess(); + } else if(mStatusCode == TextToSpeechClient.Status.STOPPED) { + dispatcher.dispatchOnStop(); } else { - dispatcher.dispatchOnDone(); + dispatcher.dispatchOnError(mStatusCode); } - mLogger.onWriteData(); + mLogger.onCompleted(mStatusCode); } @Override - void stop(boolean isError) { + void stop(int statusCode) { try { mListLock.lock(); // Update our internal state. mStopped = true; - mIsError = isError; + mStatusCode = statusCode; // Wake up the audio playback thread if it was waiting on take(). // take() will return null since mStopped was true, and will then diff --git a/core/java/android/speech/tts/SynthesisRequestV2.aidl b/core/java/android/speech/tts/SynthesisRequestV2.aidl new file mode 100644 index 0000000..2ac7da6 --- /dev/null +++ b/core/java/android/speech/tts/SynthesisRequestV2.aidl @@ -0,0 +1,20 @@ +/* +** +** Copyright 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.speech.tts; + +parcelable SynthesisRequestV2;
\ No newline at end of file diff --git a/core/java/android/speech/tts/SynthesisRequestV2.java b/core/java/android/speech/tts/SynthesisRequestV2.java new file mode 100644 index 0000000..a1da49c --- /dev/null +++ b/core/java/android/speech/tts/SynthesisRequestV2.java @@ -0,0 +1,144 @@ +package android.speech.tts; + +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.speech.tts.TextToSpeechClient.UtteranceId; + +/** + * Service-side representation of a synthesis request from a V2 API client. Contains: + * <ul> + * <li>The utterance to synthesize</li> + * <li>The id of the utterance (String, result of {@link UtteranceId#toUniqueString()}</li> + * <li>The synthesis voice name (String, result of {@link VoiceInfo#getName()})</li> + * <li>Voice parameters (Bundle of parameters)</li> + * <li>Audio parameters (Bundle of parameters)</li> + * </ul> + */ +public final class SynthesisRequestV2 implements Parcelable { + /** Synthesis utterance. */ + private final String mText; + + /** Synthesis id. */ + private final String mUtteranceId; + + /** Voice ID. */ + private final String mVoiceName; + + /** Voice Parameters. */ + private final Bundle mVoiceParams; + + /** Audio Parameters. */ + private final Bundle mAudioParams; + + /** + * Constructor for test purposes. + */ + public SynthesisRequestV2(String text, String utteranceId, String voiceName, + Bundle voiceParams, Bundle audioParams) { + this.mText = text; + this.mUtteranceId = utteranceId; + this.mVoiceName = voiceName; + this.mVoiceParams = voiceParams; + this.mAudioParams = audioParams; + } + + /** + * Parcel based constructor. + * + * @hide + */ + public SynthesisRequestV2(Parcel in) { + this.mText = in.readString(); + this.mUtteranceId = in.readString(); + this.mVoiceName = in.readString(); + this.mVoiceParams = in.readBundle(); + this.mAudioParams = in.readBundle(); + } + + SynthesisRequestV2(String text, String utteranceId, RequestConfig rconfig) { + this.mText = text; + this.mUtteranceId = utteranceId; + this.mVoiceName = rconfig.getVoice().getName(); + this.mVoiceParams = rconfig.getVoiceParams(); + this.mAudioParams = rconfig.getAudioParams(); + } + + /** + * Write to parcel. + * + * @hide + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mText); + dest.writeString(mUtteranceId); + dest.writeString(mVoiceName); + dest.writeBundle(mVoiceParams); + dest.writeBundle(mAudioParams); + } + + /** + * @return the text which should be synthesized. + */ + public String getText() { + return mText; + } + + /** + * @return the id of the synthesis request. It's an output of a call to the + * {@link UtteranceId#toUniqueString()} method of the {@link UtteranceId} associated with + * this request. + */ + public String getUtteranceId() { + return mUtteranceId; + } + + /** + * @return the name of the voice to use for this synthesis request. Result of a call to + * the {@link VoiceInfo#getName()} method. + */ + public String getVoiceName() { + return mVoiceName; + } + + /** + * @return bundle of voice parameters. + */ + public Bundle getVoiceParams() { + return mVoiceParams; + } + + /** + * @return bundle of audio parameters. + */ + public Bundle getAudioParams() { + return mAudioParams; + } + + /** + * Parcel creators. + * + * @hide + */ + public static final Parcelable.Creator<SynthesisRequestV2> CREATOR = + new Parcelable.Creator<SynthesisRequestV2>() { + @Override + public SynthesisRequestV2 createFromParcel(Parcel source) { + return new SynthesisRequestV2(source); + } + + @Override + public SynthesisRequestV2[] newArray(int size) { + return new SynthesisRequestV2[size]; + } + }; + + /** + * @hide + */ + @Override + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java index 2752085..02152fb 100644 --- a/core/java/android/speech/tts/TextToSpeech.java +++ b/core/java/android/speech/tts/TextToSpeech.java @@ -54,7 +54,9 @@ import java.util.Set; * When you are done using the TextToSpeech instance, call the {@link #shutdown()} method * to release the native resources used by the TextToSpeech engine. * + * @deprecated Use {@link TextToSpeechClient} instead */ +@Deprecated public class TextToSpeech { private static final String TAG = "TextToSpeech"; @@ -970,7 +972,7 @@ public class TextToSpeech { @Override public Integer run(ITextToSpeechService service) throws RemoteException { return service.playSilence(getCallerIdentity(), durationInMs, queueMode, - getParams(params)); + params == null ? null : params.get(Engine.KEY_PARAM_UTTERANCE_ID)); } }, ERROR, "playSilence"); } @@ -1443,8 +1445,17 @@ public class TextToSpeech { private boolean mEstablished; private final ITextToSpeechCallback.Stub mCallback = new ITextToSpeechCallback.Stub() { + public void onStop(String utteranceId) throws RemoteException { + // do nothing + }; + + @Override + public void onFallback(String utteranceId) throws RemoteException { + // do nothing + } + @Override - public void onDone(String utteranceId) { + public void onSuccess(String utteranceId) { UtteranceProgressListener listener = mUtteranceProgressListener; if (listener != null) { listener.onDone(utteranceId); @@ -1452,7 +1463,7 @@ public class TextToSpeech { } @Override - public void onError(String utteranceId) { + public void onError(String utteranceId, int errorCode) { UtteranceProgressListener listener = mUtteranceProgressListener; if (listener != null) { listener.onError(utteranceId); @@ -1466,6 +1477,11 @@ public class TextToSpeech { listener.onStart(utteranceId); } } + + @Override + public void onVoicesInfoChange(List<VoiceInfo> voicesInfo) throws RemoteException { + // Ignore it + } }; private class SetupConnectionAsyncTask extends AsyncTask<Void, Void, Integer> { diff --git a/core/java/android/speech/tts/TextToSpeechClient.java b/core/java/android/speech/tts/TextToSpeechClient.java new file mode 100644 index 0000000..c6a14f2 --- /dev/null +++ b/core/java/android/speech/tts/TextToSpeechClient.java @@ -0,0 +1,1047 @@ +/* + * 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.speech.tts; + +import android.app.Activity; +import android.app.Application; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.media.AudioManager; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.speech.tts.ITextToSpeechCallback; +import android.speech.tts.ITextToSpeechService; +import android.util.Log; +import android.util.Pair; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Synthesizes speech from text for immediate playback or to create a sound + * file. + * <p> + * This is an updated version of the speech synthesis client that supersedes + * {@link android.speech.tts.TextToSpeech}. + * <p> + * A TextToSpeechClient instance can only be used to synthesize text once it has + * connected to the service. The TextToSpeechClient instance will start establishing + * the connection after a call to the {@link #connect()} method. This is usually done in + * {@link Application#onCreate()} or {@link Activity#onCreate}. When the connection + * is established, the instance will call back using the + * {@link TextToSpeechClient.ConnectionCallbacks} interface. Only after a + * successful callback is the client usable. + * <p> + * After successful connection, the list of all available voices can be obtained + * by calling the {@link TextToSpeechClient#getEngineStatus() method. The client can + * choose a voice using some custom heuristic and build a {@link RequestConfig} object + * using {@link RequestConfig.Builder}, or can use one of the common heuristics found + * in ({@link RequestConfigHelper}. + * <p> + * When you are done using the TextToSpeechClient instance, call the + * {@link #disconnect()} method to release the connection. + * <p> + * In the rare case of a change to the set of available voices, the service will call to the + * {@link ConnectionCallbacks#onEngineStatusChange} with new set of available voices as argument. + * In response, the client HAVE to recreate all {@link RequestConfig} instances in use. + */ +public final class TextToSpeechClient { + private static final String TAG = TextToSpeechClient.class.getSimpleName(); + + private final Object mLock = new Object(); + private final TtsEngines mEnginesHelper; + private final Context mContext; + + // Guarded by mLock + private Connection mServiceConnection; + private final RequestCallbacks mDefaultRequestCallbacks; + private final ConnectionCallbacks mConnectionCallbacks; + private EngineStatus mEngineStatus; + private String mRequestedEngine; + private boolean mFallbackToDefault; + private HashMap<String, Pair<UtteranceId, RequestCallbacks>> mCallbacks; + // Guarded by mLock + + /** Common voices parameters */ + public static final class Params { + private Params() {} + + /** + * Maximum allowed time for a single request attempt, in milliseconds, before synthesis + * fails (or fallback request starts, if requested using + * {@link #FALLBACK_VOICE_NAME}). + */ + public static final String NETWORK_TIMEOUT_MS = "networkTimeoutMs"; + + /** + * Number of network request retries that are attempted in case of failure + */ + public static final String NETWORK_RETRIES_COUNT = "networkRetriesCount"; + + /** + * Should synthesizer report sub-utterance progress on synthesis. Only applicable + * for the {@link TextToSpeechClient#queueSpeak} method. + */ + public static final String TRACK_SUBUTTERANCE_PROGRESS = "trackSubutteranceProgress"; + + /** + * If a voice exposes this parameter then it supports the fallback request feature. + * + * If it is set to a valid name of some other voice ({@link VoiceInfo#getName()}) then + * in case of request failure (due to network problems or missing data), fallback request + * will be attempted. Request will be done using the voice referenced by this parameter. + * If it is the case, the client will be informed by a callback to the {@link + * RequestCallbacks#onSynthesisFallback(UtteranceId)}. + */ + public static final String FALLBACK_VOICE_NAME = "fallbackVoiceName"; + + /** + * Audio parameter for specifying a linear multiplier to the speaking speed of the voice. + * The value is a float. Values below zero decrease speed of the synthesized speech + * values above one increase it. If the value of this parameter is equal to zero, + * then it will be replaced by a settings-configurable default before it reaches + * TTS service. + */ + public static final String SPEECH_SPEED = "speechSpeed"; + + /** + * Audio parameter for controlling the pitch of the output. The Value is a positive float, + * with default of {@code 1.0}. The value is used to scale the primary frequency linearly. + * Lower values lower the tone of the synthesized voice, greater values increase it. + */ + public static final String SPEECH_PITCH = "speechPitch"; + + /** + * Audio parameter for controlling output volume. Value is a float with scale of 0 to 1 + */ + public static final String AUDIO_PARAM_VOLUME = TextToSpeech.Engine.KEY_PARAM_VOLUME; + + /** + * Audio parameter for controlling output pan. + * Value is a float ranging from -1 to +1 where -1 maps to a hard-left pan, + * 0 to center (the default behavior), and +1 to hard-right. + */ + public static final String AUDIO_PARAM_PAN = TextToSpeech.Engine.KEY_PARAM_PAN; + + /** + * Audio parameter for specifying the audio stream type to be used when speaking text + * or playing back a file. The value should be one of the STREAM_ constants + * defined in {@link AudioManager}. + */ + public static final String AUDIO_PARAM_STREAM = TextToSpeech.Engine.KEY_PARAM_STREAM; + } + + /** + * Result codes for TTS operations. + */ + public static final class Status { + private Status() {} + + /** + * Denotes a successful operation. + */ + public static final int SUCCESS = 0; + + /** + * Denotes a stop requested by a client. It's used only on the service side of the API, + * client should never expect to see this result code. + */ + public static final int STOPPED = 100; + + /** + * Denotes a generic failure. + */ + public static final int ERROR_UNKNOWN = -1; + + /** + * Denotes a failure of a TTS engine to synthesize the given input. + */ + public static final int ERROR_SYNTHESIS = 10; + + /** + * Denotes a failure of a TTS service. + */ + public static final int ERROR_SERVICE = 11; + + /** + * Denotes a failure related to the output (audio device or a file). + */ + public static final int ERROR_OUTPUT = 12; + + /** + * Denotes a failure caused by a network connectivity problems. + */ + public static final int ERROR_NETWORK = 13; + + /** + * Denotes a failure caused by network timeout. + */ + public static final int ERROR_NETWORK_TIMEOUT = 14; + + /** + * Denotes a failure caused by an invalid request. + */ + public static final int ERROR_INVALID_REQUEST = 15; + + /** + * Denotes a failure related to passing a non-unique utterance id. + */ + public static final int ERROR_NON_UNIQUE_UTTERANCE_ID = 16; + + /** + * Denotes a failure related to missing data. The TTS implementation may download + * the missing data, and if so, request will succeed in future. This error can only happen + * for voices with {@link VoiceInfo#FEATURE_MAY_AUTOINSTALL} feature. + * Note: the recommended way to avoid this error is to create a request with the fallback + * voice. + */ + public static final int ERROR_DOWNLOADING_ADDITIONAL_DATA = 17; + } + + /** + * Set of callbacks for the events related to the progress of a synthesis request + * through the synthesis queue. Each synthesis request is associated with a call to + * {@link #queueSpeak} or {@link #queueSynthesizeToFile}. + * + * The callbacks specified in this method will NOT be called on UI thread. + */ + public static abstract class RequestCallbacks { + /** + * Called after synthesis of utterance successfully starts. + */ + public void onSynthesisStart(UtteranceId utteranceId) {} + + /** + * Called after synthesis successfully finishes. + * @param utteranceId + * Unique identifier of synthesized utterance. + */ + public void onSynthesisSuccess(UtteranceId utteranceId) {} + + /** + * Called after synthesis was stopped in middle of synthesis process. + * @param utteranceId + * Unique identifier of synthesized utterance. + */ + public void onSynthesisStop(UtteranceId utteranceId) {} + + /** + * Called when requested synthesis failed and fallback synthesis is about to be attempted. + * + * Requires voice with available {@link TextToSpeechClient.Params#FALLBACK_VOICE_NAME} + * parameter, and request with this parameter enabled. + * + * This callback will be followed by callback to the {@link #onSynthesisStart}, + * {@link #onSynthesisFailure} or {@link #onSynthesisSuccess} that depends on the + * fallback outcome. + * + * For more fallback feature reference, look at the + * {@link TextToSpeechClient.Params#FALLBACK_VOICE_NAME}. + * + * @param utteranceId + * Unique identifier of synthesized utterance. + */ + public void onSynthesisFallback(UtteranceId utteranceId) {} + + /** + * Called after synthesis of utterance fails. + * + * It may be called instead or after a {@link #onSynthesisStart} callback. + * + * @param utteranceId + * Unique identifier of synthesized utterance. + * @param errorCode + * One of the values from {@link Status}. + */ + public void onSynthesisFailure(UtteranceId utteranceId, int errorCode) {} + + /** + * Called during synthesis to mark synthesis progress. + * + * Requires voice with available + * {@link TextToSpeechClient.Params#TRACK_SUBUTTERANCE_PROGRESS} parameter, and + * request with this parameter enabled. + * + * @param utteranceId + * Unique identifier of synthesized utterance. + * @param charIndex + * String index (java char offset) of recently synthesized character. + * @param msFromStart + * Miliseconds from the start of the synthesis. + */ + public void onSynthesisProgress(UtteranceId utteranceId, int charIndex, + int msFromStart) {} + } + + /** + * Interface definition of callbacks that are called when the client is + * connected or disconnected from the TTS service. + */ + public static interface ConnectionCallbacks { + /** + * After calling {@link TextToSpeechClient#connect()}, this method will be invoked + * asynchronously when the connect request has successfully completed. + * + * Clients are strongly encouraged to call {@link TextToSpeechClient#getEngineStatus()} + * and create {@link RequestConfig} objects used in subsequent synthesis requests. + */ + public void onConnectionSuccess(); + + /** + * After calling {@link TextToSpeechClient#connect()}, this method may be invoked + * asynchronously when the connect request has failed to complete. + * + * It may be also invoked synchronously, from the body of + * {@link TextToSpeechClient#connect()} method. + */ + public void onConnectionFailure(); + + /** + * Called when the connection to the service is lost. This can happen if there is a problem + * with the speech service (e.g. a crash or resource problem causes it to be killed by the + * system). When called, all requests have been canceled and no outstanding listeners will + * be executed. Applications should disable UI components that require the service. + */ + public void onServiceDisconnected(); + + /** + * After receiving {@link #onConnectionSuccess()} callback, this method may be invoked + * if engine status obtained from {@link TextToSpeechClient#getEngineStatus()}) changes. + * It usually means that some voices were removed, changed or added. + * + * Clients are required to recreate {@link RequestConfig} objects used in subsequent + * synthesis requests. + */ + public void onEngineStatusChange(EngineStatus newEngineStatus); + } + + /** State of voices as provided by engine and user. */ + public static final class EngineStatus { + /** All available voices. */ + private final List<VoiceInfo> mVoices; + + /** Name of the TTS engine package */ + private final String mPackageName; + + private EngineStatus(String packageName, List<VoiceInfo> voices) { + this.mVoices = Collections.unmodifiableList(voices); + this.mPackageName = packageName; + } + + /** + * Get an immutable list of all Voices exposed by the TTS engine. + */ + public List<VoiceInfo> getVoices() { + return mVoices; + } + + /** + * Get name of the TTS engine package currently in use. + */ + public String getEnginePackage() { + return mPackageName; + } + } + + /** Unique synthesis request identifier. */ + public static class UtteranceId { + /** Unique identifier */ + private final int id; + + /** Unique identifier generator */ + private static final AtomicInteger ID_GENERATOR = new AtomicInteger(); + + /** + * Create new, unique UtteranceId instance. + */ + public UtteranceId() { + id = ID_GENERATOR.getAndIncrement(); + } + + /** + * Returns a unique string associated with an instance of this object. + * + * This string will be used to identify the synthesis request/utterance inside the + * TTS service. + */ + public final String toUniqueString() { + return "UID" + id; + } + } + + /** + * Create TextToSpeech service client. + * + * Will connect to the default TTS service. In order to be usable, {@link #connect()} need + * to be called first and successful connection callback need to be received. + * + * @param context + * The context this instance is running in. + * @param engine + * Package name of requested TTS engine. If it's null, then default engine will + * be selected regardless of {@code fallbackToDefaultEngine} parameter value. + * @param fallbackToDefaultEngine + * If requested engine is not available, should we fallback to the default engine? + * @param defaultRequestCallbacks + * Default request callbacks, it will be used for all synthesis requests without + * supplied RequestCallbacks instance. Can't be null. + * @param connectionCallbacks + * Callbacks for connecting and disconnecting from the service. Can't be null. + */ + public TextToSpeechClient(Context context, + String engine, boolean fallbackToDefaultEngine, + RequestCallbacks defaultRequestCallbacks, + ConnectionCallbacks connectionCallbacks) { + if (context == null) + throw new IllegalArgumentException("context can't be null"); + if (defaultRequestCallbacks == null) + throw new IllegalArgumentException("defaultRequestCallbacks can't be null"); + if (connectionCallbacks == null) + throw new IllegalArgumentException("connectionCallbacks can't be null"); + mContext = context; + mEnginesHelper = new TtsEngines(mContext); + mCallbacks = new HashMap<String, Pair<UtteranceId, RequestCallbacks>>(); + mDefaultRequestCallbacks = defaultRequestCallbacks; + mConnectionCallbacks = connectionCallbacks; + + mRequestedEngine = engine; + mFallbackToDefault = fallbackToDefaultEngine; + } + + /** + * Create TextToSpeech service client. Will connect to the default TTS + * service. In order to be usable, {@link #connect()} need to be called + * first and successful connection callback need to be received. + * + * @param context Context this instance is running in. + * @param defaultRequestCallbacks Default request callbacks, it + * will be used for all synthesis requests without supplied + * RequestCallbacks instance. Can't be null. + * @param connectionCallbacks Callbacks for connecting and disconnecting + * from the service. Can't be null. + */ + public TextToSpeechClient(Context context, RequestCallbacks defaultRequestCallbacks, + ConnectionCallbacks connectionCallbacks) { + this(context, null, true, defaultRequestCallbacks, connectionCallbacks); + } + + + private boolean initTts(String requestedEngine, boolean fallbackToDefaultEngine) { + // Step 1: Try connecting to the engine that was requested. + if (requestedEngine != null) { + if (mEnginesHelper.isEngineInstalled(requestedEngine)) { + if ((mServiceConnection = connectToEngine(requestedEngine)) != null) { + return true; + } else if (!fallbackToDefaultEngine) { + Log.w(TAG, "Couldn't connect to requested engine: " + requestedEngine); + return false; + } + } else if (!fallbackToDefaultEngine) { + Log.w(TAG, "Requested engine not installed: " + requestedEngine); + return false; + } + } + + // Step 2: Try connecting to the user's default engine. + final String defaultEngine = mEnginesHelper.getDefaultEngine(); + if (defaultEngine != null && !defaultEngine.equals(requestedEngine)) { + if ((mServiceConnection = connectToEngine(defaultEngine)) != null) { + return true; + } + } + + // Step 3: Try connecting to the highest ranked engine in the + // system. + final String highestRanked = mEnginesHelper.getHighestRankedEngineName(); + if (highestRanked != null && !highestRanked.equals(requestedEngine) && + !highestRanked.equals(defaultEngine)) { + if ((mServiceConnection = connectToEngine(highestRanked)) != null) { + return true; + } + } + + Log.w(TAG, "Couldn't find working TTS engine"); + return false; + } + + private Connection connectToEngine(String engine) { + Connection connection = new Connection(engine); + Intent intent = new Intent(TextToSpeech.Engine.INTENT_ACTION_TTS_SERVICE); + intent.setPackage(engine); + boolean bound = mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE); + if (!bound) { + Log.e(TAG, "Failed to bind to " + engine); + return null; + } else { + Log.i(TAG, "Successfully bound to " + engine); + return connection; + } + } + + + /** + * Connects the client to TTS service. This method returns immediately, and connects to the + * service in the background. + * + * After connection initializes successfully, {@link ConnectionCallbacks#onConnectionSuccess()} + * is called. On a failure {@link ConnectionCallbacks#onConnectionFailure} is called. + * + * Both of those callback may be called asynchronously on the main thread, + * {@link ConnectionCallbacks#onConnectionFailure} may be called synchronously, before + * this method returns. + */ + public void connect() { + synchronized (mLock) { + if (mServiceConnection != null) { + return; + } + if(!initTts(mRequestedEngine, mFallbackToDefault)) { + mConnectionCallbacks.onConnectionFailure(); + } + } + } + + /** + * Checks if the client is currently connected to the service, so that + * requests to other methods will succeed. + */ + public boolean isConnected() { + synchronized (mLock) { + return mServiceConnection != null && mServiceConnection.isEstablished(); + } + } + + /** + * Closes the connection to TextToSpeech service. No calls can be made on this object after + * calling this method. + * It is good practice to call this method in the onDestroy() method of an Activity + * so the TextToSpeech engine can be cleanly stopped. + */ + public void disconnect() { + synchronized (mLock) { + if (mServiceConnection != null) { + mServiceConnection.disconnect(); + mServiceConnection = null; + mCallbacks.clear(); + } + } + } + + /** + * Register callback. + * + * @param utteranceId Non-null UtteranceId instance. + * @param callback Non-null callbacks for the request + * @return Status.SUCCESS or error code in case of invalid arguments. + */ + private int addCallback(UtteranceId utteranceId, RequestCallbacks callback) { + synchronized (mLock) { + if (utteranceId == null || callback == null) { + return Status.ERROR_INVALID_REQUEST; + } + if (mCallbacks.put(utteranceId.toUniqueString(), + new Pair<UtteranceId, RequestCallbacks>(utteranceId, callback)) != null) { + return Status.ERROR_NON_UNIQUE_UTTERANCE_ID; + } + return Status.SUCCESS; + } + } + + /** + * Remove and return callback. + * + * @param utteranceIdStr Unique string obtained from {@link UtteranceId#toUniqueString}. + */ + private Pair<UtteranceId, RequestCallbacks> removeCallback(String utteranceIdStr) { + synchronized (mLock) { + return mCallbacks.remove(utteranceIdStr); + } + } + + /** + * Get callback and utterance id. + * + * @param utteranceIdStr Unique string obtained from {@link UtteranceId#toUniqueString}. + */ + private Pair<UtteranceId, RequestCallbacks> getCallback(String utteranceIdStr) { + synchronized (mLock) { + return mCallbacks.get(utteranceIdStr); + } + } + + /** + * Remove callback and call {@link RequestCallbacks#onSynthesisFailure} with passed + * error code. + * + * @param utteranceIdStr Unique string obtained from {@link UtteranceId#toUniqueString}. + * @param errorCode argument to {@link RequestCallbacks#onSynthesisFailure} call. + */ + private void removeCallbackAndErr(String utteranceIdStr, int errorCode) { + synchronized (mLock) { + Pair<UtteranceId, RequestCallbacks> c = mCallbacks.remove(utteranceIdStr); + c.second.onSynthesisFailure(c.first, errorCode); + } + } + + /** + * Retrieve TTS engine status {@link EngineStatus}. Requires connected client. + */ + public EngineStatus getEngineStatus() { + synchronized (mLock) { + return mEngineStatus; + } + } + + /** + * Query TTS engine about available voices and defaults. + * + * @return EngineStatus is connected or null if client is disconnected. + */ + private EngineStatus requestEngineStatus(ITextToSpeechService service) + throws RemoteException { + List<VoiceInfo> voices = service.getVoicesInfo(); + if (voices == null) { + Log.e(TAG, "Requested engine doesn't support TTS V2 API"); + return null; + } + + return new EngineStatus(mServiceConnection.getEngineName(), voices); + } + + private class Connection implements ServiceConnection { + private final String mEngineName; + + private ITextToSpeechService mService; + + private boolean mEstablished; + + private PrepareConnectionAsyncTask mSetupConnectionAsyncTask; + + public Connection(String engineName) { + this.mEngineName = engineName; + } + + private final ITextToSpeechCallback.Stub mCallback = new ITextToSpeechCallback.Stub() { + + @Override + public void onStart(String utteranceIdStr) { + synchronized (mLock) { + Pair<UtteranceId, RequestCallbacks> callbacks = getCallback(utteranceIdStr); + callbacks.second.onSynthesisStart(callbacks.first); + } + } + + public void onStop(String utteranceIdStr) { + synchronized (mLock) { + Pair<UtteranceId, RequestCallbacks> callbacks = removeCallback(utteranceIdStr); + callbacks.second.onSynthesisStop(callbacks.first); + } + } + + @Override + public void onSuccess(String utteranceIdStr) { + synchronized (mLock) { + Pair<UtteranceId, RequestCallbacks> callbacks = removeCallback(utteranceIdStr); + callbacks.second.onSynthesisSuccess(callbacks.first); + } + } + + public void onFallback(String utteranceIdStr) { + synchronized (mLock) { + Pair<UtteranceId, RequestCallbacks> callbacks = getCallback( + utteranceIdStr); + callbacks.second.onSynthesisFallback(callbacks.first); + } + }; + + @Override + public void onError(String utteranceIdStr, int errorCode) { + removeCallbackAndErr(utteranceIdStr, errorCode); + } + + @Override + public void onVoicesInfoChange(List<VoiceInfo> voicesInfo) { + synchronized (mLock) { + mEngineStatus = new EngineStatus(mServiceConnection.getEngineName(), + voicesInfo); + mConnectionCallbacks.onEngineStatusChange(mEngineStatus); + } + } + }; + + private class PrepareConnectionAsyncTask extends AsyncTask<Void, Void, EngineStatus> { + + private final ComponentName mName; + + public PrepareConnectionAsyncTask(ComponentName name) { + mName = name; + } + + @Override + protected EngineStatus doInBackground(Void... params) { + synchronized(mLock) { + if (isCancelled()) { + return null; + } + try { + mService.setCallback(getCallerIdentity(), mCallback); + return requestEngineStatus(mService); + } catch (RemoteException re) { + Log.e(TAG, "Error setting up the TTS service"); + return null; + } + } + } + + @Override + protected void onPostExecute(EngineStatus result) { + synchronized(mLock) { + if (mSetupConnectionAsyncTask == this) { + mSetupConnectionAsyncTask = null; + } + if (result == null) { + Log.e(TAG, "Setup task failed"); + disconnect(); + mConnectionCallbacks.onConnectionFailure(); + return; + } + + mEngineStatus = result; + mEstablished = true; + } + mConnectionCallbacks.onConnectionSuccess(); + } + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + Log.i(TAG, "Connected to " + name); + + synchronized(mLock) { + mEstablished = false; + mService = ITextToSpeechService.Stub.asInterface(service); + startSetupConnectionTask(name); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + Log.i(TAG, "Asked to disconnect from " + name); + + synchronized(mLock) { + stopSetupConnectionTask(); + } + mConnectionCallbacks.onServiceDisconnected(); + } + + private void startSetupConnectionTask(ComponentName name) { + stopSetupConnectionTask(); + mSetupConnectionAsyncTask = new PrepareConnectionAsyncTask(name); + mSetupConnectionAsyncTask.execute(); + } + + private boolean stopSetupConnectionTask() { + boolean result = false; + if (mSetupConnectionAsyncTask != null) { + result = mSetupConnectionAsyncTask.cancel(false); + mSetupConnectionAsyncTask = null; + } + return result; + } + + IBinder getCallerIdentity() { + return mCallback; + } + + boolean isEstablished() { + return mService != null && mEstablished; + } + + boolean runAction(Action action) { + synchronized (mLock) { + try { + action.run(mService); + return true; + } catch (Exception ex) { + Log.e(TAG, action.getName() + " failed", ex); + disconnect(); + return false; + } + } + } + + void disconnect() { + mContext.unbindService(this); + stopSetupConnectionTask(); + mService = null; + mEstablished = false; + if (mServiceConnection == this) { + mServiceConnection = null; + } + } + + String getEngineName() { + return mEngineName; + } + } + + private abstract class Action { + private final String mName; + + public Action(String name) { + mName = name; + } + + public String getName() {return mName;} + abstract void run(ITextToSpeechService service) throws RemoteException; + } + + private IBinder getCallerIdentity() { + if (mServiceConnection != null) { + return mServiceConnection.getCallerIdentity(); + } + return null; + } + + private boolean runAction(Action action) { + synchronized (mLock) { + if (mServiceConnection == null) { + return false; + } + if (!mServiceConnection.isEstablished()) { + return false; + } + mServiceConnection.runAction(action); + return true; + } + } + + private static final String ACTION_STOP_NAME = "stop"; + + /** + * Interrupts the current utterance spoken (whether played or rendered to file) and discards + * other utterances in the queue. + */ + public void stop() { + runAction(new Action(ACTION_STOP_NAME) { + @Override + public void run(ITextToSpeechService service) throws RemoteException { + if (service.stop(getCallerIdentity()) != Status.SUCCESS) { + Log.e(TAG, "Stop failed"); + } + mCallbacks.clear(); + } + }); + } + + private static final String ACTION_QUEUE_SPEAK_NAME = "queueSpeak"; + + /** + * Speaks the string using the specified queuing strategy using current + * voice. This method is asynchronous, i.e. the method just adds the request + * to the queue of TTS requests and then returns. The synthesis might not + * have finished (or even started!) at the time when this method returns. + * + * @param utterance The string of text to be spoken. No longer than + * 1000 characters. + * @param utteranceId Unique identificator used to track the synthesis progress + * in {@link RequestCallbacks}. + * @param config Synthesis request configuration. Can't be null. Has to contain a + * voice. + * @param callbacks Synthesis request callbacks. If null, default request + * callbacks object will be used. + */ + public void queueSpeak(final String utterance, final UtteranceId utteranceId, + final RequestConfig config, + final RequestCallbacks callbacks) { + runAction(new Action(ACTION_QUEUE_SPEAK_NAME) { + @Override + public void run(ITextToSpeechService service) throws RemoteException { + RequestCallbacks c = mDefaultRequestCallbacks; + if (callbacks != null) { + c = callbacks; + } + int addCallbackStatus = addCallback(utteranceId, c); + if (addCallbackStatus != Status.SUCCESS) { + c.onSynthesisFailure(utteranceId, Status.ERROR_INVALID_REQUEST); + return; + } + + int queueResult = service.speakV2( + getCallerIdentity(), + new SynthesisRequestV2(utterance, utteranceId.toUniqueString(), config)); + if (queueResult != Status.SUCCESS) { + removeCallbackAndErr(utteranceId.toUniqueString(), queueResult); + } + } + }); + } + + private static final String ACTION_QUEUE_SYNTHESIZE_TO_FILE = "queueSynthesizeToFile"; + + /** + * Synthesizes the given text to a file using the specified parameters. This + * method is asynchronous, i.e. the method just adds the request to the + * queue of TTS requests and then returns. The synthesis might not have + * finished (or even started!) at the time when this method returns. + * + * @param utterance The text that should be synthesized. No longer than + * 1000 characters. + * @param utteranceId Unique identificator used to track the synthesis progress + * in {@link RequestCallbacks}. + * @param outputFile File to write the generated audio data to. + * @param config Synthesis request configuration. Can't be null. Have to contain a + * voice. + * @param callbacks Synthesis request callbacks. If null, default request + * callbacks object will be used. + */ + public void queueSynthesizeToFile(final String utterance, final UtteranceId utteranceId, + final File outputFile, final RequestConfig config, + final RequestCallbacks callbacks) { + runAction(new Action(ACTION_QUEUE_SYNTHESIZE_TO_FILE) { + @Override + public void run(ITextToSpeechService service) throws RemoteException { + RequestCallbacks c = mDefaultRequestCallbacks; + if (callbacks != null) { + c = callbacks; + } + int addCallbackStatus = addCallback(utteranceId, c); + if (addCallbackStatus != Status.SUCCESS) { + c.onSynthesisFailure(utteranceId, Status.ERROR_INVALID_REQUEST); + return; + } + + ParcelFileDescriptor fileDescriptor = null; + try { + if (outputFile.exists() && !outputFile.canWrite()) { + Log.e(TAG, "No permissions to write to " + outputFile); + removeCallbackAndErr(utteranceId.toUniqueString(), Status.ERROR_OUTPUT); + return; + } + fileDescriptor = ParcelFileDescriptor.open(outputFile, + ParcelFileDescriptor.MODE_WRITE_ONLY | + ParcelFileDescriptor.MODE_CREATE | + ParcelFileDescriptor.MODE_TRUNCATE); + + int queueResult = service.synthesizeToFileDescriptorV2(getCallerIdentity(), + fileDescriptor, + new SynthesisRequestV2(utterance, utteranceId.toUniqueString(), + config)); + fileDescriptor.close(); + if (queueResult != Status.SUCCESS) { + removeCallbackAndErr(utteranceId.toUniqueString(), queueResult); + } + } catch (FileNotFoundException e) { + Log.e(TAG, "Opening file " + outputFile + " failed", e); + removeCallbackAndErr(utteranceId.toUniqueString(), Status.ERROR_OUTPUT); + } catch (IOException e) { + Log.e(TAG, "Closing file " + outputFile + " failed", e); + removeCallbackAndErr(utteranceId.toUniqueString(), Status.ERROR_OUTPUT); + } + } + }); + } + + private static final String ACTION_QUEUE_SILENCE_NAME = "queueSilence"; + + /** + * Plays silence for the specified amount of time. This method is asynchronous, + * i.e. the method just adds the request to the queue of TTS requests and then + * returns. The synthesis might not have finished (or even started!) at the time + * when this method returns. + * + * @param durationInMs The duration of the silence in milliseconds. + * @param utteranceId Unique identificator used to track the synthesis progress + * in {@link RequestCallbacks}. + * @param callbacks Synthesis request callbacks. If null, default request + * callbacks object will be used. + */ + public void queueSilence(final long durationInMs, final UtteranceId utteranceId, + final RequestCallbacks callbacks) { + runAction(new Action(ACTION_QUEUE_SILENCE_NAME) { + @Override + public void run(ITextToSpeechService service) throws RemoteException { + RequestCallbacks c = mDefaultRequestCallbacks; + if (callbacks != null) { + c = callbacks; + } + int addCallbackStatus = addCallback(utteranceId, c); + if (addCallbackStatus != Status.SUCCESS) { + c.onSynthesisFailure(utteranceId, Status.ERROR_INVALID_REQUEST); + } + + int queueResult = service.playSilence(getCallerIdentity(), durationInMs, + TextToSpeech.QUEUE_ADD, utteranceId.toUniqueString()); + + if (queueResult != Status.SUCCESS) { + removeCallbackAndErr(utteranceId.toUniqueString(), queueResult); + } + } + }); + } + + + private static final String ACTION_QUEUE_AUDIO_NAME = "queueAudio"; + + /** + * Plays the audio resource using the specified parameters. + * This method is asynchronous, i.e. the method just adds the request to the queue of TTS + * requests and then returns. The synthesis might not have finished (or even started!) at the + * time when this method returns. + * + * @param audioUrl The audio resource that should be played + * @param utteranceId Unique identificator used to track synthesis progress + * in {@link RequestCallbacks}. + * @param config Synthesis request configuration. Can't be null. Doesn't have to contain a + * voice (only system parameters are used). + * @param callbacks Synthesis request callbacks. If null, default request + * callbacks object will be used. + */ + public void queueAudio(final Uri audioUrl, final UtteranceId utteranceId, + final RequestConfig config, final RequestCallbacks callbacks) { + runAction(new Action(ACTION_QUEUE_AUDIO_NAME) { + @Override + public void run(ITextToSpeechService service) throws RemoteException { + RequestCallbacks c = mDefaultRequestCallbacks; + if (callbacks != null) { + c = callbacks; + } + int addCallbackStatus = addCallback(utteranceId, c); + if (addCallbackStatus != Status.SUCCESS) { + c.onSynthesisFailure(utteranceId, Status.ERROR_INVALID_REQUEST); + } + + int queueResult = service.playAudioV2(getCallerIdentity(), audioUrl, + utteranceId.toUniqueString(), config.getVoiceParams()); + + if (queueResult != Status.SUCCESS) { + removeCallbackAndErr(utteranceId.toUniqueString(), queueResult); + } + } + }); + } +} diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java index 575855c..d7c51fc 100644 --- a/core/java/android/speech/tts/TextToSpeechService.java +++ b/core/java/android/speech/tts/TextToSpeechService.java @@ -34,26 +34,27 @@ import android.speech.tts.TextToSpeech.Engine; import android.text.TextUtils; import android.util.Log; -import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Locale; +import java.util.Map; +import java.util.MissingResourceException; import java.util.Set; /** * Abstract base class for TTS engine implementations. The following methods - * need to be implemented. - * + * need to be implemented for V1 API ({@link TextToSpeech}) implementation. * <ul> - * <li>{@link #onIsLanguageAvailable}</li> - * <li>{@link #onLoadLanguage}</li> - * <li>{@link #onGetLanguage}</li> - * <li>{@link #onSynthesizeText}</li> - * <li>{@link #onStop}</li> + * <li>{@link #onIsLanguageAvailable}</li> + * <li>{@link #onLoadLanguage}</li> + * <li>{@link #onGetLanguage}</li> + * <li>{@link #onSynthesizeText}</li> + * <li>{@link #onStop}</li> * </ul> - * * The first three deal primarily with language management, and are used to * query the engine for it's support for a given language and indicate to it * that requests in a given language are imminent. @@ -61,22 +62,44 @@ import java.util.Set; * {@link #onSynthesizeText} is central to the engine implementation. The * implementation should synthesize text as per the request parameters and * return synthesized data via the supplied callback. This class and its helpers - * will then consume that data, which might mean queueing it for playback or writing - * it to a file or similar. All calls to this method will be on a single - * thread, which will be different from the main thread of the service. Synthesis - * must be synchronous which means the engine must NOT hold on the callback or call - * any methods on it after the method returns + * will then consume that data, which might mean queuing it for playback or writing + * it to a file or similar. All calls to this method will be on a single thread, + * which will be different from the main thread of the service. Synthesis must be + * synchronous which means the engine must NOT hold on to the callback or call any + * methods on it after the method returns. * - * {@link #onStop} tells the engine that it should stop all ongoing synthesis, if - * any. Any pending data from the current synthesis will be discarded. + * {@link #onStop} tells the engine that it should stop + * all ongoing synthesis, if any. Any pending data from the current synthesis + * will be discarded. * + * {@link #onGetLanguage} is not required as of JELLYBEAN_MR2 (API 18) and later, it is only + * called on earlier versions of Android. + * <p> + * In order to fully support the V2 API ({@link TextToSpeechClient}), + * these methods must be implemented: + * <ul> + * <li>{@link #onSynthesizeTextV2}</li> + * <li>{@link #checkVoicesInfo}</li> + * <li>{@link #onVoicesInfoChange}</li> + * <li>{@link #implementsV2API}</li> + * </ul> + * In addition {@link #implementsV2API} has to return true. + * <p> + * If the service does not implement these methods and {@link #implementsV2API} returns false, + * then the V2 API will be provided by converting V2 requests ({@link #onSynthesizeTextV2}) + * to V1 requests ({@link #onSynthesizeText}). On service setup, all of the available device + * locales will be fed to {@link #onIsLanguageAvailable} to check if they are supported. + * If they are, embedded and/or network voices will be created depending on the result of + * {@link #onGetFeaturesForLanguage}. + * <p> + * Note that a V2 service will still receive requests from V1 clients and has to implement all + * of the V1 API methods. */ public abstract class TextToSpeechService extends Service { private static final boolean DBG = false; private static final String TAG = "TextToSpeechService"; - private static final String SYNTH_THREAD_NAME = "SynthThread"; private SynthHandler mSynthHandler; @@ -89,6 +112,11 @@ public abstract class TextToSpeechService extends Service { private CallbackMap mCallbacks; private String mPackageName; + private final Object mVoicesInfoLock = new Object(); + + private List<VoiceInfo> mVoicesInfoList; + private Map<String, VoiceInfo> mVoicesInfoLookup; + @Override public void onCreate() { if (DBG) Log.d(TAG, "onCreate()"); @@ -108,6 +136,7 @@ public abstract class TextToSpeechService extends Service { mPackageName = getApplicationInfo().packageName; String[] defaultLocale = getSettingsLocale(); + // Load default language onLoadLanguage(defaultLocale[0], defaultLocale[1], defaultLocale[2]); } @@ -148,6 +177,9 @@ public abstract class TextToSpeechService extends Service { /** * Returns the language, country and variant currently being used by the TTS engine. * + * This method will be called only on Android 4.2 and before (API <= 17). In later versions + * this method is not called by the Android TTS framework. + * * Can be called on multiple threads. * * @return A 3-element array, containing language (ISO 3-letter code), @@ -191,21 +223,159 @@ public abstract class TextToSpeechService extends Service { protected abstract void onStop(); /** - * Tells the service to synthesize speech from the given text. This method should - * block until the synthesis is finished. - * - * Called on the synthesis thread. + * Tells the service to synthesize speech from the given text. This method + * should block until the synthesis is finished. Used for requests from V1 + * clients ({@link android.speech.tts.TextToSpeech}). Called on the synthesis + * thread. * * @param request The synthesis request. - * @param callback The callback the the engine must use to make data available for - * playback or for writing to a file. + * @param callback The callback that the engine must use to make data + * available for playback or for writing to a file. */ protected abstract void onSynthesizeText(SynthesisRequest request, SynthesisCallback callback); /** + * Check the available voices data and return an immutable list of the available voices. + * The output of this method will be passed to clients to allow them to configure synthesis + * requests. + * + * Can be called on multiple threads. + * + * The result of this method will be saved and served to all TTS clients. If a TTS service wants + * to update the set of available voices, it should call the {@link #forceVoicesInfoCheck()} + * method. + */ + protected List<VoiceInfo> checkVoicesInfo() { + if (implementsV2API()) { + throw new IllegalStateException("For proper V2 API implementation this method has to" + + " be implemented"); + } + + // V2 to V1 interface adapter. This allows using V2 client interface on V1-only services. + Bundle defaultParams = new Bundle(); + defaultParams.putFloat(TextToSpeechClient.Params.SPEECH_PITCH, 1.0f); + defaultParams.putFloat(TextToSpeechClient.Params.SPEECH_SPEED, -1.0f); + + // Enumerate all locales and check if they are available + ArrayList<VoiceInfo> voicesInfo = new ArrayList<VoiceInfo>(); + int id = 0; + for (Locale locale : Locale.getAvailableLocales()) { + int expectedStatus = TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE; + if (locale.getVariant().isEmpty()) { + if (locale.getCountry().isEmpty()) { + expectedStatus = TextToSpeech.LANG_AVAILABLE; + } else { + expectedStatus = TextToSpeech.LANG_COUNTRY_AVAILABLE; + } + } + try { + int localeStatus = onIsLanguageAvailable(locale.getISO3Language(), + locale.getISO3Country(), locale.getVariant()); + if (localeStatus != expectedStatus) { + continue; + } + } catch (MissingResourceException e) { + // Ignore locale without iso 3 codes + continue; + } + + Set<String> features = onGetFeaturesForLanguage(locale.getISO3Language(), + locale.getISO3Country(), locale.getVariant()); + + VoiceInfo.Builder builder = new VoiceInfo.Builder(); + builder.setLatency(VoiceInfo.LATENCY_NORMAL); + builder.setQuality(VoiceInfo.QUALITY_NORMAL); + builder.setLocale(locale); + builder.setParamsWithDefaults(defaultParams); + + if (features == null || features.contains( + TextToSpeech.Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS)) { + builder.setName(locale.toString() + "-embedded"); + builder.setRequiresNetworkConnection(false); + voicesInfo.add(builder.build()); + } + + if (features != null && features.contains( + TextToSpeech.Engine.KEY_FEATURE_NETWORK_SYNTHESIS)) { + builder.setName(locale.toString() + "-network"); + builder.setRequiresNetworkConnection(true); + voicesInfo.add(builder.build()); + } + } + + return voicesInfo; + } + + /** + * Tells the synthesis thread that it should reload voice data. + * There's a high probability that the underlying set of available voice data has changed. + * Called only on the synthesis thread. + */ + protected void onVoicesInfoChange() { + + } + + /** + * Tells the service to synthesize speech from the given text. This method + * should block until the synthesis is finished. Used for requests from V2 + * client {@link android.speech.tts.TextToSpeechClient}. Called on the + * synthesis thread. + * + * @param request The synthesis request. + * @param callback The callback the the engine must use to make data + * available for playback or for writing to a file. + */ + protected void onSynthesizeTextV2(SynthesisRequestV2 request, + VoiceInfo selectedVoice, + SynthesisCallback callback) { + if (implementsV2API()) { + throw new IllegalStateException("For proper V2 API implementation this method has to" + + " be implemented"); + } + + // Convert to V1 params + int speechRate = (int) (request.getVoiceParams().getFloat( + TextToSpeechClient.Params.SPEECH_SPEED, 1.0f) * 100); + int speechPitch = (int) (request.getVoiceParams().getFloat( + TextToSpeechClient.Params.SPEECH_PITCH, 1.0f) * 100); + + // Provide adapter to V1 API + Bundle params = new Bundle(); + params.putString(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, request.getUtteranceId()); + params.putInt(TextToSpeech.Engine.KEY_PARAM_PITCH, speechPitch); + params.putInt(TextToSpeech.Engine.KEY_PARAM_RATE, speechRate); + if (selectedVoice.getRequiresNetworkConnection()) { + params.putString(TextToSpeech.Engine.KEY_FEATURE_NETWORK_SYNTHESIS, "true"); + } else { + params.putString(TextToSpeech.Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS, "true"); + } + + // Build V1 request + SynthesisRequest requestV1 = new SynthesisRequest(request.getText(), params); + Locale locale = selectedVoice.getLocale(); + requestV1.setLanguage(locale.getISO3Language(), locale.getISO3Country(), + locale.getVariant()); + requestV1.setSpeechRate(speechRate); + requestV1.setPitch(speechPitch); + + // Synthesize using V1 interface + onSynthesizeText(requestV1, callback); + } + + /** + * If true, this service implements proper V2 TTS API service. If it's false, + * V2 API will be provided through adapter. + */ + protected boolean implementsV2API() { + return false; + } + + /** * Queries the service for a set of features supported for a given language. * + * Can be called on multiple threads. + * * @param lang ISO-3 language code. * @param country ISO-3 country code. May be empty or null. * @param variant Language variant. May be empty or null. @@ -215,6 +385,69 @@ public abstract class TextToSpeechService extends Service { return null; } + private List<VoiceInfo> getVoicesInfo() { + synchronized (mVoicesInfoLock) { + if (mVoicesInfoList == null) { + // Get voices. Defensive copy to make sure TTS engine won't alter the list. + mVoicesInfoList = new ArrayList<VoiceInfo>(checkVoicesInfo()); + // Build lookup map + mVoicesInfoLookup = new HashMap<String, VoiceInfo>((int) ( + mVoicesInfoList.size()*1.5f)); + for (VoiceInfo voiceInfo : mVoicesInfoList) { + VoiceInfo prev = mVoicesInfoLookup.put(voiceInfo.getName(), voiceInfo); + if (prev != null) { + Log.e(TAG, "Duplicate name (" + voiceInfo.getName() + ") of the voice "); + } + } + } + return mVoicesInfoList; + } + } + + public VoiceInfo getVoicesInfoWithName(String name) { + synchronized (mVoicesInfoLock) { + if (mVoicesInfoLookup != null) { + return mVoicesInfoLookup.get(name); + } + } + return null; + } + + /** + * Force TTS service to reevaluate the set of available languages. Will result in + * a call to {@link #checkVoicesInfo()} on the same thread, {@link #onVoicesInfoChange} + * on the synthesizer thread and callback to + * {@link TextToSpeechClient.ConnectionCallbacks#onEngineStatusChange} of all connected + * TTS clients. + * + * Use this method only if you know that set of available languages changed. + * + * Can be called on multiple threads. + */ + public void forceVoicesInfoCheck() { + synchronized (mVoicesInfoLock) { + List<VoiceInfo> old = mVoicesInfoList; + + mVoicesInfoList = null; // Force recreation of voices info list + getVoicesInfo(); + + if (mVoicesInfoList == null) { + throw new IllegalStateException("This method applies only to services " + + "supporting V2 TTS API. This services doesn't support V2 TTS API."); + } + + if (old != null) { + // Flush all existing items, and inform synthesis thread about the change. + mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_FLUSH, + new VoicesInfoChangeItem()); + // TODO: Handle items that may be added to queue after SynthesizerRestartItem + // but before client reconnection + // Disconnect all of them + mCallbacks.dispatchVoicesInfoChange(mVoicesInfoList); + } + } + } + private int getDefaultSpeechRate() { return getSecureSettingInt(Settings.Secure.TTS_DEFAULT_RATE, Engine.DEFAULT_RATE); } @@ -317,7 +550,8 @@ public abstract class TextToSpeechService extends Service { if (!speechItem.isValid()) { if (utterenceProgress != null) { - utterenceProgress.dispatchOnError(); + utterenceProgress.dispatchOnError( + TextToSpeechClient.Status.ERROR_INVALID_REQUEST); } return TextToSpeech.ERROR; } @@ -342,12 +576,13 @@ public abstract class TextToSpeechService extends Service { // // Note that this string is interned, so the == comparison works. msg.obj = speechItem.getCallerIdentity(); + if (sendMessage(msg)) { return TextToSpeech.SUCCESS; } else { Log.w(TAG, "SynthThread has quit"); if (utterenceProgress != null) { - utterenceProgress.dispatchOnError(); + utterenceProgress.dispatchOnError(TextToSpeechClient.Status.ERROR_SERVICE); } return TextToSpeech.ERROR; } @@ -399,9 +634,11 @@ public abstract class TextToSpeechService extends Service { } interface UtteranceProgressDispatcher { - public void dispatchOnDone(); + public void dispatchOnFallback(); + public void dispatchOnStop(); + public void dispatchOnSuccess(); public void dispatchOnStart(); - public void dispatchOnError(); + public void dispatchOnError(int errorCode); } /** @@ -409,15 +646,13 @@ public abstract class TextToSpeechService extends Service { */ private abstract class SpeechItem { private final Object mCallerIdentity; - protected final Bundle mParams; private final int mCallerUid; private final int mCallerPid; private boolean mStarted = false; private boolean mStopped = false; - public SpeechItem(Object caller, int callerUid, int callerPid, Bundle params) { + public SpeechItem(Object caller, int callerUid, int callerPid) { mCallerIdentity = caller; - mParams = params; mCallerUid = callerUid; mCallerPid = callerPid; } @@ -446,20 +681,18 @@ public abstract class TextToSpeechService extends Service { * Must not be called more than once. * * Only called on the synthesis thread. - * - * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}. */ - public int play() { + public void play() { synchronized (this) { if (mStarted) { throw new IllegalStateException("play() called twice"); } mStarted = true; } - return playImpl(); + playImpl(); } - protected abstract int playImpl(); + protected abstract void playImpl(); /** * Stops the speech item. @@ -485,20 +718,37 @@ public abstract class TextToSpeechService extends Service { } /** - * An item in the synth thread queue that process utterance. + * An item in the synth thread queue that process utterance (and call back to client about + * progress). */ private abstract class UtteranceSpeechItem extends SpeechItem implements UtteranceProgressDispatcher { - public UtteranceSpeechItem(Object caller, int callerUid, int callerPid, Bundle params) { - super(caller, callerUid, callerPid, params); + public UtteranceSpeechItem(Object caller, int callerUid, int callerPid) { + super(caller, callerUid, callerPid); + } + + @Override + public void dispatchOnSuccess() { + final String utteranceId = getUtteranceId(); + if (utteranceId != null) { + mCallbacks.dispatchOnSuccess(getCallerIdentity(), utteranceId); + } } @Override - public void dispatchOnDone() { + public void dispatchOnStop() { final String utteranceId = getUtteranceId(); if (utteranceId != null) { - mCallbacks.dispatchOnDone(getCallerIdentity(), utteranceId); + mCallbacks.dispatchOnStop(getCallerIdentity(), utteranceId); + } + } + + @Override + public void dispatchOnFallback() { + final String utteranceId = getUtteranceId(); + if (utteranceId != null) { + mCallbacks.dispatchOnFallback(getCallerIdentity(), utteranceId); } } @@ -511,44 +761,260 @@ public abstract class TextToSpeechService extends Service { } @Override - public void dispatchOnError() { + public void dispatchOnError(int errorCode) { final String utteranceId = getUtteranceId(); if (utteranceId != null) { - mCallbacks.dispatchOnError(getCallerIdentity(), utteranceId); + mCallbacks.dispatchOnError(getCallerIdentity(), utteranceId, errorCode); } } - public int getStreamType() { - return getIntParam(Engine.KEY_PARAM_STREAM, Engine.DEFAULT_STREAM); + abstract public String getUtteranceId(); + + String getStringParam(Bundle params, String key, String defaultValue) { + return params == null ? defaultValue : params.getString(key, defaultValue); + } + + int getIntParam(Bundle params, String key, int defaultValue) { + return params == null ? defaultValue : params.getInt(key, defaultValue); + } + + float getFloatParam(Bundle params, String key, float defaultValue) { + return params == null ? defaultValue : params.getFloat(key, defaultValue); + } + } + + /** + * UtteranceSpeechItem for V1 API speech items. V1 API speech items keep + * synthesis parameters in a single Bundle passed as parameter. This class + * allow subclasses to access them conveniently. + */ + private abstract class SpeechItemV1 extends UtteranceSpeechItem { + protected final Bundle mParams; + + SpeechItemV1(Object callerIdentity, int callerUid, int callerPid, + Bundle params) { + super(callerIdentity, callerUid, callerPid); + mParams = params; + } + + boolean hasLanguage() { + return !TextUtils.isEmpty(getStringParam(mParams, Engine.KEY_PARAM_LANGUAGE, null)); } - public float getVolume() { - return getFloatParam(Engine.KEY_PARAM_VOLUME, Engine.DEFAULT_VOLUME); + int getSpeechRate() { + return getIntParam(mParams, Engine.KEY_PARAM_RATE, getDefaultSpeechRate()); } - public float getPan() { - return getFloatParam(Engine.KEY_PARAM_PAN, Engine.DEFAULT_PAN); + int getPitch() { + return getIntParam(mParams, Engine.KEY_PARAM_PITCH, Engine.DEFAULT_PITCH); } + @Override public String getUtteranceId() { - return getStringParam(Engine.KEY_PARAM_UTTERANCE_ID, null); + return getStringParam(mParams, Engine.KEY_PARAM_UTTERANCE_ID, null); + } + + int getStreamType() { + return getIntParam(mParams, Engine.KEY_PARAM_STREAM, Engine.DEFAULT_STREAM); + } + + float getVolume() { + return getFloatParam(mParams, Engine.KEY_PARAM_VOLUME, Engine.DEFAULT_VOLUME); + } + + float getPan() { + return getFloatParam(mParams, Engine.KEY_PARAM_PAN, Engine.DEFAULT_PAN); + } + } + + class SynthesisSpeechItemV2 extends UtteranceSpeechItem { + private final SynthesisRequestV2 mSynthesisRequest; + private AbstractSynthesisCallback mSynthesisCallback; + private final EventLoggerV2 mEventLogger; + + public SynthesisSpeechItemV2(Object callerIdentity, int callerUid, int callerPid, + SynthesisRequestV2 synthesisRequest) { + super(callerIdentity, callerUid, callerPid); + + mSynthesisRequest = synthesisRequest; + mEventLogger = new EventLoggerV2(synthesisRequest, callerUid, callerPid, + mPackageName); + + updateSpeechSpeedParam(synthesisRequest); + } + + private void updateSpeechSpeedParam(SynthesisRequestV2 synthesisRequest) { + Bundle voiceParams = mSynthesisRequest.getVoiceParams(); + + // Inject default speech speed if needed + if (voiceParams.containsKey(TextToSpeechClient.Params.SPEECH_SPEED)) { + if (voiceParams.getFloat(TextToSpeechClient.Params.SPEECH_SPEED) <= 0) { + voiceParams.putFloat(TextToSpeechClient.Params.SPEECH_SPEED, + getDefaultSpeechRate() / 100.0f); + } + } } - protected String getStringParam(String key, String defaultValue) { - return mParams == null ? defaultValue : mParams.getString(key, defaultValue); + @Override + public boolean isValid() { + if (mSynthesisRequest.getText() == null) { + Log.e(TAG, "null synthesis text"); + return false; + } + if (mSynthesisRequest.getText().length() >= TextToSpeech.getMaxSpeechInputLength()) { + Log.w(TAG, "Text too long: " + mSynthesisRequest.getText().length() + " chars"); + return false; + } + + return true; } - protected int getIntParam(String key, int defaultValue) { - return mParams == null ? defaultValue : mParams.getInt(key, defaultValue); + @Override + protected void playImpl() { + AbstractSynthesisCallback synthesisCallback; + if (mEventLogger != null) { + mEventLogger.onRequestProcessingStart(); + } + synchronized (this) { + // stop() might have been called before we enter this + // synchronized block. + if (isStopped()) { + return; + } + mSynthesisCallback = createSynthesisCallback(); + synthesisCallback = mSynthesisCallback; + } + + // Get voice info + VoiceInfo voiceInfo = getVoicesInfoWithName(mSynthesisRequest.getVoiceName()); + if (voiceInfo != null) { + // Primary voice + TextToSpeechService.this.onSynthesizeTextV2(mSynthesisRequest, voiceInfo, + synthesisCallback); + } else { + Log.e(TAG, "Unknown voice name:" + mSynthesisRequest.getVoiceName()); + synthesisCallback.error(TextToSpeechClient.Status.ERROR_INVALID_REQUEST); + } + + // Fix for case where client called .start() & .error(), but did not called .done() + if (!synthesisCallback.hasFinished()) { + synthesisCallback.done(); + } } - protected float getFloatParam(String key, float defaultValue) { - return mParams == null ? defaultValue : mParams.getFloat(key, defaultValue); + @Override + protected void stopImpl() { + AbstractSynthesisCallback synthesisCallback; + synchronized (this) { + synthesisCallback = mSynthesisCallback; + } + if (synthesisCallback != null) { + // If the synthesis callback is null, it implies that we haven't + // entered the synchronized(this) block in playImpl which in + // turn implies that synthesis would not have started. + synthesisCallback.stop(); + TextToSpeechService.this.onStop(); + } } + protected AbstractSynthesisCallback createSynthesisCallback() { + return new PlaybackSynthesisCallback(getStreamType(), getVolume(), getPan(), + mAudioPlaybackHandler, this, getCallerIdentity(), mEventLogger, + implementsV2API()); + } + + private int getStreamType() { + return getIntParam(mSynthesisRequest.getAudioParams(), + TextToSpeechClient.Params.AUDIO_PARAM_STREAM, + Engine.DEFAULT_STREAM); + } + + private float getVolume() { + return getFloatParam(mSynthesisRequest.getAudioParams(), + TextToSpeechClient.Params.AUDIO_PARAM_VOLUME, + Engine.DEFAULT_VOLUME); + } + + private float getPan() { + return getFloatParam(mSynthesisRequest.getAudioParams(), + TextToSpeechClient.Params.AUDIO_PARAM_PAN, + Engine.DEFAULT_PAN); + } + + @Override + public String getUtteranceId() { + return mSynthesisRequest.getUtteranceId(); + } + } + + private class SynthesisToFileOutputStreamSpeechItemV2 extends SynthesisSpeechItemV2 { + private final FileOutputStream mFileOutputStream; + + public SynthesisToFileOutputStreamSpeechItemV2(Object callerIdentity, int callerUid, + int callerPid, + SynthesisRequestV2 synthesisRequest, + FileOutputStream fileOutputStream) { + super(callerIdentity, callerUid, callerPid, synthesisRequest); + mFileOutputStream = fileOutputStream; + } + + @Override + protected AbstractSynthesisCallback createSynthesisCallback() { + return new FileSynthesisCallback(mFileOutputStream.getChannel(), + this, getCallerIdentity(), implementsV2API()); + } + + @Override + protected void playImpl() { + super.playImpl(); + try { + mFileOutputStream.close(); + } catch(IOException e) { + Log.w(TAG, "Failed to close output file", e); + } + } + } + + private class AudioSpeechItemV2 extends UtteranceSpeechItem { + private final AudioPlaybackQueueItem mItem; + private final Bundle mAudioParams; + private final String mUtteranceId; + + public AudioSpeechItemV2(Object callerIdentity, int callerUid, int callerPid, + String utteranceId, Bundle audioParams, Uri uri) { + super(callerIdentity, callerUid, callerPid); + mUtteranceId = utteranceId; + mAudioParams = audioParams; + mItem = new AudioPlaybackQueueItem(this, getCallerIdentity(), + TextToSpeechService.this, uri, getStreamType()); + } + + @Override + public boolean isValid() { + return true; + } + + @Override + protected void playImpl() { + mAudioPlaybackHandler.enqueue(mItem); + } + + @Override + protected void stopImpl() { + // Do nothing. + } + + protected int getStreamType() { + return mAudioParams.getInt(TextToSpeechClient.Params.AUDIO_PARAM_STREAM); + } + + public String getUtteranceId() { + return mUtteranceId; + } } - class SynthesisSpeechItem extends UtteranceSpeechItem { + + class SynthesisSpeechItemV1 extends SpeechItemV1 { // Never null. private final String mText; private final SynthesisRequest mSynthesisRequest; @@ -556,10 +1022,10 @@ public abstract class TextToSpeechService extends Service { // Non null after synthesis has started, and all accesses // guarded by 'this'. private AbstractSynthesisCallback mSynthesisCallback; - private final EventLogger mEventLogger; + private final EventLoggerV1 mEventLogger; private final int mCallerUid; - public SynthesisSpeechItem(Object callerIdentity, int callerUid, int callerPid, + public SynthesisSpeechItemV1(Object callerIdentity, int callerUid, int callerPid, Bundle params, String text) { super(callerIdentity, callerUid, callerPid, params); mText = text; @@ -567,7 +1033,7 @@ public abstract class TextToSpeechService extends Service { mSynthesisRequest = new SynthesisRequest(mText, mParams); mDefaultLocale = getSettingsLocale(); setRequestParams(mSynthesisRequest); - mEventLogger = new EventLogger(mSynthesisRequest, callerUid, callerPid, + mEventLogger = new EventLoggerV1(mSynthesisRequest, callerUid, callerPid, mPackageName); } @@ -589,25 +1055,30 @@ public abstract class TextToSpeechService extends Service { } @Override - protected int playImpl() { + protected void playImpl() { AbstractSynthesisCallback synthesisCallback; mEventLogger.onRequestProcessingStart(); synchronized (this) { // stop() might have been called before we enter this // synchronized block. if (isStopped()) { - return TextToSpeech.ERROR; + return; } mSynthesisCallback = createSynthesisCallback(); synthesisCallback = mSynthesisCallback; } + TextToSpeechService.this.onSynthesizeText(mSynthesisRequest, synthesisCallback); - return synthesisCallback.isDone() ? TextToSpeech.SUCCESS : TextToSpeech.ERROR; + + // Fix for case where client called .start() & .error(), but did not called .done() + if (synthesisCallback.hasStarted() && !synthesisCallback.hasFinished()) { + synthesisCallback.done(); + } } protected AbstractSynthesisCallback createSynthesisCallback() { return new PlaybackSynthesisCallback(getStreamType(), getVolume(), getPan(), - mAudioPlaybackHandler, this, getCallerIdentity(), mEventLogger); + mAudioPlaybackHandler, this, getCallerIdentity(), mEventLogger, false); } private void setRequestParams(SynthesisRequest request) { @@ -632,37 +1103,25 @@ public abstract class TextToSpeechService extends Service { } } - public String getLanguage() { - return getStringParam(Engine.KEY_PARAM_LANGUAGE, mDefaultLocale[0]); - } - - private boolean hasLanguage() { - return !TextUtils.isEmpty(getStringParam(Engine.KEY_PARAM_LANGUAGE, null)); - } - private String getCountry() { if (!hasLanguage()) return mDefaultLocale[1]; - return getStringParam(Engine.KEY_PARAM_COUNTRY, ""); + return getStringParam(mParams, Engine.KEY_PARAM_COUNTRY, ""); } private String getVariant() { if (!hasLanguage()) return mDefaultLocale[2]; - return getStringParam(Engine.KEY_PARAM_VARIANT, ""); + return getStringParam(mParams, Engine.KEY_PARAM_VARIANT, ""); } - private int getSpeechRate() { - return getIntParam(Engine.KEY_PARAM_RATE, getDefaultSpeechRate()); - } - - private int getPitch() { - return getIntParam(Engine.KEY_PARAM_PITCH, Engine.DEFAULT_PITCH); + public String getLanguage() { + return getStringParam(mParams, Engine.KEY_PARAM_LANGUAGE, mDefaultLocale[0]); } } - private class SynthesisToFileOutputStreamSpeechItem extends SynthesisSpeechItem { + private class SynthesisToFileOutputStreamSpeechItemV1 extends SynthesisSpeechItemV1 { private final FileOutputStream mFileOutputStream; - public SynthesisToFileOutputStreamSpeechItem(Object callerIdentity, int callerUid, + public SynthesisToFileOutputStreamSpeechItemV1(Object callerIdentity, int callerUid, int callerPid, Bundle params, String text, FileOutputStream fileOutputStream) { super(callerIdentity, callerUid, callerPid, params, text); mFileOutputStream = fileOutputStream; @@ -670,30 +1129,26 @@ public abstract class TextToSpeechService extends Service { @Override protected AbstractSynthesisCallback createSynthesisCallback() { - return new FileSynthesisCallback(mFileOutputStream.getChannel()); + return new FileSynthesisCallback(mFileOutputStream.getChannel(), + this, getCallerIdentity(), false); } @Override - protected int playImpl() { + protected void playImpl() { dispatchOnStart(); - int status = super.playImpl(); - if (status == TextToSpeech.SUCCESS) { - dispatchOnDone(); - } else { - dispatchOnError(); - } + super.playImpl(); try { mFileOutputStream.close(); } catch(IOException e) { Log.w(TAG, "Failed to close output file", e); } - return status; } } - private class AudioSpeechItem extends UtteranceSpeechItem { + private class AudioSpeechItemV1 extends SpeechItemV1 { private final AudioPlaybackQueueItem mItem; - public AudioSpeechItem(Object callerIdentity, int callerUid, int callerPid, + + public AudioSpeechItemV1(Object callerIdentity, int callerUid, int callerPid, Bundle params, Uri uri) { super(callerIdentity, callerUid, callerPid, params); mItem = new AudioPlaybackQueueItem(this, getCallerIdentity(), @@ -706,23 +1161,29 @@ public abstract class TextToSpeechService extends Service { } @Override - protected int playImpl() { + protected void playImpl() { mAudioPlaybackHandler.enqueue(mItem); - return TextToSpeech.SUCCESS; } @Override protected void stopImpl() { // Do nothing. } + + @Override + public String getUtteranceId() { + return getStringParam(mParams, Engine.KEY_PARAM_UTTERANCE_ID, null); + } } private class SilenceSpeechItem extends UtteranceSpeechItem { private final long mDuration; + private final String mUtteranceId; public SilenceSpeechItem(Object callerIdentity, int callerUid, int callerPid, - Bundle params, long duration) { - super(callerIdentity, callerUid, callerPid, params); + String utteranceId, long duration) { + super(callerIdentity, callerUid, callerPid); + mUtteranceId = utteranceId; mDuration = duration; } @@ -732,26 +1193,57 @@ public abstract class TextToSpeechService extends Service { } @Override - protected int playImpl() { + protected void playImpl() { mAudioPlaybackHandler.enqueue(new SilencePlaybackQueueItem( this, getCallerIdentity(), mDuration)); - return TextToSpeech.SUCCESS; } @Override protected void stopImpl() { - // Do nothing, handled by AudioPlaybackHandler#stopForApp + + } + + @Override + public String getUtteranceId() { + return mUtteranceId; + } + } + + /** + * Call {@link TextToSpeechService#onVoicesInfoChange} on synthesis thread. + */ + private class VoicesInfoChangeItem extends SpeechItem { + public VoicesInfoChangeItem() { + super(null, 0, 0); // It's never initiated by an user + } + + @Override + public boolean isValid() { + return true; + } + + @Override + protected void playImpl() { + TextToSpeechService.this.onVoicesInfoChange(); + } + + @Override + protected void stopImpl() { + // No-op } } + /** + * Call {@link TextToSpeechService#onLoadLanguage} on synth thread. + */ private class LoadLanguageItem extends SpeechItem { private final String mLanguage; private final String mCountry; private final String mVariant; public LoadLanguageItem(Object callerIdentity, int callerUid, int callerPid, - Bundle params, String language, String country, String variant) { - super(callerIdentity, callerUid, callerPid, params); + String language, String country, String variant) { + super(callerIdentity, callerUid, callerPid); mLanguage = language; mCountry = country; mVariant = variant; @@ -763,14 +1255,8 @@ public abstract class TextToSpeechService extends Service { } @Override - protected int playImpl() { - int result = TextToSpeechService.this.onLoadLanguage(mLanguage, mCountry, mVariant); - if (result == TextToSpeech.LANG_AVAILABLE || - result == TextToSpeech.LANG_COUNTRY_AVAILABLE || - result == TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE) { - return TextToSpeech.SUCCESS; - } - return TextToSpeech.ERROR; + protected void playImpl() { + TextToSpeechService.this.onLoadLanguage(mLanguage, mCountry, mVariant); } @Override @@ -800,7 +1286,7 @@ public abstract class TextToSpeechService extends Service { return TextToSpeech.ERROR; } - SpeechItem item = new SynthesisSpeechItem(caller, + SpeechItem item = new SynthesisSpeechItemV1(caller, Binder.getCallingUid(), Binder.getCallingPid(), params, text); return mSynthHandler.enqueueSpeechItem(queueMode, item); } @@ -818,7 +1304,7 @@ public abstract class TextToSpeechService extends Service { final ParcelFileDescriptor sameFileDescriptor = ParcelFileDescriptor.adoptFd( fileDescriptor.detachFd()); - SpeechItem item = new SynthesisToFileOutputStreamSpeechItem(caller, + SpeechItem item = new SynthesisToFileOutputStreamSpeechItemV1(caller, Binder.getCallingUid(), Binder.getCallingPid(), params, text, new ParcelFileDescriptor.AutoCloseOutputStream(sameFileDescriptor)); return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item); @@ -830,19 +1316,19 @@ public abstract class TextToSpeechService extends Service { return TextToSpeech.ERROR; } - SpeechItem item = new AudioSpeechItem(caller, + SpeechItem item = new AudioSpeechItemV1(caller, Binder.getCallingUid(), Binder.getCallingPid(), params, audioUri); return mSynthHandler.enqueueSpeechItem(queueMode, item); } @Override - public int playSilence(IBinder caller, long duration, int queueMode, Bundle params) { - if (!checkNonNull(caller, params)) { + public int playSilence(IBinder caller, long duration, int queueMode, String utteranceId) { + if (!checkNonNull(caller)) { return TextToSpeech.ERROR; } SpeechItem item = new SilenceSpeechItem(caller, - Binder.getCallingUid(), Binder.getCallingPid(), params, duration); + Binder.getCallingUid(), Binder.getCallingPid(), utteranceId, duration); return mSynthHandler.enqueueSpeechItem(queueMode, item); } @@ -912,7 +1398,7 @@ public abstract class TextToSpeechService extends Service { retVal == TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE) { SpeechItem item = new LoadLanguageItem(caller, Binder.getCallingUid(), - Binder.getCallingPid(), null, lang, country, variant); + Binder.getCallingPid(), lang, country, variant); if (mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item) != TextToSpeech.SUCCESS) { @@ -943,6 +1429,58 @@ public abstract class TextToSpeechService extends Service { } return true; } + + @Override + public List<VoiceInfo> getVoicesInfo() { + return TextToSpeechService.this.getVoicesInfo(); + } + + @Override + public int speakV2(IBinder callingInstance, + SynthesisRequestV2 request) { + if (!checkNonNull(callingInstance, request)) { + return TextToSpeech.ERROR; + } + + SpeechItem item = new SynthesisSpeechItemV2(callingInstance, + Binder.getCallingUid(), Binder.getCallingPid(), request); + return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item); + } + + @Override + public int synthesizeToFileDescriptorV2(IBinder callingInstance, + ParcelFileDescriptor fileDescriptor, + SynthesisRequestV2 request) { + if (!checkNonNull(callingInstance, request, fileDescriptor)) { + return TextToSpeech.ERROR; + } + + // In test env, ParcelFileDescriptor instance may be EXACTLY the same + // one that is used by client. And it will be closed by a client, thus + // preventing us from writing anything to it. + final ParcelFileDescriptor sameFileDescriptor = ParcelFileDescriptor.adoptFd( + fileDescriptor.detachFd()); + + SpeechItem item = new SynthesisToFileOutputStreamSpeechItemV2(callingInstance, + Binder.getCallingUid(), Binder.getCallingPid(), request, + new ParcelFileDescriptor.AutoCloseOutputStream(sameFileDescriptor)); + return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item); + + } + + @Override + public int playAudioV2( + IBinder callingInstance, Uri audioUri, String utteranceId, + Bundle systemParameters) { + if (!checkNonNull(callingInstance, audioUri, systemParameters)) { + return TextToSpeech.ERROR; + } + + SpeechItem item = new AudioSpeechItemV2(callingInstance, + Binder.getCallingUid(), Binder.getCallingPid(), utteranceId, systemParameters, + audioUri); + return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item); + } }; private class CallbackMap extends RemoteCallbackList<ITextToSpeechCallback> { @@ -964,11 +1502,31 @@ public abstract class TextToSpeechService extends Service { } } - public void dispatchOnDone(Object callerIdentity, String utteranceId) { + public void dispatchOnFallback(Object callerIdentity, String utteranceId) { + ITextToSpeechCallback cb = getCallbackFor(callerIdentity); + if (cb == null) return; + try { + cb.onFallback(utteranceId); + } catch (RemoteException e) { + Log.e(TAG, "Callback onFallback failed: " + e); + } + } + + public void dispatchOnStop(Object callerIdentity, String utteranceId) { + ITextToSpeechCallback cb = getCallbackFor(callerIdentity); + if (cb == null) return; + try { + cb.onStop(utteranceId); + } catch (RemoteException e) { + Log.e(TAG, "Callback onStop failed: " + e); + } + } + + public void dispatchOnSuccess(Object callerIdentity, String utteranceId) { ITextToSpeechCallback cb = getCallbackFor(callerIdentity); if (cb == null) return; try { - cb.onDone(utteranceId); + cb.onSuccess(utteranceId); } catch (RemoteException e) { Log.e(TAG, "Callback onDone failed: " + e); } @@ -985,11 +1543,12 @@ public abstract class TextToSpeechService extends Service { } - public void dispatchOnError(Object callerIdentity, String utteranceId) { + public void dispatchOnError(Object callerIdentity, String utteranceId, + int errorCode) { ITextToSpeechCallback cb = getCallbackFor(callerIdentity); if (cb == null) return; try { - cb.onError(utteranceId); + cb.onError(utteranceId, errorCode); } catch (RemoteException e) { Log.e(TAG, "Callback onError failed: " + e); } @@ -1001,7 +1560,7 @@ public abstract class TextToSpeechService extends Service { synchronized (mCallerToCallback) { mCallerToCallback.remove(caller); } - mSynthHandler.stopForApp(caller); + //mSynthHandler.stopForApp(caller); } @Override @@ -1012,6 +1571,18 @@ public abstract class TextToSpeechService extends Service { } } + public void dispatchVoicesInfoChange(List<VoiceInfo> voicesInfo) { + synchronized (mCallerToCallback) { + for (ITextToSpeechCallback callback : mCallerToCallback.values()) { + try { + callback.onVoicesInfoChange(voicesInfo); + } catch (RemoteException e) { + Log.e(TAG, "Failed to request reconnect", e); + } + } + } + } + private ITextToSpeechCallback getCallbackFor(Object caller) { ITextToSpeechCallback cb; IBinder asBinder = (IBinder) caller; @@ -1021,7 +1592,5 @@ public abstract class TextToSpeechService extends Service { return cb; } - } - } diff --git a/core/java/android/speech/tts/VoiceInfo.aidl b/core/java/android/speech/tts/VoiceInfo.aidl new file mode 100644 index 0000000..4005f8b --- /dev/null +++ b/core/java/android/speech/tts/VoiceInfo.aidl @@ -0,0 +1,20 @@ +/* +** +** Copyright 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.speech.tts; + +parcelable VoiceInfo;
\ No newline at end of file diff --git a/core/java/android/speech/tts/VoiceInfo.java b/core/java/android/speech/tts/VoiceInfo.java new file mode 100644 index 0000000..16b9a97 --- /dev/null +++ b/core/java/android/speech/tts/VoiceInfo.java @@ -0,0 +1,325 @@ +package android.speech.tts; + +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Locale; + +/** + * Characteristics and features of a Text-To-Speech Voice. Each TTS Engine can expose + * multiple voices for multiple locales, with different set of features. + * + * Each VoiceInfo has an unique name. This name can be obtained using the {@link #getName()} method + * and will persist until the client is asked to re-evaluate the list of available voices in the + * {@link TextToSpeechClient.ConnectionCallbacks#onEngineStatusChange(android.speech.tts.TextToSpeechClient.EngineStatus)} + * callback. The name can be used to reference a VoiceInfo in an instance of {@link RequestConfig}; + * the {@link TextToSpeechClient.Params#FALLBACK_VOICE_NAME} voice parameter is an example of this. + * It is recommended that the voice name never change during the TTS service lifetime. + */ +public final class VoiceInfo implements Parcelable { + /** Very low, but still intelligible quality of speech synthesis */ + public static final int QUALITY_VERY_LOW = 100; + + /** Low, not human-like quality of speech synthesis */ + public static final int QUALITY_LOW = 200; + + /** Normal quality of speech synthesis */ + public static final int QUALITY_NORMAL = 300; + + /** High, human-like quality of speech synthesis */ + public static final int QUALITY_HIGH = 400; + + /** Very high, almost human-indistinguishable quality of speech synthesis */ + public static final int QUALITY_VERY_HIGH = 500; + + /** Very low expected synthesizer latency (< 20ms) */ + public static final int LATENCY_VERY_LOW = 100; + + /** Low expected synthesizer latency (~20ms) */ + public static final int LATENCY_LOW = 200; + + /** Normal expected synthesizer latency (~50ms) */ + public static final int LATENCY_NORMAL = 300; + + /** Network based expected synthesizer latency (~200ms) */ + public static final int LATENCY_HIGH = 400; + + /** Very slow network based expected synthesizer latency (> 200ms) */ + public static final int LATENCY_VERY_HIGH = 500; + + /** Additional feature key, with string value, gender of the speaker */ + public static final String FEATURE_SPEAKER_GENDER = "speakerGender"; + + /** Additional feature key, with integer value, speaking speed in words per minute + * when {@link TextToSpeechClient.Params#SPEECH_SPEED} parameter is set to {@code 1.0} */ + public static final String FEATURE_WORDS_PER_MINUTE = "wordsPerMinute"; + + /** + * Additional feature key, with boolean value, that indicates that voice may need to + * download additional data if used for synthesis. + * + * Making a request with a voice that has this feature may result in a + * {@link TextToSpeechClient.Status#ERROR_DOWNLOADING_ADDITIONAL_DATA} error. It's recommended + * to set the {@link TextToSpeechClient.Params#FALLBACK_VOICE_NAME} voice parameter to reference + * a fully installed voice (or network voice) that can serve as replacement. + * + * Note: It's a good practice for a TTS engine to provide a sensible fallback voice as the + * default value for {@link TextToSpeechClient.Params#FALLBACK_VOICE_NAME} parameter if this + * feature is present. + */ + public static final String FEATURE_MAY_AUTOINSTALL = "mayAutoInstall"; + + private final String mName; + private final Locale mLocale; + private final int mQuality; + private final int mLatency; + private final boolean mRequiresNetworkConnection; + private final Bundle mParams; + private final Bundle mAdditionalFeatures; + + private VoiceInfo(Parcel in) { + this.mName = in.readString(); + String[] localesData = new String[3]; + in.readStringArray(localesData); + this.mLocale = new Locale(localesData[0], localesData[1], localesData[2]); + + this.mQuality = in.readInt(); + this.mLatency = in.readInt(); + this.mRequiresNetworkConnection = (in.readByte() == 1); + + this.mParams = in.readBundle(); + this.mAdditionalFeatures = in.readBundle(); + } + + private VoiceInfo(String name, + Locale locale, + int quality, + int latency, + boolean requiresNetworkConnection, + Bundle params, + Bundle additionalFeatures) { + this.mName = name; + this.mLocale = locale; + this.mQuality = quality; + this.mLatency = latency; + this.mRequiresNetworkConnection = requiresNetworkConnection; + this.mParams = params; + this.mAdditionalFeatures = additionalFeatures; + } + + /** Builder, allows TTS engines to create VoiceInfo instances. */ + public static final class Builder { + private String name; + private Locale locale; + private int quality = VoiceInfo.QUALITY_NORMAL; + private int latency = VoiceInfo.LATENCY_NORMAL; + private boolean requiresNetworkConnection; + private Bundle params; + private Bundle additionalFeatures; + + public Builder() { + + } + + /** + * Copy fields from given VoiceInfo instance. + */ + public Builder(VoiceInfo voiceInfo) { + this.name = voiceInfo.mName; + this.locale = voiceInfo.mLocale; + this.quality = voiceInfo.mQuality; + this.latency = voiceInfo.mLatency; + this.requiresNetworkConnection = voiceInfo.mRequiresNetworkConnection; + this.params = (Bundle)voiceInfo.mParams.clone(); + this.additionalFeatures = (Bundle) voiceInfo.mAdditionalFeatures.clone(); + } + + /** + * Sets the voice's unique name. It will be used by clients to reference the voice used by a + * request. + * + * It's recommended that each voice use the same consistent name during the TTS service + * lifetime. + */ + public Builder setName(String name) { + this.name = name; + return this; + } + + /** + * Sets voice locale. This has to be a valid locale, built from ISO 639-1 and ISO 3166-1 + * two letter codes. + */ + public Builder setLocale(Locale locale) { + this.locale = locale; + return this; + } + + /** + * Sets map of all available request parameters with their default values. + * Some common parameter names can be found in {@link TextToSpeechClient.Params} static + * members. + */ + public Builder setParamsWithDefaults(Bundle params) { + this.params = params; + return this; + } + + /** + * Sets map of additional voice features. Some common feature names can be found in + * {@link VoiceInfo} static members. + */ + public Builder setAdditionalFeatures(Bundle additionalFeatures) { + this.additionalFeatures = additionalFeatures; + return this; + } + + /** + * Sets the voice quality (higher is better). + */ + public Builder setQuality(int quality) { + this.quality = quality; + return this; + } + + /** + * Sets the voice latency (lower is better). + */ + public Builder setLatency(int latency) { + this.latency = latency; + return this; + } + + /** + * Sets whether the voice requires network connection to work properly. + */ + public Builder setRequiresNetworkConnection(boolean requiresNetworkConnection) { + this.requiresNetworkConnection = requiresNetworkConnection; + return this; + } + + /** + * @return The built VoiceInfo instance. + */ + public VoiceInfo build() { + if (name == null || name.isEmpty()) { + throw new IllegalStateException("Name can't be null or empty"); + } + if (locale == null) { + throw new IllegalStateException("Locale can't be null"); + } + + return new VoiceInfo(name, locale, quality, latency, + requiresNetworkConnection, + ((params == null) ? new Bundle() : + (Bundle)params.clone()), + ((additionalFeatures == null) ? new Bundle() : + (Bundle)additionalFeatures.clone())); + } + } + + /** + * @hide + */ + @Override + public int describeContents() { + return 0; + } + + /** + * @hide + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mName); + String[] localesData = new String[]{mLocale.getLanguage(), mLocale.getCountry(), mLocale.getVariant()}; + dest.writeStringArray(localesData); + dest.writeInt(mQuality); + dest.writeInt(mLatency); + dest.writeByte((byte) (mRequiresNetworkConnection ? 1 : 0)); + dest.writeBundle(mParams); + dest.writeBundle(mAdditionalFeatures); + } + + /** + * @hide + */ + public static final Parcelable.Creator<VoiceInfo> CREATOR = new Parcelable.Creator<VoiceInfo>() { + @Override + public VoiceInfo createFromParcel(Parcel in) { + return new VoiceInfo(in); + } + + @Override + public VoiceInfo[] newArray(int size) { + return new VoiceInfo[size]; + } + }; + + /** + * @return The voice's locale + */ + public Locale getLocale() { + return mLocale; + } + + /** + * @return The voice's quality (higher is better) + */ + public int getQuality() { + return mQuality; + } + + /** + * @return The voice's latency (lower is better) + */ + public int getLatency() { + return mLatency; + } + + /** + * @return Does the Voice require a network connection to work. + */ + public boolean getRequiresNetworkConnection() { + return mRequiresNetworkConnection; + } + + /** + * @return Bundle of all available parameters with their default values. + */ + public Bundle getParamsWithDefaults() { + return mParams; + } + + /** + * @return Unique voice name. + * + * Each VoiceInfo has an unique name, that persists until client is asked to re-evaluate the + * set of the available languages in the {@link TextToSpeechClient.ConnectionCallbacks#onEngineStatusChange(android.speech.tts.TextToSpeechClient.EngineStatus)} + * callback (Voice may disappear from the set if voice was removed by the user). + */ + public String getName() { + return mName; + } + + /** + * @return Additional features of the voice. + */ + public Bundle getAdditionalFeatures() { + return mAdditionalFeatures; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(64); + return builder.append("VoiceInfo[Name: ").append(mName) + .append(" ,locale: ").append(mLocale) + .append(" ,quality: ").append(mQuality) + .append(" ,latency: ").append(mLatency) + .append(" ,requiresNetwork: ").append(mRequiresNetworkConnection) + .append(" ,paramsWithDefaults: ").append(mParams.toString()) + .append(" ,additionalFeatures: ").append(mAdditionalFeatures.toString()) + .append("]").toString(); + } +} diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java index f839d52..c80321c 100644 --- a/core/java/android/text/Html.java +++ b/core/java/android/text/Html.java @@ -48,11 +48,8 @@ import android.text.style.TypefaceSpan; import android.text.style.URLSpan; import android.text.style.UnderlineSpan; -import com.android.internal.util.XmlUtils; - import java.io.IOException; import java.io.StringReader; -import java.util.HashMap; /** * This class processes HTML strings into displayable styled text. diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java index 34274a6..b55cd6a 100644 --- a/core/java/android/text/SpannableStringBuilder.java +++ b/core/java/android/text/SpannableStringBuilder.java @@ -29,6 +29,7 @@ import java.lang.reflect.Array; */ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable, Editable, Appendable, GraphicsOperations { + private final static String TAG = "SpannableStringBuilder"; /** * Create a new SpannableStringBuilder with empty contents */ @@ -436,10 +437,26 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable } // Documentation from interface - public SpannableStringBuilder replace(final int start, final int end, + public SpannableStringBuilder replace(int start, int end, CharSequence tb, int tbstart, int tbend) { checkRange("replace", start, end); + // Sanity check + if (start > end) { + Log.w(TAG, "Bad arguments to #replace : " + + "start = " + start + ", end = " + end); + final int tmp = start; + start = end; + end = tmp; + } + if (tbstart > tbend) { + Log.w(TAG, "Bad arguments to #replace : " + + "tbstart = " + tbstart + ", tbend = " + tbend); + final int tmp = tbstart; + tbstart = tbend; + tbend = tmp; + } + int filtercount = mFilters.length; for (int i = 0; i < filtercount; i++) { CharSequence repl = mFilters[i].filter(tb, tbstart, tbend, this, start, end); @@ -613,8 +630,9 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable // 0-length Spanned.SPAN_EXCLUSIVE_EXCLUSIVE if (flagsStart == POINT && flagsEnd == MARK && start == end) { - if (send) Log.e("SpannableStringBuilder", - "SPAN_EXCLUSIVE_EXCLUSIVE spans cannot have a zero length"); + if (send) { + Log.e(TAG, "SPAN_EXCLUSIVE_EXCLUSIVE spans cannot have a zero length"); + } // Silently ignore invalid spans when they are created from this class. // This avoids the duplication of the above test code before all the // calls to setSpan that are done in this class diff --git a/core/java/android/text/format/DateUtils.java b/core/java/android/text/format/DateUtils.java index 22675b4..d0ed871 100644 --- a/core/java/android/text/format/DateUtils.java +++ b/core/java/android/text/format/DateUtils.java @@ -28,7 +28,6 @@ import java.util.Date; import java.util.Formatter; import java.util.GregorianCalendar; import java.util.Locale; -import java.util.TimeZone; import libcore.icu.DateIntervalFormat; import libcore.icu.LocaleData; diff --git a/core/java/android/text/method/HideReturnsTransformationMethod.java b/core/java/android/text/method/HideReturnsTransformationMethod.java index ce18692..c6a90ca 100644 --- a/core/java/android/text/method/HideReturnsTransformationMethod.java +++ b/core/java/android/text/method/HideReturnsTransformationMethod.java @@ -16,13 +16,6 @@ package android.text.method; -import android.graphics.Rect; -import android.text.GetChars; -import android.text.Spanned; -import android.text.SpannedString; -import android.text.TextUtils; -import android.view.View; - /** * This transformation method causes any carriage return characters (\r) * to be hidden by displaying them as zero-width non-breaking space diff --git a/core/java/android/text/method/PasswordTransformationMethod.java b/core/java/android/text/method/PasswordTransformationMethod.java index b769b76..88a69b9 100644 --- a/core/java/android/text/method/PasswordTransformationMethod.java +++ b/core/java/android/text/method/PasswordTransformationMethod.java @@ -25,7 +25,6 @@ import android.text.GetChars; import android.text.NoCopySpan; import android.text.TextUtils; import android.text.TextWatcher; -import android.text.Selection; import android.text.Spanned; import android.text.Spannable; import android.text.style.UpdateLayout; diff --git a/core/java/android/text/method/SingleLineTransformationMethod.java b/core/java/android/text/method/SingleLineTransformationMethod.java index 6a05fe4..818526a 100644 --- a/core/java/android/text/method/SingleLineTransformationMethod.java +++ b/core/java/android/text/method/SingleLineTransformationMethod.java @@ -16,15 +16,6 @@ package android.text.method; -import android.graphics.Rect; -import android.text.Editable; -import android.text.GetChars; -import android.text.Spannable; -import android.text.Spanned; -import android.text.SpannedString; -import android.text.TextUtils; -import android.view.View; - /** * This transformation method causes any newline characters (\n) to be * displayed as spaces instead of causing line breaks, and causes diff --git a/core/java/android/text/style/BackgroundColorSpan.java b/core/java/android/text/style/BackgroundColorSpan.java index 580a369..cda8015 100644 --- a/core/java/android/text/style/BackgroundColorSpan.java +++ b/core/java/android/text/style/BackgroundColorSpan.java @@ -26,9 +26,9 @@ public class BackgroundColorSpan extends CharacterStyle private final int mColor; - public BackgroundColorSpan(int color) { - mColor = color; - } + public BackgroundColorSpan(int color) { + mColor = color; + } public BackgroundColorSpan(Parcel src) { mColor = src.readInt(); @@ -46,12 +46,12 @@ public class BackgroundColorSpan extends CharacterStyle dest.writeInt(mColor); } - public int getBackgroundColor() { - return mColor; - } + public int getBackgroundColor() { + return mColor; + } - @Override - public void updateDrawState(TextPaint ds) { - ds.bgColor = mColor; - } + @Override + public void updateDrawState(TextPaint ds) { + ds.bgColor = mColor; + } } diff --git a/core/java/android/text/style/CharacterStyle.java b/core/java/android/text/style/CharacterStyle.java index 14dfddd..5b95f1a 100644 --- a/core/java/android/text/style/CharacterStyle.java +++ b/core/java/android/text/style/CharacterStyle.java @@ -24,7 +24,7 @@ import android.text.TextPaint; * ones may just implement {@link UpdateAppearance}. */ public abstract class CharacterStyle { - public abstract void updateDrawState(TextPaint tp); + public abstract void updateDrawState(TextPaint tp); /** * A given CharacterStyle can only applied to a single region of a given diff --git a/core/java/android/text/style/DrawableMarginSpan.java b/core/java/android/text/style/DrawableMarginSpan.java index c2564d5..20b6886 100644 --- a/core/java/android/text/style/DrawableMarginSpan.java +++ b/core/java/android/text/style/DrawableMarginSpan.java @@ -19,7 +19,6 @@ package android.text.style; import android.graphics.drawable.Drawable; import android.graphics.Paint; import android.graphics.Canvas; -import android.graphics.RectF; import android.text.Spanned; import android.text.Layout; diff --git a/core/java/android/text/style/DynamicDrawableSpan.java b/core/java/android/text/style/DynamicDrawableSpan.java index 89dc45b..5b8a6dd 100644 --- a/core/java/android/text/style/DynamicDrawableSpan.java +++ b/core/java/android/text/style/DynamicDrawableSpan.java @@ -17,12 +17,9 @@ package android.text.style; import android.graphics.Canvas; -import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; -import android.graphics.Paint.Style; import android.graphics.drawable.Drawable; -import android.util.Log; import java.lang.ref.WeakReference; diff --git a/core/java/android/text/style/ForegroundColorSpan.java b/core/java/android/text/style/ForegroundColorSpan.java index 476124d..c9e09bd 100644 --- a/core/java/android/text/style/ForegroundColorSpan.java +++ b/core/java/android/text/style/ForegroundColorSpan.java @@ -26,9 +26,9 @@ public class ForegroundColorSpan extends CharacterStyle private final int mColor; - public ForegroundColorSpan(int color) { - mColor = color; - } + public ForegroundColorSpan(int color) { + mColor = color; + } public ForegroundColorSpan(Parcel src) { mColor = src.readInt(); @@ -46,12 +46,12 @@ public class ForegroundColorSpan extends CharacterStyle dest.writeInt(mColor); } - public int getForegroundColor() { - return mColor; - } + public int getForegroundColor() { + return mColor; + } - @Override - public void updateDrawState(TextPaint ds) { - ds.setColor(mColor); - } + @Override + public void updateDrawState(TextPaint ds) { + ds.setColor(mColor); + } } diff --git a/core/java/android/text/style/IconMarginSpan.java b/core/java/android/text/style/IconMarginSpan.java index c786a17..cf9a705 100644 --- a/core/java/android/text/style/IconMarginSpan.java +++ b/core/java/android/text/style/IconMarginSpan.java @@ -19,7 +19,6 @@ package android.text.style; import android.graphics.Paint; import android.graphics.Bitmap; import android.graphics.Canvas; -import android.graphics.RectF; import android.text.Spanned; import android.text.Layout; diff --git a/core/java/android/text/style/ImageSpan.java b/core/java/android/text/style/ImageSpan.java index 74b9463..3d6f8e6 100644 --- a/core/java/android/text/style/ImageSpan.java +++ b/core/java/android/text/style/ImageSpan.java @@ -145,7 +145,7 @@ public class ImageSpan extends DynamicDrawableSpan { } } else { try { - drawable = mContext.getResources().getDrawable(mResourceId); + drawable = mContext.getDrawable(mResourceId); drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); } catch (Exception e) { diff --git a/core/java/android/text/style/LineHeightSpan.java b/core/java/android/text/style/LineHeightSpan.java index 44a1706..1ebee82 100644 --- a/core/java/android/text/style/LineHeightSpan.java +++ b/core/java/android/text/style/LineHeightSpan.java @@ -17,8 +17,6 @@ package android.text.style; import android.graphics.Paint; -import android.graphics.Canvas; -import android.text.Layout; import android.text.TextPaint; public interface LineHeightSpan diff --git a/core/java/android/text/style/MaskFilterSpan.java b/core/java/android/text/style/MaskFilterSpan.java index 64ab0d8..2ff52a8 100644 --- a/core/java/android/text/style/MaskFilterSpan.java +++ b/core/java/android/text/style/MaskFilterSpan.java @@ -21,18 +21,18 @@ import android.text.TextPaint; public class MaskFilterSpan extends CharacterStyle implements UpdateAppearance { - private MaskFilter mFilter; + private MaskFilter mFilter; - public MaskFilterSpan(MaskFilter filter) { - mFilter = filter; - } + public MaskFilterSpan(MaskFilter filter) { + mFilter = filter; + } - public MaskFilter getMaskFilter() { - return mFilter; - } + public MaskFilter getMaskFilter() { + return mFilter; + } - @Override - public void updateDrawState(TextPaint ds) { - ds.setMaskFilter(mFilter); - } + @Override + public void updateDrawState(TextPaint ds) { + ds.setMaskFilter(mFilter); + } } diff --git a/core/java/android/text/style/MetricAffectingSpan.java b/core/java/android/text/style/MetricAffectingSpan.java index 92558eb..853ecc6 100644 --- a/core/java/android/text/style/MetricAffectingSpan.java +++ b/core/java/android/text/style/MetricAffectingSpan.java @@ -16,7 +16,6 @@ package android.text.style; -import android.graphics.Paint; import android.text.TextPaint; /** @@ -27,7 +26,7 @@ public abstract class MetricAffectingSpan extends CharacterStyle implements UpdateLayout { - public abstract void updateMeasureState(TextPaint p); + public abstract void updateMeasureState(TextPaint p); /** * Returns "this" for most MetricAffectingSpans, but for diff --git a/core/java/android/text/style/RasterizerSpan.java b/core/java/android/text/style/RasterizerSpan.java index 75b5bcc..cae9640 100644 --- a/core/java/android/text/style/RasterizerSpan.java +++ b/core/java/android/text/style/RasterizerSpan.java @@ -21,18 +21,18 @@ import android.text.TextPaint; public class RasterizerSpan extends CharacterStyle implements UpdateAppearance { - private Rasterizer mRasterizer; + private Rasterizer mRasterizer; - public RasterizerSpan(Rasterizer r) { - mRasterizer = r; - } + public RasterizerSpan(Rasterizer r) { + mRasterizer = r; + } - public Rasterizer getRasterizer() { - return mRasterizer; - } + public Rasterizer getRasterizer() { + return mRasterizer; + } - @Override - public void updateDrawState(TextPaint ds) { - ds.setRasterizer(mRasterizer); - } + @Override + public void updateDrawState(TextPaint ds) { + ds.setRasterizer(mRasterizer); + } } diff --git a/core/java/android/text/style/RelativeSizeSpan.java b/core/java/android/text/style/RelativeSizeSpan.java index 9717362..632dbd4 100644 --- a/core/java/android/text/style/RelativeSizeSpan.java +++ b/core/java/android/text/style/RelativeSizeSpan.java @@ -23,11 +23,11 @@ import android.text.TextUtils; public class RelativeSizeSpan extends MetricAffectingSpan implements ParcelableSpan { - private final float mProportion; + private final float mProportion; - public RelativeSizeSpan(float proportion) { - mProportion = proportion; - } + public RelativeSizeSpan(float proportion) { + mProportion = proportion; + } public RelativeSizeSpan(Parcel src) { mProportion = src.readFloat(); @@ -45,17 +45,17 @@ public class RelativeSizeSpan extends MetricAffectingSpan implements ParcelableS dest.writeFloat(mProportion); } - public float getSizeChange() { - return mProportion; - } + public float getSizeChange() { + return mProportion; + } - @Override - public void updateDrawState(TextPaint ds) { - ds.setTextSize(ds.getTextSize() * mProportion); - } + @Override + public void updateDrawState(TextPaint ds) { + ds.setTextSize(ds.getTextSize() * mProportion); + } - @Override - public void updateMeasureState(TextPaint ds) { - ds.setTextSize(ds.getTextSize() * mProportion); - } + @Override + public void updateMeasureState(TextPaint ds) { + ds.setTextSize(ds.getTextSize() * mProportion); + } } diff --git a/core/java/android/text/style/ScaleXSpan.java b/core/java/android/text/style/ScaleXSpan.java index 655064b..a22a5a1 100644 --- a/core/java/android/text/style/ScaleXSpan.java +++ b/core/java/android/text/style/ScaleXSpan.java @@ -23,11 +23,11 @@ import android.text.TextUtils; public class ScaleXSpan extends MetricAffectingSpan implements ParcelableSpan { - private final float mProportion; + private final float mProportion; - public ScaleXSpan(float proportion) { - mProportion = proportion; - } + public ScaleXSpan(float proportion) { + mProportion = proportion; + } public ScaleXSpan(Parcel src) { mProportion = src.readFloat(); @@ -45,17 +45,17 @@ public class ScaleXSpan extends MetricAffectingSpan implements ParcelableSpan { dest.writeFloat(mProportion); } - public float getScaleX() { - return mProportion; - } + public float getScaleX() { + return mProportion; + } - @Override - public void updateDrawState(TextPaint ds) { - ds.setTextScaleX(ds.getTextScaleX() * mProportion); - } + @Override + public void updateDrawState(TextPaint ds) { + ds.setTextScaleX(ds.getTextScaleX() * mProportion); + } - @Override - public void updateMeasureState(TextPaint ds) { - ds.setTextScaleX(ds.getTextScaleX() * mProportion); - } + @Override + public void updateMeasureState(TextPaint ds) { + ds.setTextScaleX(ds.getTextScaleX() * mProportion); + } } diff --git a/core/java/android/text/style/StrikethroughSpan.java b/core/java/android/text/style/StrikethroughSpan.java index b51363a..303e415 100644 --- a/core/java/android/text/style/StrikethroughSpan.java +++ b/core/java/android/text/style/StrikethroughSpan.java @@ -40,8 +40,8 @@ public class StrikethroughSpan extends CharacterStyle public void writeToParcel(Parcel dest, int flags) { } - @Override - public void updateDrawState(TextPaint ds) { - ds.setStrikeThruText(true); - } + @Override + public void updateDrawState(TextPaint ds) { + ds.setStrikeThruText(true); + } } diff --git a/core/java/android/text/style/StyleSpan.java b/core/java/android/text/style/StyleSpan.java index 8e6147c..b08f70e 100644 --- a/core/java/android/text/style/StyleSpan.java +++ b/core/java/android/text/style/StyleSpan.java @@ -33,17 +33,17 @@ import android.text.TextUtils; */ public class StyleSpan extends MetricAffectingSpan implements ParcelableSpan { - private final int mStyle; - - /** - * - * @param style An integer constant describing the style for this span. Examples - * include bold, italic, and normal. Values are constants defined - * in {@link android.graphics.Typeface}. - */ - public StyleSpan(int style) { - mStyle = style; - } + private final int mStyle; + + /** + * + * @param style An integer constant describing the style for this span. Examples + * include bold, italic, and normal. Values are constants defined + * in {@link android.graphics.Typeface}. + */ + public StyleSpan(int style) { + mStyle = style; + } public StyleSpan(Parcel src) { mStyle = src.readInt(); @@ -61,19 +61,19 @@ public class StyleSpan extends MetricAffectingSpan implements ParcelableSpan { dest.writeInt(mStyle); } - /** - * Returns the style constant defined in {@link android.graphics.Typeface}. - */ - public int getStyle() { - return mStyle; - } + /** + * Returns the style constant defined in {@link android.graphics.Typeface}. + */ + public int getStyle() { + return mStyle; + } - @Override + @Override public void updateDrawState(TextPaint ds) { apply(ds, mStyle); } - @Override + @Override public void updateMeasureState(TextPaint paint) { apply(paint, mStyle); } diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java index 0ec7e84..8b40953 100644 --- a/core/java/android/text/style/SuggestionSpan.java +++ b/core/java/android/text/style/SuggestionSpan.java @@ -166,25 +166,25 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan { return; } - int defStyle = com.android.internal.R.attr.textAppearanceMisspelledSuggestion; + int defStyleAttr = com.android.internal.R.attr.textAppearanceMisspelledSuggestion; TypedArray typedArray = context.obtainStyledAttributes( - null, com.android.internal.R.styleable.SuggestionSpan, defStyle, 0); + null, com.android.internal.R.styleable.SuggestionSpan, defStyleAttr, 0); mMisspelledUnderlineThickness = typedArray.getDimension( com.android.internal.R.styleable.SuggestionSpan_textUnderlineThickness, 0); mMisspelledUnderlineColor = typedArray.getColor( com.android.internal.R.styleable.SuggestionSpan_textUnderlineColor, Color.BLACK); - defStyle = com.android.internal.R.attr.textAppearanceEasyCorrectSuggestion; + defStyleAttr = com.android.internal.R.attr.textAppearanceEasyCorrectSuggestion; typedArray = context.obtainStyledAttributes( - null, com.android.internal.R.styleable.SuggestionSpan, defStyle, 0); + null, com.android.internal.R.styleable.SuggestionSpan, defStyleAttr, 0); mEasyCorrectUnderlineThickness = typedArray.getDimension( com.android.internal.R.styleable.SuggestionSpan_textUnderlineThickness, 0); mEasyCorrectUnderlineColor = typedArray.getColor( com.android.internal.R.styleable.SuggestionSpan_textUnderlineColor, Color.BLACK); - defStyle = com.android.internal.R.attr.textAppearanceAutoCorrectionSuggestion; + defStyleAttr = com.android.internal.R.attr.textAppearanceAutoCorrectionSuggestion; typedArray = context.obtainStyledAttributes( - null, com.android.internal.R.styleable.SuggestionSpan, defStyle, 0); + null, com.android.internal.R.styleable.SuggestionSpan, defStyleAttr, 0); mAutoCorrectionUnderlineThickness = typedArray.getDimension( com.android.internal.R.styleable.SuggestionSpan_textUnderlineThickness, 0); mAutoCorrectionUnderlineColor = typedArray.getColor( diff --git a/core/java/android/text/style/UnderlineSpan.java b/core/java/android/text/style/UnderlineSpan.java index b0cb0e8..80b2427 100644 --- a/core/java/android/text/style/UnderlineSpan.java +++ b/core/java/android/text/style/UnderlineSpan.java @@ -40,8 +40,8 @@ public class UnderlineSpan extends CharacterStyle public void writeToParcel(Parcel dest, int flags) { } - @Override - public void updateDrawState(TextPaint ds) { - ds.setUnderlineText(true); - } + @Override + public void updateDrawState(TextPaint ds) { + ds.setUnderlineText(true); + } } diff --git a/core/java/android/transition/Recolor.java b/core/java/android/transition/Recolor.java index 70111d1..1638f67 100644 --- a/core/java/android/transition/Recolor.java +++ b/core/java/android/transition/Recolor.java @@ -17,7 +17,6 @@ package android.transition; import android.animation.Animator; -import android.animation.ArgbEvaluator; import android.animation.ObjectAnimator; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; @@ -75,8 +74,8 @@ public class Recolor extends Transition { if (startColor.getColor() != endColor.getColor()) { endColor.setColor(startColor.getColor()); changed = true; - return ObjectAnimator.ofObject(endBackground, "color", - new ArgbEvaluator(), startColor.getColor(), endColor.getColor()); + return ObjectAnimator.ofArgb(endBackground, "color", startColor.getColor(), + endColor.getColor()); } } if (view instanceof TextView) { @@ -86,8 +85,7 @@ public class Recolor extends Transition { if (start != end) { textView.setTextColor(end); changed = true; - return ObjectAnimator.ofObject(textView, "textColor", - new ArgbEvaluator(), start, end); + return ObjectAnimator.ofArgb(textView, "textColor", start, end); } } return null; diff --git a/core/java/android/transition/Scene.java b/core/java/android/transition/Scene.java index e1f1896..4267a65 100644 --- a/core/java/android/transition/Scene.java +++ b/core/java/android/transition/Scene.java @@ -34,7 +34,7 @@ public final class Scene { private Context mContext; private int mLayoutId = -1; private ViewGroup mSceneRoot; - private ViewGroup mLayout; // alternative to layoutId + private View mLayout; // alternative to layoutId Runnable mEnterAction, mExitAction; /** @@ -114,6 +114,15 @@ public final class Scene { * @param layout The view hierarchy of this scene, added as a child * of sceneRoot when this scene is entered. */ + public Scene(ViewGroup sceneRoot, View layout) { + mSceneRoot = sceneRoot; + mLayout = layout; + } + + /** + * @deprecated use {@link #Scene(ViewGroup, View)}. + */ + @Deprecated public Scene(ViewGroup sceneRoot, ViewGroup layout) { mSceneRoot = sceneRoot; mLayout = layout; diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java index da9ba5a..c88b4c0 100644 --- a/core/java/android/transition/Transition.java +++ b/core/java/android/transition/Transition.java @@ -28,6 +28,7 @@ import android.view.TextureView; import android.view.View; import android.view.ViewGroup; import android.view.ViewOverlay; +import android.view.WindowId; import android.widget.ListView; import android.widget.Spinner; @@ -496,7 +497,8 @@ public abstract class Transition implements Cloneable { view = (start != null) ? start.view : null; } if (animator != null) { - AnimationInfo info = new AnimationInfo(view, getName(), infoValues); + AnimationInfo info = new AnimationInfo(view, getName(), + sceneRoot.getWindowId(), infoValues); runningAnimators.put(animator, info); mAnimators.add(animator); } @@ -1077,6 +1079,9 @@ public abstract class Transition implements Cloneable { if (view == null) { return; } + if (!isValidTarget(view, view.getId())) { + return; + } boolean isListViewItem = false; if (view.getParent() instanceof ListView) { isListViewItem = true; @@ -1109,30 +1114,32 @@ public abstract class Transition implements Cloneable { } } } - TransitionValues values = new TransitionValues(); - values.view = view; - if (start) { - captureStartValues(values); - } else { - captureEndValues(values); - } - if (start) { - if (!isListViewItem) { - mStartValues.viewValues.put(view, values); - if (id >= 0) { - mStartValues.idValues.put((int) id, values); - } + if (view.getParent() instanceof ViewGroup) { + TransitionValues values = new TransitionValues(); + values.view = view; + if (start) { + captureStartValues(values); } else { - mStartValues.itemIdValues.put(itemId, values); + captureEndValues(values); } - } else { - if (!isListViewItem) { - mEndValues.viewValues.put(view, values); - if (id >= 0) { - mEndValues.idValues.put((int) id, values); + if (start) { + if (!isListViewItem) { + mStartValues.viewValues.put(view, values); + if (id >= 0) { + mStartValues.idValues.put((int) id, values); + } + } else { + mStartValues.itemIdValues.put(itemId, values); } } else { - mEndValues.itemIdValues.put(itemId, values); + if (!isListViewItem) { + mEndValues.viewValues.put(view, values); + if (id >= 0) { + mEndValues.idValues.put((int) id, values); + } + } else { + mEndValues.itemIdValues.put(itemId, values); + } } } if (view instanceof ViewGroup) { @@ -1194,13 +1201,17 @@ public abstract class Transition implements Cloneable { * * @hide */ - public void pause() { + public void pause(View sceneRoot) { if (!mEnded) { ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators(); int numOldAnims = runningAnimators.size(); + WindowId windowId = sceneRoot.getWindowId(); for (int i = numOldAnims - 1; i >= 0; i--) { - Animator anim = runningAnimators.keyAt(i); - anim.pause(); + AnimationInfo info = runningAnimators.valueAt(i); + if (info.view != null && windowId.equals(info.windowId)) { + Animator anim = runningAnimators.keyAt(i); + anim.pause(); + } } if (mListeners != null && mListeners.size() > 0) { ArrayList<TransitionListener> tmpListeners = @@ -1221,14 +1232,18 @@ public abstract class Transition implements Cloneable { * * @hide */ - public void resume() { + public void resume(View sceneRoot) { if (mPaused) { if (!mEnded) { ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators(); int numOldAnims = runningAnimators.size(); + WindowId windowId = sceneRoot.getWindowId(); for (int i = numOldAnims - 1; i >= 0; i--) { - Animator anim = runningAnimators.keyAt(i); - anim.resume(); + AnimationInfo info = runningAnimators.valueAt(i); + if (info.view != null && windowId.equals(info.windowId)) { + Animator anim = runningAnimators.keyAt(i); + anim.resume(); + } } if (mListeners != null && mListeners.size() > 0) { ArrayList<TransitionListener> tmpListeners = @@ -1467,6 +1482,10 @@ public abstract class Transition implements Cloneable { mCanRemoveViews = canRemoveViews; } + public boolean canRemoveViews() { + return mCanRemoveViews; + } + @Override public String toString() { return toString(""); @@ -1629,16 +1648,19 @@ public abstract class Transition implements Cloneable { * animation should be canceled or a new animation noop'd. The structure holds * information about the state that an animation is going to, to be compared to * end state of a new animation. + * @hide */ - private static class AnimationInfo { - View view; + public static class AnimationInfo { + public View view; String name; TransitionValues values; + WindowId windowId; - AnimationInfo(View view, String name, TransitionValues values) { + AnimationInfo(View view, String name, WindowId windowId, TransitionValues values) { this.view = view; this.name = name; this.values = values; + this.windowId = windowId; } } diff --git a/core/java/android/transition/TransitionInflater.java b/core/java/android/transition/TransitionInflater.java index 9f77d5e..912f2ed 100644 --- a/core/java/android/transition/TransitionInflater.java +++ b/core/java/android/transition/TransitionInflater.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; +import android.text.TextUtils; import android.util.AttributeSet; import android.util.Xml; import android.view.InflateException; @@ -284,25 +285,27 @@ public class TransitionInflater { com.android.internal.R.styleable.TransitionManager); int transitionId = a.getResourceId( com.android.internal.R.styleable.TransitionManager_transition, -1); - Scene fromScene = null, toScene = null; int fromId = a.getResourceId( com.android.internal.R.styleable.TransitionManager_fromScene, -1); - if (fromId >= 0) fromScene = Scene.getSceneForLayout(sceneRoot, fromId, mContext); + Scene fromScene = (fromId < 0) ? null: Scene.getSceneForLayout(sceneRoot, fromId, mContext); int toId = a.getResourceId( com.android.internal.R.styleable.TransitionManager_toScene, -1); - if (toId >= 0) toScene = Scene.getSceneForLayout(sceneRoot, toId, mContext); + Scene toScene = (toId < 0) ? null : Scene.getSceneForLayout(sceneRoot, toId, mContext); + if (transitionId >= 0) { Transition transition = inflateTransition(transitionId); if (transition != null) { - if (fromScene != null) { - if (toScene == null){ - throw new RuntimeException("No matching toScene for given fromScene " + + if (fromScene == null) { + if (toScene == null) { + throw new RuntimeException("No matching fromScene or toScene " + "for transition ID " + transitionId); } else { - transitionManager.setTransition(fromScene, toScene, transition); + transitionManager.setTransition(toScene, transition); } - } else if (toId >= 0) { - transitionManager.setTransition(toScene, transition); + } else if (toScene == null) { + transitionManager.setExitTransition(fromScene, transition); + } else { + transitionManager.setTransition(fromScene, toScene, transition); } } } diff --git a/core/java/android/transition/TransitionManager.java b/core/java/android/transition/TransitionManager.java index 3bf6790..1614d34 100644 --- a/core/java/android/transition/TransitionManager.java +++ b/core/java/android/transition/TransitionManager.java @@ -67,7 +67,10 @@ public class TransitionManager { private static Transition sDefaultTransition = new AutoTransition(); + private static final String[] EMPTY_STRINGS = new String[0]; + ArrayMap<Scene, Transition> mSceneTransitions = new ArrayMap<Scene, Transition>(); + ArrayMap<Scene, Transition> mExitSceneTransitions = new ArrayMap<Scene, Transition>(); ArrayMap<Scene, ArrayMap<Scene, Transition>> mScenePairTransitions = new ArrayMap<Scene, ArrayMap<Scene, Transition>>(); private static ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>> @@ -116,6 +119,21 @@ public class TransitionManager { } /** + * Sets a specific transition to occur when the given scene is exited. This + * has the lowest priority -- if a Scene-to-Scene transition or + * Scene enter transition can be applied, it will. + * + * @param scene The scene which, when exited, will cause the given + * transition to run. + * @param transition The transition that will play when the given scene is + * exited. A value of null will result in the default behavior of + * using the default transition instead. + */ + public void setExitTransition(Scene scene, Transition transition) { + mExitSceneTransitions.put(scene, transition); + } + + /** * Sets a specific transition to occur when the given pair of scenes is * exited/entered. * @@ -163,6 +181,9 @@ public class TransitionManager { } } transition = mSceneTransitions.get(scene); + if (transition == null && sceneRoot != null) { + transition = mExitSceneTransitions.get(Scene.getCurrentScene(sceneRoot)); + } return (transition != null) ? transition : sDefaultTransition; } @@ -218,6 +239,34 @@ public class TransitionManager { } /** + * Retrieve the transition to a target defined scene if one has been + * associated with this TransitionManager. + * + * @param toScene Target scene that this transition will move to + * @return Transition corresponding to the given toScene or null + * if no association exists in this TransitionManager + * + * @see #setTransition(Scene, Transition) + * @hide + */ + public Transition getEnterTransition(Scene toScene) { + return mSceneTransitions.get(toScene); + } + + /** + * Retrieve the transition from a defined scene to a target named scene if one has been + * associated with this TransitionManager. + * + * @param fromScene Scene that this transition starts from + * @return Transition corresponding to the given fromScene or null + * if no association exists in this TransitionManager + * @hide + */ + public Transition getExitTransition(Scene fromScene) { + return mExitSceneTransitions.get(fromScene); + } + + /** * This private utility class is used to listen for both OnPreDraw and * OnAttachStateChange events. OnPreDraw events are the main ones we care * about since that's what triggers the transition to take place. @@ -253,7 +302,7 @@ public class TransitionManager { ArrayList<Transition> runningTransitions = getRunningTransitions().get(mSceneRoot); if (runningTransitions != null && runningTransitions.size() > 0) { for (Transition runningTransition : runningTransitions) { - runningTransition.resume(); + runningTransition.resume(mSceneRoot); } } mTransition.clearValues(true); @@ -286,7 +335,7 @@ public class TransitionManager { mTransition.captureValues(mSceneRoot, false); if (previousRunningTransitions != null) { for (Transition runningTransition : previousRunningTransitions) { - runningTransition.resume(); + runningTransition.resume(mSceneRoot); } } mTransition.playTransition(mSceneRoot); @@ -302,7 +351,7 @@ public class TransitionManager { if (runningTransitions != null && runningTransitions.size() > 0) { for (Transition runningTransition : runningTransitions) { - runningTransition.pause(); + runningTransition.pause(sceneRoot); } } @@ -329,7 +378,6 @@ public class TransitionManager { // Auto transition if there is no transition declared for the Scene, but there is // a root or parent view changeScene(scene, getTransition(scene)); - } /** diff --git a/core/java/android/transition/TransitionSet.java b/core/java/android/transition/TransitionSet.java index 4545e3b..19d6b3d 100644 --- a/core/java/android/transition/TransitionSet.java +++ b/core/java/android/transition/TransitionSet.java @@ -317,21 +317,21 @@ public class TransitionSet extends Transition { /** @hide */ @Override - public void pause() { - super.pause(); + public void pause(View sceneRoot) { + super.pause(sceneRoot); int numTransitions = mTransitions.size(); for (int i = 0; i < numTransitions; ++i) { - mTransitions.get(i).pause(); + mTransitions.get(i).pause(sceneRoot); } } /** @hide */ @Override - public void resume() { - super.resume(); + public void resume(View sceneRoot) { + super.resume(sceneRoot); int numTransitions = mTransitions.size(); for (int i = 0; i < numTransitions; ++i) { - mTransitions.get(i).resume(); + mTransitions.get(i).resume(sceneRoot); } } diff --git a/core/java/android/util/EventLogTags.java b/core/java/android/util/EventLogTags.java index 8c18417..f4ce4fd 100644 --- a/core/java/android/util/EventLogTags.java +++ b/core/java/android/util/EventLogTags.java @@ -16,14 +16,8 @@ package android.util; -import android.util.Log; - import java.io.BufferedReader; -import java.io.FileReader; import java.io.IOException; -import java.util.HashMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * @deprecated This class is no longer functional. diff --git a/core/java/android/util/LocalLog.java b/core/java/android/util/LocalLog.java index 641d1b4..eeb6d58 100644 --- a/core/java/android/util/LocalLog.java +++ b/core/java/android/util/LocalLog.java @@ -20,7 +20,6 @@ import android.text.format.Time; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.io.StringWriter; import java.util.Iterator; import java.util.LinkedList; diff --git a/core/java/android/util/LongArray.java b/core/java/android/util/LongArray.java new file mode 100644 index 0000000..290a89b --- /dev/null +++ b/core/java/android/util/LongArray.java @@ -0,0 +1,166 @@ +/* + * 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.util; + +import com.android.internal.util.ArrayUtils; + +/** + * Implements a growing array of long primitives. + * + * @hide + */ +public class LongArray implements Cloneable { + private static final int MIN_CAPACITY_INCREMENT = 12; + + private long[] mValues; + private int mSize; + + /** + * Creates an empty LongArray with the default initial capacity. + */ + public LongArray() { + this(10); + } + + /** + * Creates an empty LongArray with the specified initial capacity. + */ + public LongArray(int initialCapacity) { + if (initialCapacity == 0) { + mValues = ContainerHelpers.EMPTY_LONGS; + } else { + initialCapacity = ArrayUtils.idealLongArraySize(initialCapacity); + mValues = new long[initialCapacity]; + } + mSize = 0; + } + + /** + * Appends the specified value to the end of this array. + */ + public void add(long value) { + add(mSize, value); + } + + /** + * Inserts a value at the specified position in this array. + * + * @throws IndexOutOfBoundsException when index < 0 || index > size() + */ + public void add(int index, long value) { + if (index < 0 || index > mSize) { + throw new IndexOutOfBoundsException(); + } + + ensureCapacity(1); + + if (mSize - index != 0) { + System.arraycopy(mValues, index, mValues, index + 1, mSize - index); + } + + mValues[index] = value; + mSize++; + } + + /** + * Adds the values in the specified array to this array. + */ + public void addAll(LongArray values) { + final int count = values.mSize; + ensureCapacity(count); + + System.arraycopy(values.mValues, 0, mValues, mSize, count); + mSize += count; + } + + /** + * Ensures capacity to append at least <code>count</code> values. + */ + private void ensureCapacity(int count) { + final int currentSize = mSize; + final int minCapacity = currentSize + count; + if (minCapacity >= mValues.length) { + final int targetCap = currentSize + (currentSize < (MIN_CAPACITY_INCREMENT / 2) ? + MIN_CAPACITY_INCREMENT : currentSize >> 1); + final int newCapacity = targetCap > minCapacity ? targetCap : minCapacity; + final long[] newValues = new long[ArrayUtils.idealLongArraySize(newCapacity)]; + System.arraycopy(mValues, 0, newValues, 0, currentSize); + mValues = newValues; + } + } + + /** + * Removes all values from this array. + */ + public void clear() { + mSize = 0; + } + + @Override + @SuppressWarnings("unchecked") + public LongArray clone() { + LongArray clone = null; + try { + clone = (LongArray) super.clone(); + clone.mValues = mValues.clone(); + } catch (CloneNotSupportedException cnse) { + /* ignore */ + } + return clone; + } + + /** + * Returns the value at the specified position in this array. + */ + public long get(int index) { + if (index >= mSize) { + throw new ArrayIndexOutOfBoundsException(mSize, index); + } + return mValues[index]; + } + + /** + * Returns the index of the first occurrence of the specified value in this + * array, or -1 if this array does not contain the value. + */ + public int indexOf(long value) { + final int n = mSize; + for (int i = 0; i < n; i++) { + if (mValues[i] == value) { + return i; + } + } + return -1; + } + + /** + * Removes the value at the specified index from this array. + */ + public void remove(int index) { + if (index >= mSize) { + throw new ArrayIndexOutOfBoundsException(mSize, index); + } + System.arraycopy(mValues, index, mValues, index + 1, mSize - index); + } + + /** + * Returns the number of values in this array. + */ + public int size() { + return mSize; + } +} diff --git a/core/java/android/util/LongSparseLongArray.java b/core/java/android/util/LongSparseLongArray.java index 6654899..b8073dd 100644 --- a/core/java/android/util/LongSparseLongArray.java +++ b/core/java/android/util/LongSparseLongArray.java @@ -18,8 +18,6 @@ package android.util; import com.android.internal.util.ArrayUtils; -import java.util.Arrays; - /** * Map of {@code long} to {@code long}. Unlike a normal array of longs, there * can be gaps in the indices. It is intended to be more memory efficient than using a diff --git a/core/java/android/util/LruCache.java b/core/java/android/util/LruCache.java index dd504c1..4015488 100644 --- a/core/java/android/util/LruCache.java +++ b/core/java/android/util/LruCache.java @@ -87,9 +87,8 @@ public class LruCache<K, V> { /** * Sets the size of the cache. - * @param maxSize The new maximum size. * - * @hide + * @param maxSize The new maximum size. */ public void resize(int maxSize) { if (maxSize <= 0) { diff --git a/core/java/android/util/Slog.java b/core/java/android/util/Slog.java index 70795bb..b25d80f 100644 --- a/core/java/android/util/Slog.java +++ b/core/java/android/util/Slog.java @@ -16,11 +16,6 @@ package android.util; -import com.android.internal.os.RuntimeInit; - -import java.io.PrintWriter; -import java.io.StringWriter; - /** * @hide */ diff --git a/core/java/android/util/SparseBooleanArray.java b/core/java/android/util/SparseBooleanArray.java index 905dcb0..f59ef0f6d 100644 --- a/core/java/android/util/SparseBooleanArray.java +++ b/core/java/android/util/SparseBooleanArray.java @@ -115,6 +115,13 @@ public class SparseBooleanArray implements Cloneable { } } + /** @hide */ + public void removeAt(int index) { + System.arraycopy(mKeys, index + 1, mKeys, index, mSize - (index + 1)); + System.arraycopy(mValues, index + 1, mValues, index, mSize - (index + 1)); + mSize--; + } + /** * Adds a mapping from the specified key to the specified value, * replacing the previous mapping from the specified key if there @@ -191,6 +198,11 @@ public class SparseBooleanArray implements Cloneable { return mValues[index]; } + /** @hide */ + public void setValueAt(int index, boolean value) { + mValues[index] = value; + } + /** * Returns the index for which {@link #keyAt} would return the * specified key, or a negative number if the specified diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java index 41d3700..3859ad4 100644 --- a/core/java/android/view/AccessibilityInteractionController.java +++ b/core/java/android/view/AccessibilityInteractionController.java @@ -24,7 +24,6 @@ import android.os.Looper; import android.os.Message; import android.os.Process; import android.os.RemoteException; -import android.util.SparseLongArray; import android.view.View.AttachInfo; import android.view.accessibility.AccessibilityInteractionClient; import android.view.accessibility.AccessibilityNodeInfo; @@ -881,13 +880,12 @@ final class AccessibilityInteractionController { AccessibilityNodeInfo parent = provider.createAccessibilityNodeInfo(parentVirtualDescendantId); if (parent != null) { - SparseLongArray childNodeIds = parent.getChildNodeIds(); - final int childCount = childNodeIds.size(); + final int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) { if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { return; } - final long childNodeId = childNodeIds.get(i); + final long childNodeId = parent.getChildId(i); if (childNodeId != current.getSourceNodeId()) { final int childVirtualDescendantId = AccessibilityNodeInfo.getVirtualDescendantId(childNodeId); @@ -906,14 +904,13 @@ final class AccessibilityInteractionController { private void prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root, AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) { - SparseLongArray childNodeIds = root.getChildNodeIds(); final int initialOutInfosSize = outInfos.size(); - final int childCount = childNodeIds.size(); + final int childCount = root.getChildCount(); for (int i = 0; i < childCount; i++) { if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { return; } - final long childNodeId = childNodeIds.get(i); + final long childNodeId = root.getChildId(i); AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo( AccessibilityNodeInfo.getVirtualDescendantId(childNodeId)); if (child != null) { diff --git a/core/java/android/view/AccessibilityIterators.java b/core/java/android/view/AccessibilityIterators.java index 17ce4f6..e59937d 100644 --- a/core/java/android/view/AccessibilityIterators.java +++ b/core/java/android/view/AccessibilityIterators.java @@ -17,8 +17,6 @@ package android.view; import android.content.ComponentCallbacks; -import android.content.Context; -import android.content.pm.ActivityInfo; import android.content.res.Configuration; import java.text.BreakIterator; diff --git a/core/java/android/view/ContextThemeWrapper.java b/core/java/android/view/ContextThemeWrapper.java index 6c733f9..0afbde9 100644 --- a/core/java/android/view/ContextThemeWrapper.java +++ b/core/java/android/view/ContextThemeWrapper.java @@ -20,14 +20,12 @@ import android.content.Context; import android.content.ContextWrapper; import android.content.res.Configuration; import android.content.res.Resources; -import android.os.Build; /** * A ContextWrapper that allows you to modify the theme from what is in the * wrapped context. */ public class ContextThemeWrapper extends ContextWrapper { - private Context mBase; private int mThemeResource; private Resources.Theme mTheme; private LayoutInflater mInflater; @@ -40,13 +38,11 @@ public class ContextThemeWrapper extends ContextWrapper { public ContextThemeWrapper(Context base, int themeres) { super(base); - mBase = base; mThemeResource = themeres; } @Override protected void attachBaseContext(Context newBase) { super.attachBaseContext(newBase); - mBase = newBase; } /** @@ -110,11 +106,11 @@ public class ContextThemeWrapper extends ContextWrapper { @Override public Object getSystemService(String name) { if (LAYOUT_INFLATER_SERVICE.equals(name)) { if (mInflater == null) { - mInflater = LayoutInflater.from(mBase).cloneInContext(this); + mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this); } return mInflater; } - return mBase.getSystemService(name); + return getBaseContext().getSystemService(name); } /** @@ -136,7 +132,7 @@ public class ContextThemeWrapper extends ContextWrapper { final boolean first = mTheme == null; if (first) { mTheme = getResources().newTheme(); - Resources.Theme theme = mBase.getTheme(); + Resources.Theme theme = getBaseContext().getTheme(); if (theme != null) { mTheme.setTo(theme); } diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 7d310a2..d3f63b4 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -528,6 +528,7 @@ public final class Display { * 90 degrees clockwise and thus the returned value here will be * {@link Surface#ROTATION_90 Surface.ROTATION_90}. */ + @Surface.Rotation public int getRotation() { synchronized (this) { updateDisplayInfoLocked(); @@ -540,6 +541,7 @@ public final class Display { * @return orientation of this display. */ @Deprecated + @Surface.Rotation public int getOrientation() { return getRotation(); } diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java index 8944207..7fd7b83 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -20,7 +20,6 @@ import android.content.res.CompatibilityInfo; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; -import android.os.Process; import android.util.DisplayMetrics; import libcore.util.Objects; @@ -144,6 +143,7 @@ public final class DisplayInfo implements Parcelable { * more than one physical display. * </p> */ + @Surface.Rotation public int rotation; /** diff --git a/core/java/android/view/DisplayList.java b/core/java/android/view/DisplayList.java index 43fd628..be6f401 100644 --- a/core/java/android/view/DisplayList.java +++ b/core/java/android/view/DisplayList.java @@ -17,6 +17,7 @@ package android.view; import android.graphics.Matrix; +import android.graphics.Path; /** * <p>A display list records a series of graphics related operations and can replay @@ -86,18 +87,15 @@ import android.graphics.Matrix; * * <pre class="prettyprint"> * private void createDisplayList() { - * HardwareRenderer renderer = getHardwareRenderer(); - * if (renderer != null) { - * mDisplayList = renderer.createDisplayList(); - * HardwareCanvas canvas = mDisplayList.start(width, height); - * try { - * for (Bitmap b : mBitmaps) { - * canvas.drawBitmap(b, 0.0f, 0.0f, null); - * canvas.translate(0.0f, b.getHeight()); - * } - * } finally { - * displayList.end(); + * mDisplayList = DisplayList.create("MyDisplayList"); + * HardwareCanvas canvas = mDisplayList.start(width, height); + * try { + * for (Bitmap b : mBitmaps) { + * canvas.drawBitmap(b, 0.0f, 0.0f, null); + * canvas.translate(0.0f, b.getHeight()); * } + * } finally { + * displayList.end(); * } * } * @@ -123,12 +121,10 @@ import android.graphics.Matrix; * * @hide */ -public abstract class DisplayList { - private boolean mDirty; - +public class DisplayList { /** * Flag used when calling - * {@link HardwareCanvas#drawDisplayList(DisplayList, android.graphics.Rect, int)} + * {@link HardwareCanvas#drawDisplayList(DisplayList, android.graphics.Rect, int)} * When this flag is set, draw operations lying outside of the bounds of the * display list will be culled early. It is recommeneded to always set this * flag. @@ -141,7 +137,7 @@ public abstract class DisplayList { /** * Indicates that the display list is done drawing. - * + * * @see HardwareCanvas#drawDisplayList(DisplayList, android.graphics.Rect, int) * * @hide @@ -150,7 +146,7 @@ public abstract class DisplayList { /** * Indicates that the display list needs another drawing pass. - * + * * @see HardwareCanvas#drawDisplayList(DisplayList, android.graphics.Rect, int) * * @hide @@ -159,9 +155,9 @@ public abstract class DisplayList { /** * Indicates that the display list needs to re-execute its GL functors. - * - * @see HardwareCanvas#drawDisplayList(DisplayList, android.graphics.Rect, int) - * @see HardwareCanvas#callDrawGLFunction(int) + * + * @see HardwareCanvas#drawDisplayList(DisplayList, android.graphics.Rect, int) + * @see HardwareCanvas#callDrawGLFunction(long) * * @hide */ @@ -176,6 +172,29 @@ public abstract class DisplayList { */ public static final int STATUS_DREW = 0x4; + private boolean mValid; + private final long mNativeDisplayList; + private HardwareRenderer mRenderer; + + private DisplayList(String name) { + mNativeDisplayList = nCreate(); + nSetDisplayListName(mNativeDisplayList, name); + } + + /** + * Creates a new display list that can be used to record batches of + * drawing operations. + * + * @param name The name of the display list, used for debugging purpose. May be null. + * + * @return A new display list. + * + * @hide + */ + public static DisplayList create(String name) { + return new DisplayList(name); + } + /** * Starts recording the display list. All operations performed on the * returned canvas are recorded and stored in this display list. @@ -191,7 +210,13 @@ public abstract class DisplayList { * @see #end() * @see #isValid() */ - public abstract HardwareCanvas start(int width, int height); + public HardwareCanvas start(int width, int height) { + HardwareCanvas canvas = GLES20RecordingCanvas.obtain(); + canvas.setViewport(width, height); + // The dirty rect should always be null for a display list + canvas.onPreDraw(null); + return canvas; + } /** * Ends the recording for this display list. A display list cannot be @@ -201,65 +226,46 @@ public abstract class DisplayList { * @see #start(int, int) * @see #isValid() */ - public abstract void end(); - - /** - * Clears resources held onto by this display list. After calling this method - * {@link #isValid()} will return false. - * - * @see #isValid() - * @see #reset() - */ - public abstract void clear(); - + public void end(HardwareRenderer renderer, HardwareCanvas endCanvas) { + if (!(endCanvas instanceof GLES20RecordingCanvas)) { + throw new IllegalArgumentException("Passed an invalid canvas to end!"); + } + + GLES20RecordingCanvas canvas = (GLES20RecordingCanvas) endCanvas; + canvas.onPostDraw(); + long displayListData = canvas.finishRecording(); + if (renderer != mRenderer) { + // If we are changing renderers first destroy with the old + // renderer, then set with the new one + destroyDisplayListData(); + } + mRenderer = renderer; + setDisplayListData(displayListData); + canvas.recycle(); + mValid = true; + } /** * Reset native resources. This is called when cleaning up the state of display lists * during destruction of hardware resources, to ensure that we do not hold onto * obsolete resources after related resources are gone. * - * @see #clear() - * * @hide */ - public abstract void reset(); + public void destroyDisplayListData() { + if (!mValid) return; - /** - * Sets the dirty flag. When a display list is dirty, {@link #clear()} should - * be invoked whenever possible. - * - * @see #isDirty() - * @see #clear() - * - * @hide - */ - public void markDirty() { - mDirty = true; + setDisplayListData(0); + mRenderer = null; + mValid = false; } - /** - * Removes the dirty flag. This method can be used to cancel a cleanup - * previously scheduled by setting the dirty flag. - * - * @see #isDirty() - * @see #clear() - * - * @hide - */ - protected void clearDirty() { - mDirty = false; - } - - /** - * Indicates whether the display list is dirty. - * - * @see #markDirty() - * @see #clear() - * - * @hide - */ - public boolean isDirty() { - return mDirty; + private void setDisplayListData(long newData) { + if (mRenderer != null) { + mRenderer.setDisplayListData(mNativeDisplayList, newData); + } else { + throw new IllegalStateException("Trying to set data without a renderer! data=" + newData); + } } /** @@ -268,16 +274,14 @@ public abstract class DisplayList { * * @return boolean true if the display list is able to be replayed, false otherwise. */ - public abstract boolean isValid(); + public boolean isValid() { return mValid; } - /** - * Return the amount of memory used by this display list. - * - * @return The size of this display list in bytes - * - * @hide - */ - public abstract int getSize(); + long getNativeDisplayList() { + if (!mValid) { + throw new IllegalStateException("The display list is not valid."); + } + return mNativeDisplayList; + } /////////////////////////////////////////////////////////////////////////// // DisplayList Property Setters @@ -292,7 +296,9 @@ public abstract class DisplayList { * * @hide */ - public abstract void setCaching(boolean caching); + public void setCaching(boolean caching) { + nSetCaching(mNativeDisplayList, caching); + } /** * Set whether the display list should clip itself to its bounds. This property is controlled by @@ -300,44 +306,95 @@ public abstract class DisplayList { * * @param clipToBounds true if the display list should clip to its bounds */ - public abstract void setClipToBounds(boolean clipToBounds); + public void setClipToBounds(boolean clipToBounds) { + nSetClipToBounds(mNativeDisplayList, clipToBounds); + } /** - * Set the static matrix on the display list. The specified matrix is combined with other - * transforms (such as {@link #setScaleX(float)}, {@link #setRotation(float)}, etc.) + * Set whether the display list should collect and Z order all 3d composited descendents, and + * draw them in order with the default Z=0 content. * - * @param matrix A transform matrix to apply to this display list + * @param isolatedZVolume true if the display list should collect and Z order descendents. + */ + public void setIsolatedZVolume(boolean isolatedZVolume) { + nSetIsolatedZVolume(mNativeDisplayList, isolatedZVolume); + } + + /** + * Sets whether the display list should be drawn immediately after the + * closest ancestor display list where isolateZVolume is true. If the + * display list itself satisfies this constraint, changing this attribute + * has no effect on drawing order. * - * @see #getMatrix(android.graphics.Matrix) - * @see #getMatrix() + * @param shouldProject true if the display list should be projected onto a + * containing volume. */ - public abstract void setMatrix(Matrix matrix); + public void setProjectBackwards(boolean shouldProject) { + nSetProjectBackwards(mNativeDisplayList, shouldProject); + } + + /** + * Sets whether the display list is a projection receiver - that its parent + * DisplayList should draw any descendent DisplayLists with + * ProjectBackwards=true directly on top of it. Default value is false. + */ + public void setProjectionReceiver(boolean shouldRecieve) { + nSetProjectionReceiver(mNativeDisplayList, shouldRecieve); + } /** - * Returns the static matrix set on this display list. + * Sets the outline, defining the shape that casts a shadow, and the path to + * be clipped if setClipToOutline is set. * - * @return A new {@link Matrix} instance populated with this display list's static - * matrix + * Deep copies the native path to simplify reference ownership. * - * @see #getMatrix(android.graphics.Matrix) - * @see #setMatrix(android.graphics.Matrix) + * @param outline Convex, CW Path to store in the DisplayList. May be null. + */ + public void setOutline(Path outline) { + long nativePath = (outline == null) ? 0 : outline.mNativePath; + nSetOutline(mNativeDisplayList, nativePath); + } + + /** + * Enables or disables clipping to the outline. + * + * @param clipToOutline true if clipping to the outline. + */ + public void setClipToOutline(boolean clipToOutline) { + nSetClipToOutline(mNativeDisplayList, clipToOutline); + } + + /** + * Set whether the DisplayList should cast a shadow. + * + * The shape of the shadow casting area is defined by the outline of the display list, if set + * and non-empty, otherwise it will be the bounds rect. */ - public Matrix getMatrix() { - return getMatrix(new Matrix()); + public void setCastsShadow(boolean castsShadow) { + nSetCastsShadow(mNativeDisplayList, castsShadow); } /** - * Copies this display list's static matrix into the specified matrix. + * Sets whether the DisplayList should be drawn with perspective applied from the global camera. * - * @param matrix The {@link Matrix} instance in which to copy this display - * list's static matrix. Cannot be null + * If set to true, camera distance will be ignored. Defaults to false. + */ + public void setUsesGlobalCamera(boolean usesGlobalCamera) { + nSetUsesGlobalCamera(mNativeDisplayList, usesGlobalCamera); + } + + /** + * Set the static matrix on the display list. The specified matrix is combined with other + * transforms (such as {@link #setScaleX(float)}, {@link #setRotation(float)}, etc.) * - * @return The <code>matrix</code> parameter, for convenience + * @param matrix A transform matrix to apply to this display list * + * @see #getMatrix(android.graphics.Matrix) * @see #getMatrix() - * @see #setMatrix(android.graphics.Matrix) */ - public abstract Matrix getMatrix(Matrix matrix); + public void setStaticMatrix(Matrix matrix) { + nSetStaticMatrix(mNativeDisplayList, matrix.native_instance); + } /** * Set the Animation matrix on the display list. This matrix exists if an Animation is @@ -349,7 +406,10 @@ public abstract class DisplayList { * * @hide */ - public abstract void setAnimationMatrix(Matrix matrix); + public void setAnimationMatrix(Matrix matrix) { + nSetAnimationMatrix(mNativeDisplayList, + (matrix != null) ? matrix.native_instance : 0); + } /** * Sets the translucency level for the display list. @@ -359,7 +419,9 @@ public abstract class DisplayList { * @see View#setAlpha(float) * @see #getAlpha() */ - public abstract void setAlpha(float alpha); + public void setAlpha(float alpha) { + nSetAlpha(mNativeDisplayList, alpha); + } /** * Returns the translucency level of this display list. @@ -368,7 +430,9 @@ public abstract class DisplayList { * * @see #setAlpha(float) */ - public abstract float getAlpha(); + public float getAlpha() { + return nGetAlpha(mNativeDisplayList); + } /** * Sets whether the display list renders content which overlaps. Non-overlapping rendering @@ -381,7 +445,9 @@ public abstract class DisplayList { * @see android.view.View#hasOverlappingRendering() * @see #hasOverlappingRendering() */ - public abstract void setHasOverlappingRendering(boolean hasOverlappingRendering); + public void setHasOverlappingRendering(boolean hasOverlappingRendering) { + nSetHasOverlappingRendering(mNativeDisplayList, hasOverlappingRendering); + } /** * Indicates whether the content of this display list overlaps. @@ -390,126 +456,176 @@ public abstract class DisplayList { * * @see #setHasOverlappingRendering(boolean) */ - public abstract boolean hasOverlappingRendering(); + public boolean hasOverlappingRendering() { + //noinspection SimplifiableIfStatement + return nHasOverlappingRendering(mNativeDisplayList); + } /** - * Sets the translation value for the display list on the X axis + * Sets the translation value for the display list on the X axis. * * @param translationX The X axis translation value of the display list, in pixels * * @see View#setTranslationX(float) * @see #getTranslationX() */ - public abstract void setTranslationX(float translationX); + public void setTranslationX(float translationX) { + nSetTranslationX(mNativeDisplayList, translationX); + } /** * Returns the translation value for this display list on the X axis, in pixels. * * @see #setTranslationX(float) */ - public abstract float getTranslationX(); + public float getTranslationX() { + return nGetTranslationX(mNativeDisplayList); + } /** - * Sets the translation value for the display list on the Y axis + * Sets the translation value for the display list on the Y axis. * * @param translationY The Y axis translation value of the display list, in pixels * * @see View#setTranslationY(float) * @see #getTranslationY() */ - public abstract void setTranslationY(float translationY); + public void setTranslationY(float translationY) { + nSetTranslationY(mNativeDisplayList, translationY); + } /** * Returns the translation value for this display list on the Y axis, in pixels. * * @see #setTranslationY(float) */ - public abstract float getTranslationY(); + public float getTranslationY() { + return nGetTranslationY(mNativeDisplayList); + } + + /** + * Sets the translation value for the display list on the Z axis. + * + * @see View#setTranslationZ(float) + * @see #getTranslationZ() + */ + public void setTranslationZ(float translationZ) { + nSetTranslationZ(mNativeDisplayList, translationZ); + } /** - * Sets the rotation value for the display list around the Z axis + * Returns the translation value for this display list on the Z axis. + * + * @see #setTranslationZ(float) + */ + public float getTranslationZ() { + return nGetTranslationZ(mNativeDisplayList); + } + + /** + * Sets the rotation value for the display list around the Z axis. * * @param rotation The rotation value of the display list, in degrees * * @see View#setRotation(float) * @see #getRotation() */ - public abstract void setRotation(float rotation); + public void setRotation(float rotation) { + nSetRotation(mNativeDisplayList, rotation); + } /** * Returns the rotation value for this display list around the Z axis, in degrees. * * @see #setRotation(float) */ - public abstract float getRotation(); + public float getRotation() { + return nGetRotation(mNativeDisplayList); + } /** - * Sets the rotation value for the display list around the X axis + * Sets the rotation value for the display list around the X axis. * * @param rotationX The rotation value of the display list, in degrees * * @see View#setRotationX(float) * @see #getRotationX() */ - public abstract void setRotationX(float rotationX); + public void setRotationX(float rotationX) { + nSetRotationX(mNativeDisplayList, rotationX); + } /** * Returns the rotation value for this display list around the X axis, in degrees. * * @see #setRotationX(float) */ - public abstract float getRotationX(); + public float getRotationX() { + return nGetRotationX(mNativeDisplayList); + } /** - * Sets the rotation value for the display list around the Y axis + * Sets the rotation value for the display list around the Y axis. * * @param rotationY The rotation value of the display list, in degrees * * @see View#setRotationY(float) * @see #getRotationY() */ - public abstract void setRotationY(float rotationY); + public void setRotationY(float rotationY) { + nSetRotationY(mNativeDisplayList, rotationY); + } /** * Returns the rotation value for this display list around the Y axis, in degrees. * * @see #setRotationY(float) */ - public abstract float getRotationY(); + public float getRotationY() { + return nGetRotationY(mNativeDisplayList); + } /** - * Sets the scale value for the display list on the X axis + * Sets the scale value for the display list on the X axis. * * @param scaleX The scale value of the display list * * @see View#setScaleX(float) * @see #getScaleX() */ - public abstract void setScaleX(float scaleX); + public void setScaleX(float scaleX) { + nSetScaleX(mNativeDisplayList, scaleX); + } /** * Returns the scale value for this display list on the X axis. * * @see #setScaleX(float) */ - public abstract float getScaleX(); + public float getScaleX() { + return nGetScaleX(mNativeDisplayList); + } /** - * Sets the scale value for the display list on the Y axis + * Sets the scale value for the display list on the Y axis. * * @param scaleY The scale value of the display list * * @see View#setScaleY(float) * @see #getScaleY() */ - public abstract void setScaleY(float scaleY); + public void setScaleY(float scaleY) { + nSetScaleY(mNativeDisplayList, scaleY); + } /** * Returns the scale value for this display list on the Y axis. * * @see #setScaleY(float) */ - public abstract float getScaleY(); + public float getScaleY() { + return nGetScaleY(mNativeDisplayList); + } /** * Sets all of the transform-related values of the display list @@ -525,8 +641,13 @@ public abstract class DisplayList { * * @hide */ - public abstract void setTransformationInfo(float alpha, float translationX, float translationY, - float rotation, float rotationX, float rotationY, float scaleX, float scaleY); + public void setTransformationInfo(float alpha, + float translationX, float translationY, float translationZ, + float rotation, float rotationX, float rotationY, float scaleX, float scaleY) { + nSetTransformationInfo(mNativeDisplayList, alpha, + translationX, translationY, translationZ, + rotation, rotationX, rotationY, scaleX, scaleY); + } /** * Sets the pivot value for the display list on the X axis @@ -536,14 +657,18 @@ public abstract class DisplayList { * @see View#setPivotX(float) * @see #getPivotX() */ - public abstract void setPivotX(float pivotX); + public void setPivotX(float pivotX) { + nSetPivotX(mNativeDisplayList, pivotX); + } /** * Returns the pivot value for this display list on the X axis, in pixels. * * @see #setPivotX(float) */ - public abstract float getPivotX(); + public float getPivotX() { + return nGetPivotX(mNativeDisplayList); + } /** * Sets the pivot value for the display list on the Y axis @@ -553,14 +678,18 @@ public abstract class DisplayList { * @see View#setPivotY(float) * @see #getPivotY() */ - public abstract void setPivotY(float pivotY); + public void setPivotY(float pivotY) { + nSetPivotY(mNativeDisplayList, pivotY); + } /** * Returns the pivot value for this display list on the Y axis, in pixels. * * @see #setPivotY(float) */ - public abstract float getPivotY(); + public float getPivotY() { + return nGetPivotY(mNativeDisplayList); + } /** * Sets the camera distance for the display list. Refer to @@ -572,14 +701,18 @@ public abstract class DisplayList { * @see View#setCameraDistance(float) * @see #getCameraDistance() */ - public abstract void setCameraDistance(float distance); + public void setCameraDistance(float distance) { + nSetCameraDistance(mNativeDisplayList, distance); + } /** * Returns the distance in Z of the camera of the display list. * * @see #setCameraDistance(float) */ - public abstract float getCameraDistance(); + public float getCameraDistance() { + return nGetCameraDistance(mNativeDisplayList); + } /** * Sets the left position for the display list. @@ -589,14 +722,18 @@ public abstract class DisplayList { * @see View#setLeft(int) * @see #getLeft() */ - public abstract void setLeft(int left); + public void setLeft(int left) { + nSetLeft(mNativeDisplayList, left); + } /** * Returns the left position for the display list in pixels. * * @see #setLeft(int) */ - public abstract float getLeft(); + public float getLeft() { + return nGetLeft(mNativeDisplayList); + } /** * Sets the top position for the display list. @@ -606,14 +743,18 @@ public abstract class DisplayList { * @see View#setTop(int) * @see #getTop() */ - public abstract void setTop(int top); + public void setTop(int top) { + nSetTop(mNativeDisplayList, top); + } /** * Returns the top position for the display list in pixels. * * @see #setTop(int) */ - public abstract float getTop(); + public float getTop() { + return nGetTop(mNativeDisplayList); + } /** * Sets the right position for the display list. @@ -623,14 +764,18 @@ public abstract class DisplayList { * @see View#setRight(int) * @see #getRight() */ - public abstract void setRight(int right); + public void setRight(int right) { + nSetRight(mNativeDisplayList, right); + } /** * Returns the right position for the display list in pixels. * * @see #setRight(int) */ - public abstract float getRight(); + public float getRight() { + return nGetRight(mNativeDisplayList); + } /** * Sets the bottom position for the display list. @@ -640,14 +785,18 @@ public abstract class DisplayList { * @see View#setBottom(int) * @see #getBottom() */ - public abstract void setBottom(int bottom); + public void setBottom(int bottom) { + nSetBottom(mNativeDisplayList, bottom); + } /** * Returns the bottom position for the display list in pixels. * * @see #setBottom(int) */ - public abstract float getBottom(); + public float getBottom() { + return nGetBottom(mNativeDisplayList); + } /** * Sets the left and top positions for the display list @@ -662,7 +811,9 @@ public abstract class DisplayList { * @see View#setRight(int) * @see View#setBottom(int) */ - public abstract void setLeftTopRightBottom(int left, int top, int right, int bottom); + public void setLeftTopRightBottom(int left, int top, int right, int bottom) { + nSetLeftTopRightBottom(mNativeDisplayList, left, top, right, bottom); + } /** * Offsets the left and right positions for the display list @@ -672,7 +823,9 @@ public abstract class DisplayList { * * @see View#offsetLeftAndRight(int) */ - public abstract void offsetLeftAndRight(float offset); + public void offsetLeftAndRight(float offset) { + nOffsetLeftAndRight(mNativeDisplayList, offset); + } /** * Offsets the top and bottom values for the display list @@ -682,5 +835,97 @@ public abstract class DisplayList { * * @see View#offsetTopAndBottom(int) */ - public abstract void offsetTopAndBottom(float offset); + public void offsetTopAndBottom(float offset) { + nOffsetTopAndBottom(mNativeDisplayList, offset); + } + + /** + * Outputs the display list to the log. This method exists for use by + * tools to output display lists for selected nodes to the log. + * + * @hide + */ + public void output() { + nOutput(mNativeDisplayList); + } + + /////////////////////////////////////////////////////////////////////////// + // Native methods + /////////////////////////////////////////////////////////////////////////// + + private static native long nCreate(); + private static native void nDestroyDisplayList(long displayList); + private static native void nSetDisplayListName(long displayList, String name); + + // Properties + + private static native void nOffsetTopAndBottom(long displayList, float offset); + private static native void nOffsetLeftAndRight(long displayList, float offset); + private static native void nSetLeftTopRightBottom(long displayList, int left, int top, + int right, int bottom); + private static native void nSetBottom(long displayList, int bottom); + private static native void nSetRight(long displayList, int right); + private static native void nSetTop(long displayList, int top); + private static native void nSetLeft(long displayList, int left); + private static native void nSetCameraDistance(long displayList, float distance); + private static native void nSetPivotY(long displayList, float pivotY); + private static native void nSetPivotX(long displayList, float pivotX); + private static native void nSetCaching(long displayList, boolean caching); + private static native void nSetClipToBounds(long displayList, boolean clipToBounds); + private static native void nSetProjectBackwards(long displayList, boolean shouldProject); + private static native void nSetProjectionReceiver(long displayList, boolean shouldRecieve); + private static native void nSetIsolatedZVolume(long displayList, boolean isolateZVolume); + private static native void nSetOutline(long displayList, long nativePath); + private static native void nSetClipToOutline(long displayList, boolean clipToOutline); + private static native void nSetCastsShadow(long displayList, boolean castsShadow); + private static native void nSetUsesGlobalCamera(long displayList, boolean usesGlobalCamera); + private static native void nSetAlpha(long displayList, float alpha); + private static native void nSetHasOverlappingRendering(long displayList, + boolean hasOverlappingRendering); + private static native void nSetTranslationX(long displayList, float translationX); + private static native void nSetTranslationY(long displayList, float translationY); + private static native void nSetTranslationZ(long displayList, float translationZ); + private static native void nSetRotation(long displayList, float rotation); + private static native void nSetRotationX(long displayList, float rotationX); + private static native void nSetRotationY(long displayList, float rotationY); + private static native void nSetScaleX(long displayList, float scaleX); + private static native void nSetScaleY(long displayList, float scaleY); + private static native void nSetTransformationInfo(long displayList, float alpha, + float translationX, float translationY, float translationZ, + float rotation, float rotationX, float rotationY, float scaleX, float scaleY); + private static native void nSetStaticMatrix(long displayList, long nativeMatrix); + private static native void nSetAnimationMatrix(long displayList, long animationMatrix); + + private static native boolean nHasOverlappingRendering(long displayList); + private static native float nGetAlpha(long displayList); + private static native float nGetLeft(long displayList); + private static native float nGetTop(long displayList); + private static native float nGetRight(long displayList); + private static native float nGetBottom(long displayList); + private static native float nGetCameraDistance(long displayList); + private static native float nGetScaleX(long displayList); + private static native float nGetScaleY(long displayList); + private static native float nGetTranslationX(long displayList); + private static native float nGetTranslationY(long displayList); + private static native float nGetTranslationZ(long displayList); + private static native float nGetRotation(long displayList); + private static native float nGetRotationX(long displayList); + private static native float nGetRotationY(long displayList); + private static native float nGetPivotX(long displayList); + private static native float nGetPivotY(long displayList); + private static native void nOutput(long displayList); + + /////////////////////////////////////////////////////////////////////////// + // Finalization + /////////////////////////////////////////////////////////////////////////// + + @Override + protected void finalize() throws Throwable { + try { + destroyDisplayListData(); + nDestroyDisplayList(mNativeDisplayList); + } finally { + super.finalize(); + } + } } diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java index d533060..6c6fc9b 100644 --- a/core/java/android/view/GLES20Canvas.java +++ b/core/java/android/view/GLES20Canvas.java @@ -18,7 +18,6 @@ package android.view; import android.graphics.Bitmap; import android.graphics.Canvas; -import android.graphics.ColorFilter; import android.graphics.DrawFilter; import android.graphics.Matrix; import android.graphics.NinePatch; @@ -31,7 +30,6 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; import android.graphics.Shader; -import android.graphics.SurfaceTexture; import android.graphics.TemporaryBuffer; import android.text.GraphicsOperations; import android.text.SpannableString; @@ -46,10 +44,9 @@ class GLES20Canvas extends HardwareCanvas { private static final int MODIFIER_NONE = 0; private static final int MODIFIER_SHADOW = 1; private static final int MODIFIER_SHADER = 2; - private static final int MODIFIER_COLOR_FILTER = 4; private final boolean mOpaque; - private long mRenderer; + protected long mRenderer; // The native renderer will be destroyed when this object dies. // DO NOT overwrite this reference once it is set. @@ -88,15 +85,6 @@ class GLES20Canvas extends HardwareCanvas { GLES20Canvas(boolean translucent) { this(false, translucent); } - - /** - * Creates a canvas to render into an FBO. - */ - GLES20Canvas(long layer, boolean translucent) { - mOpaque = !translucent; - mRenderer = nCreateLayerRenderer(layer); - setupFinalizer(); - } protected GLES20Canvas(boolean record, boolean translucent) { mOpaque = !translucent; @@ -118,12 +106,7 @@ class GLES20Canvas extends HardwareCanvas { } } - protected void resetDisplayListRenderer() { - nResetDisplayListRenderer(mRenderer); - } - private static native long nCreateRenderer(); - private static native long nCreateLayerRenderer(long layer); private static native long nCreateDisplayListRenderer(); private static native void nResetDisplayListRenderer(long renderer); private static native void nDestroyRenderer(long renderer); @@ -145,13 +128,11 @@ class GLES20Canvas extends HardwareCanvas { } } - @Override - public void setName(String name) { - super.setName(name); - nSetName(mRenderer, name); + public static void setProperty(String name, String value) { + nSetProperty(name, value); } - private static native void nSetName(long renderer, String name); + private static native void nSetProperty(String name, String value); /////////////////////////////////////////////////////////////////////////// // Hardware layers @@ -159,12 +140,12 @@ class GLES20Canvas extends HardwareCanvas { @Override void pushLayerUpdate(HardwareLayer layer) { - nPushLayerUpdate(mRenderer, ((GLES20RenderLayer) layer).mLayer); + nPushLayerUpdate(mRenderer, layer.getLayer()); } @Override void cancelLayerUpdate(HardwareLayer layer) { - nCancelLayerUpdate(mRenderer, ((GLES20RenderLayer) layer).mLayer); + nCancelLayerUpdate(mRenderer, layer.getLayer()); } @Override @@ -177,22 +158,7 @@ class GLES20Canvas extends HardwareCanvas { nClearLayerUpdates(mRenderer); } - static native long nCreateTextureLayer(boolean opaque, int[] layerInfo); - static native long nCreateLayer(int width, int height, boolean isOpaque, int[] layerInfo); - static native boolean nResizeLayer(long layerId, int width, int height, int[] layerInfo); - static native void nSetOpaqueLayer(long layerId, boolean isOpaque); - static native void nSetLayerPaint(long layerId, long nativePaint); - static native void nSetLayerColorFilter(long layerId, long nativeColorFilter); - static native void nUpdateTextureLayer(long layerId, int width, int height, boolean opaque, - SurfaceTexture surface); - static native void nClearLayerTexture(long layerId); - static native void nSetTextureLayerTransform(long layerId, long matrix); - static native void nDestroyLayer(long layerId); - static native void nDestroyLayerDeferred(long layerId); - static native void nUpdateRenderLayer(long layerId, long renderer, long displayList, - int left, int top, int right, int bottom); static native boolean nCopyLayer(long layerId, long bitmap); - private static native void nClearLayerUpdates(long renderer); private static native void nFlushLayerUpdates(long renderer); private static native void nPushLayerUpdate(long renderer, long layer); @@ -286,18 +252,6 @@ class GLES20Canvas extends HardwareCanvas { private static native int nGetStencilSize(); - void setCountOverdrawEnabled(boolean enabled) { - nSetCountOverdrawEnabled(mRenderer, enabled); - } - - static native void nSetCountOverdrawEnabled(long renderer, boolean enabled); - - float getOverdraw() { - return nGetOverdraw(mRenderer); - } - - static native float nGetOverdraw(long renderer); - /////////////////////////////////////////////////////////////////////////// // Functor /////////////////////////////////////////////////////////////////////////// @@ -402,22 +356,11 @@ class GLES20Canvas extends HardwareCanvas { // Display list /////////////////////////////////////////////////////////////////////////// - long getDisplayList(long displayList) { - return nGetDisplayList(mRenderer, displayList); - } - - private static native long nGetDisplayList(long renderer, long displayList); - - @Override - void outputDisplayList(DisplayList displayList) { - nOutputDisplayList(mRenderer, ((GLES20DisplayList) displayList).getNativeDisplayList()); - } - - private static native void nOutputDisplayList(long renderer, long displayList); + protected static native long nFinishRecording(long renderer); @Override public int drawDisplayList(DisplayList displayList, Rect dirty, int flags) { - return nDrawDisplayList(mRenderer, ((GLES20DisplayList) displayList).getNativeDisplayList(), + return nDrawDisplayList(mRenderer, displayList.getNativeDisplayList(), dirty, flags); } @@ -430,9 +373,7 @@ class GLES20Canvas extends HardwareCanvas { void drawHardwareLayer(HardwareLayer layer, float x, float y, Paint paint) { layer.setLayerPaint(paint); - - final GLES20Layer glLayer = (GLES20Layer) layer; - nDrawLayer(mRenderer, glLayer.getLayer(), x, y); + nDrawLayer(mRenderer, layer.getLayer(), x, y); } private static native void nDrawLayer(long renderer, long layer, float x, float y); @@ -648,15 +589,8 @@ class GLES20Canvas extends HardwareCanvas { return saveLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, paint, saveFlags); } - int count; - int modifier = paint != null ? setupColorFilter(paint) : MODIFIER_NONE; - try { - final long nativePaint = paint == null ? 0 : paint.mNativePaint; - count = nSaveLayer(mRenderer, nativePaint, saveFlags); - } finally { - if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier); - } - return count; + final long nativePaint = paint == null ? 0 : paint.mNativePaint; + return nSaveLayer(mRenderer, nativePaint, saveFlags); } private static native int nSaveLayer(long renderer, long paint, int saveFlags); @@ -665,15 +599,8 @@ class GLES20Canvas extends HardwareCanvas { public int saveLayer(float left, float top, float right, float bottom, Paint paint, int saveFlags) { if (left < right && top < bottom) { - int count; - int modifier = paint != null ? setupColorFilter(paint) : MODIFIER_NONE; - try { - final long nativePaint = paint == null ? 0 : paint.mNativePaint; - count = nSaveLayer(mRenderer, left, top, right, bottom, nativePaint, saveFlags); - } finally { - if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier); - } - return count; + final long nativePaint = paint == null ? 0 : paint.mNativePaint; + return nSaveLayer(mRenderer, left, top, right, bottom, nativePaint, saveFlags); } return save(saveFlags); } @@ -755,7 +682,7 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint) { - int modifiers = setupModifiers(paint, MODIFIER_COLOR_FILTER | MODIFIER_SHADER); + int modifiers = setupModifiers(paint, MODIFIER_SHADER); try { nDrawArc(mRenderer, oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle, useCenter, paint.mNativePaint); @@ -778,14 +705,9 @@ class GLES20Canvas extends HardwareCanvas { Bitmap bitmap = patch.getBitmap(); throwIfCannotDraw(bitmap); // Shaders are ignored when drawing patches - int modifier = paint != null ? setupColorFilter(paint) : MODIFIER_NONE; - try { - final long nativePaint = paint == null ? 0 : paint.mNativePaint; - nDrawPatch(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, patch.mNativeChunk, - dst.left, dst.top, dst.right, dst.bottom, nativePaint); - } finally { - if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier); - } + final long nativePaint = paint == null ? 0 : paint.mNativePaint; + nDrawPatch(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, patch.mNativeChunk, + dst.left, dst.top, dst.right, dst.bottom, nativePaint); } @Override @@ -793,14 +715,9 @@ class GLES20Canvas extends HardwareCanvas { Bitmap bitmap = patch.getBitmap(); throwIfCannotDraw(bitmap); // Shaders are ignored when drawing patches - int modifier = paint != null ? setupColorFilter(paint) : MODIFIER_NONE; - try { - final long nativePaint = paint == null ? 0 : paint.mNativePaint; - nDrawPatch(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, patch.mNativeChunk, - dst.left, dst.top, dst.right, dst.bottom, nativePaint); - } finally { - if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier); - } + final long nativePaint = paint == null ? 0 : paint.mNativePaint; + nDrawPatch(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, patch.mNativeChunk, + dst.left, dst.top, dst.right, dst.bottom, nativePaint); } private static native void nDrawPatch(long renderer, long bitmap, byte[] buffer, long chunk, @@ -921,14 +838,9 @@ class GLES20Canvas extends HardwareCanvas { } // Shaders are ignored when drawing bitmaps - int modifier = paint != null ? setupColorFilter(paint) : MODIFIER_NONE; - try { - final long nativePaint = paint == null ? 0 : paint.mNativePaint; - nDrawBitmap(mRenderer, colors, offset, stride, x, y, - width, height, hasAlpha, nativePaint); - } finally { - if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier); - } + final long nativePaint = paint == null ? 0 : paint.mNativePaint; + nDrawBitmap(mRenderer, colors, offset, stride, x, y, + width, height, hasAlpha, nativePaint); } private static native void nDrawBitmap(long renderer, int[] colors, int offset, int stride, @@ -976,7 +888,7 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawCircle(float cx, float cy, float radius, Paint paint) { - int modifiers = setupModifiers(paint, MODIFIER_COLOR_FILTER | MODIFIER_SHADER); + int modifiers = setupModifiers(paint, MODIFIER_SHADER); try { nDrawCircle(mRenderer, cx, cy, radius, paint.mNativePaint); } finally { @@ -1016,7 +928,7 @@ class GLES20Canvas extends HardwareCanvas { if ((offset | count) < 0 || offset + count > pts.length) { throw new IllegalArgumentException("The lines array must contain 4 elements per line."); } - int modifiers = setupModifiers(paint, MODIFIER_COLOR_FILTER | MODIFIER_SHADER); + int modifiers = setupModifiers(paint, MODIFIER_SHADER); try { nDrawLines(mRenderer, pts, offset, count, paint.mNativePaint); } finally { @@ -1034,7 +946,7 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawOval(RectF oval, Paint paint) { - int modifiers = setupModifiers(paint, MODIFIER_COLOR_FILTER | MODIFIER_SHADER); + int modifiers = setupModifiers(paint, MODIFIER_SHADER); try { nDrawOval(mRenderer, oval.left, oval.top, oval.right, oval.bottom, paint.mNativePaint); } finally { @@ -1054,7 +966,7 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawPath(Path path, Paint paint) { - int modifiers = setupModifiers(paint, MODIFIER_COLOR_FILTER | MODIFIER_SHADER); + int modifiers = setupModifiers(paint, MODIFIER_SHADER); try { if (path.isSimplePath) { if (path.rects != null) { @@ -1072,7 +984,7 @@ class GLES20Canvas extends HardwareCanvas { private static native void nDrawRects(long renderer, long region, long paint); void drawRects(float[] rects, int count, Paint paint) { - int modifiers = setupModifiers(paint, MODIFIER_COLOR_FILTER | MODIFIER_SHADER); + int modifiers = setupModifiers(paint, MODIFIER_SHADER); try { nDrawRects(mRenderer, rects, count, paint.mNativePaint); } finally { @@ -1139,7 +1051,7 @@ class GLES20Canvas extends HardwareCanvas { public void drawPoints(float[] pts, int offset, int count, Paint paint) { if (count < 2) return; - int modifiers = setupModifiers(paint, MODIFIER_COLOR_FILTER | MODIFIER_SHADER); + int modifiers = setupModifiers(paint, MODIFIER_SHADER); try { nDrawPoints(mRenderer, pts, offset, count, paint.mNativePaint); } finally { @@ -1189,7 +1101,7 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawRect(float left, float top, float right, float bottom, Paint paint) { if (left == right || top == bottom) return; - int modifiers = setupModifiers(paint, MODIFIER_COLOR_FILTER | MODIFIER_SHADER); + int modifiers = setupModifiers(paint, MODIFIER_SHADER); try { nDrawRect(mRenderer, left, top, right, bottom, paint.mNativePaint); } finally { @@ -1217,7 +1129,7 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawRoundRect(RectF rect, float rx, float ry, Paint paint) { - int modifiers = setupModifiers(paint, MODIFIER_COLOR_FILTER | MODIFIER_SHADER); + int modifiers = setupModifiers(paint, MODIFIER_SHADER); try { nDrawRoundRect(mRenderer, rect.left, rect.top, rect.right, rect.bottom, rx, ry, paint.mNativePaint); @@ -1397,12 +1309,6 @@ class GLES20Canvas extends HardwareCanvas { private int setupModifiers(Bitmap b, Paint paint) { if (b.getConfig() != Bitmap.Config.ALPHA_8) { - final ColorFilter filter = paint.getColorFilter(); - if (filter != null) { - nSetupColorFilter(mRenderer, filter.nativeColorFilter); - return MODIFIER_COLOR_FILTER; - } - return MODIFIER_NONE; } else { return setupModifiers(paint); @@ -1424,12 +1330,6 @@ class GLES20Canvas extends HardwareCanvas { modifiers |= MODIFIER_SHADER; } - final ColorFilter filter = paint.getColorFilter(); - if (filter != null) { - nSetupColorFilter(mRenderer, filter.nativeColorFilter); - modifiers |= MODIFIER_COLOR_FILTER; - } - return modifiers; } @@ -1448,26 +1348,10 @@ class GLES20Canvas extends HardwareCanvas { modifiers |= MODIFIER_SHADER; } - final ColorFilter filter = paint.getColorFilter(); - if (filter != null && (flags & MODIFIER_COLOR_FILTER) != 0) { - nSetupColorFilter(mRenderer, filter.nativeColorFilter); - modifiers |= MODIFIER_COLOR_FILTER; - } - return modifiers; } - private int setupColorFilter(Paint paint) { - final ColorFilter filter = paint.getColorFilter(); - if (filter != null) { - nSetupColorFilter(mRenderer, filter.nativeColorFilter); - return MODIFIER_COLOR_FILTER; - } - return MODIFIER_NONE; - } - private static native void nSetupShader(long renderer, long shader); - private static native void nSetupColorFilter(long renderer, long colorFilter); private static native void nSetupShadow(long renderer, float radius, float dx, float dy, int color); diff --git a/core/java/android/view/GLES20DisplayList.java b/core/java/android/view/GLES20DisplayList.java deleted file mode 100644 index 7f8b3bd..0000000 --- a/core/java/android/view/GLES20DisplayList.java +++ /dev/null @@ -1,511 +0,0 @@ -/* - * Copyright (C) 2010 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.view; - -import android.graphics.Matrix; - -import java.util.ArrayList; - -/** - * An implementation of display list for OpenGL ES 2.0. - */ -class GLES20DisplayList extends DisplayList { - private ArrayList<DisplayList> mChildDisplayLists; - - private GLES20RecordingCanvas mCanvas; - private boolean mValid; - - // Used for debugging - private final String mName; - - // The native display list will be destroyed when this object dies. - // DO NOT overwrite this reference once it is set. - private DisplayListFinalizer mFinalizer; - - GLES20DisplayList(String name) { - mName = name; - } - - boolean hasNativeDisplayList() { - return mValid && mFinalizer != null; - } - - long getNativeDisplayList() { - if (!mValid || mFinalizer == null) { - throw new IllegalStateException("The display list is not valid."); - } - return mFinalizer.mNativeDisplayList; - } - - @Override - public HardwareCanvas start(int width, int height) { - if (mCanvas != null) { - throw new IllegalStateException("Recording has already started"); - } - - mValid = false; - mCanvas = GLES20RecordingCanvas.obtain(this); - mCanvas.start(); - - mCanvas.setViewport(width, height); - // The dirty rect should always be null for a display list - mCanvas.onPreDraw(null); - - return mCanvas; - } - @Override - public void clear() { - clearDirty(); - - if (mCanvas != null) { - mCanvas.recycle(); - mCanvas = null; - } - mValid = false; - - clearReferences(); - } - - void clearReferences() { - if (mChildDisplayLists != null) mChildDisplayLists.clear(); - } - - ArrayList<DisplayList> getChildDisplayLists() { - if (mChildDisplayLists == null) mChildDisplayLists = new ArrayList<DisplayList>(); - return mChildDisplayLists; - } - - @Override - public void reset() { - if (hasNativeDisplayList()) { - nReset(mFinalizer.mNativeDisplayList); - } - clear(); - } - - @Override - public boolean isValid() { - return mValid; - } - - @Override - public void end() { - if (mCanvas != null) { - mCanvas.onPostDraw(); - if (mFinalizer != null) { - mCanvas.end(mFinalizer.mNativeDisplayList); - } else { - mFinalizer = new DisplayListFinalizer(mCanvas.end(0)); - nSetDisplayListName(mFinalizer.mNativeDisplayList, mName); - } - mCanvas.recycle(); - mCanvas = null; - mValid = true; - } - } - - @Override - public int getSize() { - if (mFinalizer == null) return 0; - return nGetDisplayListSize(mFinalizer.mNativeDisplayList); - } - - private static native void nDestroyDisplayList(long displayList); - private static native int nGetDisplayListSize(long displayList); - private static native void nSetDisplayListName(long displayList, String name); - - /////////////////////////////////////////////////////////////////////////// - // Native View Properties - /////////////////////////////////////////////////////////////////////////// - - @Override - public void setCaching(boolean caching) { - if (hasNativeDisplayList()) { - nSetCaching(mFinalizer.mNativeDisplayList, caching); - } - } - - @Override - public void setClipToBounds(boolean clipToBounds) { - if (hasNativeDisplayList()) { - nSetClipToBounds(mFinalizer.mNativeDisplayList, clipToBounds); - } - } - - @Override - public void setMatrix(Matrix matrix) { - if (hasNativeDisplayList()) { - nSetStaticMatrix(mFinalizer.mNativeDisplayList, matrix.native_instance); - } - } - - @Override - public Matrix getMatrix(Matrix matrix) { - if (hasNativeDisplayList()) { - nGetMatrix(mFinalizer.mNativeDisplayList, matrix.native_instance); - } - return matrix; - } - - @Override - public void setAnimationMatrix(Matrix matrix) { - if (hasNativeDisplayList()) { - nSetAnimationMatrix(mFinalizer.mNativeDisplayList, - (matrix != null) ? matrix.native_instance : 0); - } - } - - @Override - public void setAlpha(float alpha) { - if (hasNativeDisplayList()) { - nSetAlpha(mFinalizer.mNativeDisplayList, alpha); - } - } - - @Override - public float getAlpha() { - if (hasNativeDisplayList()) { - return nGetAlpha(mFinalizer.mNativeDisplayList); - } - return 1.0f; - } - - @Override - public void setHasOverlappingRendering(boolean hasOverlappingRendering) { - if (hasNativeDisplayList()) { - nSetHasOverlappingRendering(mFinalizer.mNativeDisplayList, hasOverlappingRendering); - } - } - - @Override - public boolean hasOverlappingRendering() { - //noinspection SimplifiableIfStatement - if (hasNativeDisplayList()) { - return nHasOverlappingRendering(mFinalizer.mNativeDisplayList); - } - return true; - } - - @Override - public void setTranslationX(float translationX) { - if (hasNativeDisplayList()) { - nSetTranslationX(mFinalizer.mNativeDisplayList, translationX); - } - } - - @Override - public float getTranslationX() { - if (hasNativeDisplayList()) { - return nGetTranslationX(mFinalizer.mNativeDisplayList); - } - return 0.0f; - } - - @Override - public void setTranslationY(float translationY) { - if (hasNativeDisplayList()) { - nSetTranslationY(mFinalizer.mNativeDisplayList, translationY); - } - } - - @Override - public float getTranslationY() { - if (hasNativeDisplayList()) { - return nGetTranslationY(mFinalizer.mNativeDisplayList); - } - return 0.0f; - } - - @Override - public void setRotation(float rotation) { - if (hasNativeDisplayList()) { - nSetRotation(mFinalizer.mNativeDisplayList, rotation); - } - } - - @Override - public float getRotation() { - if (hasNativeDisplayList()) { - return nGetRotation(mFinalizer.mNativeDisplayList); - } - return 0.0f; - } - - @Override - public void setRotationX(float rotationX) { - if (hasNativeDisplayList()) { - nSetRotationX(mFinalizer.mNativeDisplayList, rotationX); - } - } - - @Override - public float getRotationX() { - if (hasNativeDisplayList()) { - return nGetRotationX(mFinalizer.mNativeDisplayList); - } - return 0.0f; - } - - @Override - public void setRotationY(float rotationY) { - if (hasNativeDisplayList()) { - nSetRotationY(mFinalizer.mNativeDisplayList, rotationY); - } - } - - @Override - public float getRotationY() { - if (hasNativeDisplayList()) { - return nGetRotationY(mFinalizer.mNativeDisplayList); - } - return 0.0f; - } - - @Override - public void setScaleX(float scaleX) { - if (hasNativeDisplayList()) { - nSetScaleX(mFinalizer.mNativeDisplayList, scaleX); - } - } - - @Override - public float getScaleX() { - if (hasNativeDisplayList()) { - return nGetScaleX(mFinalizer.mNativeDisplayList); - } - return 1.0f; - } - - @Override - public void setScaleY(float scaleY) { - if (hasNativeDisplayList()) { - nSetScaleY(mFinalizer.mNativeDisplayList, scaleY); - } - } - - @Override - public float getScaleY() { - if (hasNativeDisplayList()) { - return nGetScaleY(mFinalizer.mNativeDisplayList); - } - return 1.0f; - } - - @Override - public void setTransformationInfo(float alpha, float translationX, float translationY, - float rotation, float rotationX, float rotationY, float scaleX, float scaleY) { - if (hasNativeDisplayList()) { - nSetTransformationInfo(mFinalizer.mNativeDisplayList, alpha, translationX, translationY, - rotation, rotationX, rotationY, scaleX, scaleY); - } - } - - @Override - public void setPivotX(float pivotX) { - if (hasNativeDisplayList()) { - nSetPivotX(mFinalizer.mNativeDisplayList, pivotX); - } - } - - @Override - public float getPivotX() { - if (hasNativeDisplayList()) { - return nGetPivotX(mFinalizer.mNativeDisplayList); - } - return 0.0f; - } - - @Override - public void setPivotY(float pivotY) { - if (hasNativeDisplayList()) { - nSetPivotY(mFinalizer.mNativeDisplayList, pivotY); - } - } - - @Override - public float getPivotY() { - if (hasNativeDisplayList()) { - return nGetPivotY(mFinalizer.mNativeDisplayList); - } - return 0.0f; - } - - @Override - public void setCameraDistance(float distance) { - if (hasNativeDisplayList()) { - nSetCameraDistance(mFinalizer.mNativeDisplayList, distance); - } - } - - @Override - public float getCameraDistance() { - if (hasNativeDisplayList()) { - return nGetCameraDistance(mFinalizer.mNativeDisplayList); - } - return 0.0f; - } - - @Override - public void setLeft(int left) { - if (hasNativeDisplayList()) { - nSetLeft(mFinalizer.mNativeDisplayList, left); - } - } - - @Override - public float getLeft() { - if (hasNativeDisplayList()) { - return nGetLeft(mFinalizer.mNativeDisplayList); - } - return 0.0f; - } - - @Override - public void setTop(int top) { - if (hasNativeDisplayList()) { - nSetTop(mFinalizer.mNativeDisplayList, top); - } - } - - @Override - public float getTop() { - if (hasNativeDisplayList()) { - return nGetTop(mFinalizer.mNativeDisplayList); - } - return 0.0f; - } - - @Override - public void setRight(int right) { - if (hasNativeDisplayList()) { - nSetRight(mFinalizer.mNativeDisplayList, right); - } - } - - @Override - public float getRight() { - if (hasNativeDisplayList()) { - return nGetRight(mFinalizer.mNativeDisplayList); - } - return 0.0f; - } - - @Override - public void setBottom(int bottom) { - if (hasNativeDisplayList()) { - nSetBottom(mFinalizer.mNativeDisplayList, bottom); - } - } - - @Override - public float getBottom() { - if (hasNativeDisplayList()) { - return nGetBottom(mFinalizer.mNativeDisplayList); - } - return 0.0f; - } - - @Override - public void setLeftTopRightBottom(int left, int top, int right, int bottom) { - if (hasNativeDisplayList()) { - nSetLeftTopRightBottom(mFinalizer.mNativeDisplayList, left, top, right, bottom); - } - } - - @Override - public void offsetLeftAndRight(float offset) { - if (hasNativeDisplayList()) { - nOffsetLeftAndRight(mFinalizer.mNativeDisplayList, offset); - } - } - - @Override - public void offsetTopAndBottom(float offset) { - if (hasNativeDisplayList()) { - nOffsetTopAndBottom(mFinalizer.mNativeDisplayList, offset); - } - } - - private static native void nReset(long displayList); - private static native void nOffsetTopAndBottom(long displayList, float offset); - private static native void nOffsetLeftAndRight(long displayList, float offset); - private static native void nSetLeftTopRightBottom(long displayList, int left, int top, - int right, int bottom); - private static native void nSetBottom(long displayList, int bottom); - private static native void nSetRight(long displayList, int right); - private static native void nSetTop(long displayList, int top); - private static native void nSetLeft(long displayList, int left); - private static native void nSetCameraDistance(long displayList, float distance); - private static native void nSetPivotY(long displayList, float pivotY); - private static native void nSetPivotX(long displayList, float pivotX); - private static native void nSetCaching(long displayList, boolean caching); - private static native void nSetClipToBounds(long displayList, boolean clipToBounds); - private static native void nSetAlpha(long displayList, float alpha); - private static native void nSetHasOverlappingRendering(long displayList, - boolean hasOverlappingRendering); - private static native void nSetTranslationX(long displayList, float translationX); - private static native void nSetTranslationY(long displayList, float translationY); - private static native void nSetRotation(long displayList, float rotation); - private static native void nSetRotationX(long displayList, float rotationX); - private static native void nSetRotationY(long displayList, float rotationY); - private static native void nSetScaleX(long displayList, float scaleX); - private static native void nSetScaleY(long displayList, float scaleY); - private static native void nSetTransformationInfo(long displayList, float alpha, - float translationX, float translationY, float rotation, float rotationX, - float rotationY, float scaleX, float scaleY); - private static native void nSetStaticMatrix(long displayList, long nativeMatrix); - private static native void nSetAnimationMatrix(long displayList, long animationMatrix); - - private static native boolean nHasOverlappingRendering(long displayList); - private static native void nGetMatrix(long displayList, long matrix); - private static native float nGetAlpha(long displayList); - private static native float nGetLeft(long displayList); - private static native float nGetTop(long displayList); - private static native float nGetRight(long displayList); - private static native float nGetBottom(long displayList); - private static native float nGetCameraDistance(long displayList); - private static native float nGetScaleX(long displayList); - private static native float nGetScaleY(long displayList); - private static native float nGetTranslationX(long displayList); - private static native float nGetTranslationY(long displayList); - private static native float nGetRotation(long displayList); - private static native float nGetRotationX(long displayList); - private static native float nGetRotationY(long displayList); - private static native float nGetPivotX(long displayList); - private static native float nGetPivotY(long displayList); - - /////////////////////////////////////////////////////////////////////////// - // Finalization - /////////////////////////////////////////////////////////////////////////// - - private static class DisplayListFinalizer { - final long mNativeDisplayList; - - public DisplayListFinalizer(long nativeDisplayList) { - mNativeDisplayList = nativeDisplayList; - } - - @Override - protected void finalize() throws Throwable { - try { - nDestroyDisplayList(mNativeDisplayList); - } finally { - super.finalize(); - } - } - } -} diff --git a/core/java/android/view/GLES20Layer.java b/core/java/android/view/GLES20Layer.java deleted file mode 100644 index 37154eb..0000000 --- a/core/java/android/view/GLES20Layer.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -package android.view; - -import android.graphics.Bitmap; -import android.graphics.Paint; - -/** - * An OpenGL ES 2.0 implementation of {@link HardwareLayer}. - */ -abstract class GLES20Layer extends HardwareLayer { - long mLayer; - Finalizer mFinalizer; - - GLES20Layer() { - } - - GLES20Layer(int width, int height, boolean opaque) { - super(width, height, opaque); - } - - /** - * Returns the native layer object used to render this layer. - * - * @return A pointer to the native layer object, or 0 if the object is NULL - */ - public long getLayer() { - return mLayer; - } - - @Override - void setLayerPaint(Paint paint) { - if (paint != null) { - GLES20Canvas.nSetLayerPaint(mLayer, paint.mNativePaint); - GLES20Canvas.nSetLayerColorFilter(mLayer, paint.getColorFilter() != null ? - paint.getColorFilter().nativeColorFilter : 0); - } - } - - @Override - public boolean copyInto(Bitmap bitmap) { - return GLES20Canvas.nCopyLayer(mLayer, bitmap.mNativeBitmap); - } - - @Override - public void destroy() { - if (mDisplayList != null) { - mDisplayList.reset(); - } - if (mFinalizer != null) { - mFinalizer.destroy(); - mFinalizer = null; - } - mLayer = 0; - } - - @Override - void clearStorage() { - if (mLayer != 0) GLES20Canvas.nClearLayerTexture(mLayer); - } - - static class Finalizer { - private long mLayerId; - - public Finalizer(long layerId) { - mLayerId = layerId; - } - - @Override - protected void finalize() throws Throwable { - try { - if (mLayerId != 0) { - GLES20Canvas.nDestroyLayerDeferred(mLayerId); - } - } finally { - super.finalize(); - } - } - - void destroy() { - GLES20Canvas.nDestroyLayer(mLayerId); - mLayerId = 0; - } - } -} diff --git a/core/java/android/view/GLES20RecordingCanvas.java b/core/java/android/view/GLES20RecordingCanvas.java index e3e1c76..2b29e5c 100644 --- a/core/java/android/view/GLES20RecordingCanvas.java +++ b/core/java/android/view/GLES20RecordingCanvas.java @@ -16,7 +16,6 @@ package android.view; -import android.graphics.Rect; import android.util.Pools.SynchronizedPool; /** @@ -33,39 +32,23 @@ class GLES20RecordingCanvas extends GLES20Canvas { private static final SynchronizedPool<GLES20RecordingCanvas> sPool = new SynchronizedPool<GLES20RecordingCanvas>(POOL_LIMIT); - private GLES20DisplayList mDisplayList; - private GLES20RecordingCanvas() { super(true, true); } - static GLES20RecordingCanvas obtain(GLES20DisplayList displayList) { + static GLES20RecordingCanvas obtain() { GLES20RecordingCanvas canvas = sPool.acquire(); if (canvas == null) { canvas = new GLES20RecordingCanvas(); } - canvas.mDisplayList = displayList; return canvas; } void recycle() { - mDisplayList = null; - resetDisplayListRenderer(); sPool.release(this); } - void start() { - mDisplayList.clearReferences(); - } - - long end(long nativeDisplayList) { - return getDisplayList(nativeDisplayList); - } - - @Override - public int drawDisplayList(DisplayList displayList, Rect dirty, int flags) { - int status = super.drawDisplayList(displayList, dirty, flags); - mDisplayList.getChildDisplayLists().add(displayList); - return status; + long finishRecording() { + return nFinishRecording(mRenderer); } } diff --git a/core/java/android/view/GLES20RenderLayer.java b/core/java/android/view/GLES20RenderLayer.java deleted file mode 100644 index 68ba77c..0000000 --- a/core/java/android/view/GLES20RenderLayer.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view; - -import android.graphics.Canvas; -import android.graphics.Matrix; -import android.graphics.Rect; - -/** - * An OpenGL ES 2.0 implementation of {@link HardwareLayer}. This - * implementation can be used a rendering target. It generates a - * {@link Canvas} that can be used to render into an FBO using OpenGL. - */ -class GLES20RenderLayer extends GLES20Layer { - private int mLayerWidth; - private int mLayerHeight; - - private final GLES20Canvas mCanvas; - - GLES20RenderLayer(int width, int height, boolean isOpaque) { - super(width, height, isOpaque); - - int[] layerInfo = new int[2]; - mLayer = GLES20Canvas.nCreateLayer(width, height, isOpaque, layerInfo); - if (mLayer != 0) { - mLayerWidth = layerInfo[0]; - mLayerHeight = layerInfo[1]; - - mCanvas = new GLES20Canvas(mLayer, !isOpaque); - mFinalizer = new Finalizer(mLayer); - } else { - mCanvas = null; - mFinalizer = null; - } - } - - @Override - boolean isValid() { - return mLayer != 0 && mLayerWidth > 0 && mLayerHeight > 0; - } - - @Override - boolean resize(int width, int height) { - if (!isValid() || width <= 0 || height <= 0) return false; - - mWidth = width; - mHeight = height; - - if (width != mLayerWidth || height != mLayerHeight) { - int[] layerInfo = new int[2]; - - if (GLES20Canvas.nResizeLayer(mLayer, width, height, layerInfo)) { - mLayerWidth = layerInfo[0]; - mLayerHeight = layerInfo[1]; - } else { - // Failure: not enough GPU resources for requested size - mLayer = 0; - mLayerWidth = 0; - mLayerHeight = 0; - } - } - return isValid(); - } - - @Override - void setOpaque(boolean isOpaque) { - mOpaque = isOpaque; - GLES20Canvas.nSetOpaqueLayer(mLayer, isOpaque); - } - - @Override - HardwareCanvas getCanvas() { - return mCanvas; - } - - @Override - void end(Canvas currentCanvas) { - HardwareCanvas canvas = getCanvas(); - if (canvas != null) { - canvas.onPostDraw(); - } - if (currentCanvas instanceof GLES20Canvas) { - ((GLES20Canvas) currentCanvas).resume(); - } - } - - @Override - HardwareCanvas start(Canvas currentCanvas) { - return start(currentCanvas, null); - } - - @Override - HardwareCanvas start(Canvas currentCanvas, Rect dirty) { - if (currentCanvas instanceof GLES20Canvas) { - ((GLES20Canvas) currentCanvas).interrupt(); - } - HardwareCanvas canvas = getCanvas(); - canvas.setViewport(mWidth, mHeight); - canvas.onPreDraw(dirty); - return canvas; - } - - /** - * Ignored - */ - @Override - void setTransform(Matrix matrix) { - } - - @Override - void redrawLater(DisplayList displayList, Rect dirtyRect) { - GLES20Canvas.nUpdateRenderLayer(mLayer, mCanvas.getRenderer(), - ((GLES20DisplayList) displayList).getNativeDisplayList(), - dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom); - } -} diff --git a/core/java/android/view/GLES20TextureLayer.java b/core/java/android/view/GLES20TextureLayer.java deleted file mode 100644 index bb5a6eb..0000000 --- a/core/java/android/view/GLES20TextureLayer.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view; - -import android.graphics.Canvas; -import android.graphics.Matrix; -import android.graphics.Rect; -import android.graphics.SurfaceTexture; - -/** - * An OpenGL ES 2.0 implementation of {@link HardwareLayer}. This - * implementation can be used as a texture. Rendering into this - * layer is not controlled by a {@link HardwareCanvas}. - */ -class GLES20TextureLayer extends GLES20Layer { - private int mTexture; - private SurfaceTexture mSurface; - - GLES20TextureLayer(boolean isOpaque) { - int[] layerInfo = new int[2]; - mLayer = GLES20Canvas.nCreateTextureLayer(isOpaque, layerInfo); - - if (mLayer != 0) { - mTexture = layerInfo[0]; - mFinalizer = new Finalizer(mLayer); - } else { - mFinalizer = null; - } - } - - @Override - boolean isValid() { - return mLayer != 0 && mTexture != 0; - } - - @Override - boolean resize(int width, int height) { - return isValid(); - } - - @Override - HardwareCanvas getCanvas() { - return null; - } - - @Override - HardwareCanvas start(Canvas currentCanvas) { - return null; - } - - @Override - HardwareCanvas start(Canvas currentCanvas, Rect dirty) { - return null; - } - - @Override - void end(Canvas currentCanvas) { - } - - SurfaceTexture getSurfaceTexture() { - if (mSurface == null) { - mSurface = new SurfaceTexture(mTexture); - } - return mSurface; - } - - void setSurfaceTexture(SurfaceTexture surfaceTexture) { - if (mSurface != null) { - mSurface.release(); - } - mSurface = surfaceTexture; - mSurface.attachToGLContext(mTexture); - } - - @Override - void update(int width, int height, boolean isOpaque) { - super.update(width, height, isOpaque); - GLES20Canvas.nUpdateTextureLayer(mLayer, width, height, isOpaque, mSurface); - } - - @Override - void setOpaque(boolean isOpaque) { - throw new UnsupportedOperationException("Use update(int, int, boolean) instead"); - } - - @Override - void setTransform(Matrix matrix) { - GLES20Canvas.nSetTextureLayerTransform(mLayer, matrix.native_instance); - } - - @Override - void redrawLater(DisplayList displayList, Rect dirtyRect) { - } -} diff --git a/core/java/android/view/GLRenderer.java b/core/java/android/view/GLRenderer.java new file mode 100644 index 0000000..81f778d --- /dev/null +++ b/core/java/android/view/GLRenderer.java @@ -0,0 +1,1554 @@ +/* + * 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.view; + +import static javax.microedition.khronos.egl.EGL10.EGL_ALPHA_SIZE; +import static javax.microedition.khronos.egl.EGL10.EGL_BAD_NATIVE_WINDOW; +import static javax.microedition.khronos.egl.EGL10.EGL_BLUE_SIZE; +import static javax.microedition.khronos.egl.EGL10.EGL_CONFIG_CAVEAT; +import static javax.microedition.khronos.egl.EGL10.EGL_DEFAULT_DISPLAY; +import static javax.microedition.khronos.egl.EGL10.EGL_DEPTH_SIZE; +import static javax.microedition.khronos.egl.EGL10.EGL_DRAW; +import static javax.microedition.khronos.egl.EGL10.EGL_GREEN_SIZE; +import static javax.microedition.khronos.egl.EGL10.EGL_HEIGHT; +import static javax.microedition.khronos.egl.EGL10.EGL_NONE; +import static javax.microedition.khronos.egl.EGL10.EGL_NO_CONTEXT; +import static javax.microedition.khronos.egl.EGL10.EGL_NO_DISPLAY; +import static javax.microedition.khronos.egl.EGL10.EGL_NO_SURFACE; +import static javax.microedition.khronos.egl.EGL10.EGL_RED_SIZE; +import static javax.microedition.khronos.egl.EGL10.EGL_RENDERABLE_TYPE; +import static javax.microedition.khronos.egl.EGL10.EGL_SAMPLES; +import static javax.microedition.khronos.egl.EGL10.EGL_SAMPLE_BUFFERS; +import static javax.microedition.khronos.egl.EGL10.EGL_STENCIL_SIZE; +import static javax.microedition.khronos.egl.EGL10.EGL_SUCCESS; +import static javax.microedition.khronos.egl.EGL10.EGL_SURFACE_TYPE; +import static javax.microedition.khronos.egl.EGL10.EGL_WIDTH; +import static javax.microedition.khronos.egl.EGL10.EGL_WINDOW_BIT; + +import android.content.ComponentCallbacks2; +import android.graphics.Bitmap; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.SurfaceTexture; +import android.opengl.EGL14; +import android.opengl.GLUtils; +import android.opengl.ManagedEGLContext; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.os.Trace; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.Surface.OutOfResourcesException; + +import com.google.android.gles_jni.EGLImpl; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGL11; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.egl.EGLDisplay; +import javax.microedition.khronos.egl.EGLSurface; +import javax.microedition.khronos.opengles.GL; + +/** + * Hardware renderer using OpenGL + * + * @hide + */ +public class GLRenderer extends HardwareRenderer { + static final int SURFACE_STATE_ERROR = 0; + static final int SURFACE_STATE_SUCCESS = 1; + static final int SURFACE_STATE_UPDATED = 2; + + static final int FUNCTOR_PROCESS_DELAY = 4; + + /** + * Number of frames to profile. + */ + private static final int PROFILE_MAX_FRAMES = 128; + + /** + * Number of floats per profiled frame. + */ + private static final int PROFILE_FRAME_DATA_COUNT = 3; + + private static final int PROFILE_DRAW_MARGIN = 0; + private static final int PROFILE_DRAW_WIDTH = 3; + private static final int[] PROFILE_DRAW_COLORS = { 0xcf3e66cc, 0xcfdc3912, 0xcfe69800 }; + private static final int PROFILE_DRAW_CURRENT_FRAME_COLOR = 0xcf5faa4d; + private static final int PROFILE_DRAW_THRESHOLD_COLOR = 0xff5faa4d; + private static final int PROFILE_DRAW_THRESHOLD_STROKE_WIDTH = 2; + private static final int PROFILE_DRAW_DP_PER_MS = 7; + + private static final String[] VISUALIZERS = { + PROFILE_PROPERTY_VISUALIZE_BARS, + PROFILE_PROPERTY_VISUALIZE_LINES + }; + + private static final String[] OVERDRAW = { + OVERDRAW_PROPERTY_SHOW, + }; + private static final int GL_VERSION = 2; + + static EGL10 sEgl; + static EGLDisplay sEglDisplay; + static EGLConfig sEglConfig; + static final Object[] sEglLock = new Object[0]; + int mWidth = -1, mHeight = -1; + + static final ThreadLocal<ManagedEGLContext> sEglContextStorage + = new ThreadLocal<ManagedEGLContext>(); + + EGLContext mEglContext; + Thread mEglThread; + + EGLSurface mEglSurface; + + GL mGl; + HardwareCanvas mCanvas; + + String mName; + + long mFrameCount; + Paint mDebugPaint; + + static boolean sDirtyRegions; + static final boolean sDirtyRegionsRequested; + static { + String dirtyProperty = SystemProperties.get(RENDER_DIRTY_REGIONS_PROPERTY, "true"); + //noinspection PointlessBooleanExpression,ConstantConditions + sDirtyRegions = "true".equalsIgnoreCase(dirtyProperty); + sDirtyRegionsRequested = sDirtyRegions; + } + + boolean mDirtyRegionsEnabled; + boolean mUpdateDirtyRegions; + + boolean mProfileEnabled; + int mProfileVisualizerType = -1; + float[] mProfileData; + ReentrantLock mProfileLock; + int mProfileCurrentFrame = -PROFILE_FRAME_DATA_COUNT; + + GraphDataProvider mDebugDataProvider; + float[][] mProfileShapes; + Paint mProfilePaint; + + boolean mDebugDirtyRegions; + int mDebugOverdraw = -1; + + final boolean mTranslucent; + + private boolean mDestroyed; + + private final Rect mRedrawClip = new Rect(); + + private final int[] mSurfaceSize = new int[2]; + private final FunctorsRunnable mFunctorsRunnable = new FunctorsRunnable(); + + private long mDrawDelta = Long.MAX_VALUE; + + private GLES20Canvas mGlCanvas; + + private DisplayMetrics mDisplayMetrics; + + private static EGLSurface sPbuffer; + private static final Object[] sPbufferLock = new Object[0]; + + private List<HardwareLayer> mAttachedLayers = new ArrayList<HardwareLayer>(); + + private static class GLRendererEglContext extends ManagedEGLContext { + final Handler mHandler = new Handler(); + + public GLRendererEglContext(EGLContext context) { + super(context); + } + + @Override + public void onTerminate(final EGLContext eglContext) { + // Make sure we do this on the correct thread. + if (mHandler.getLooper() != Looper.myLooper()) { + mHandler.post(new Runnable() { + @Override + public void run() { + onTerminate(eglContext); + } + }); + return; + } + + synchronized (sEglLock) { + if (sEgl == null) return; + + if (EGLImpl.getInitCount(sEglDisplay) == 1) { + usePbufferSurface(eglContext); + GLES20Canvas.terminateCaches(); + + sEgl.eglDestroyContext(sEglDisplay, eglContext); + sEglContextStorage.set(null); + sEglContextStorage.remove(); + + sEgl.eglDestroySurface(sEglDisplay, sPbuffer); + sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, + EGL_NO_SURFACE, EGL_NO_CONTEXT); + + sEgl.eglReleaseThread(); + sEgl.eglTerminate(sEglDisplay); + + sEgl = null; + sEglDisplay = null; + sEglConfig = null; + sPbuffer = null; + } + } + } + } + + HardwareCanvas createCanvas() { + return mGlCanvas = new GLES20Canvas(mTranslucent); + } + + ManagedEGLContext createManagedContext(EGLContext eglContext) { + return new GLRendererEglContext(mEglContext); + } + + int[] getConfig(boolean dirtyRegions) { + //noinspection PointlessBooleanExpression,ConstantConditions + final int stencilSize = GLES20Canvas.getStencilSize(); + final int swapBehavior = dirtyRegions ? EGL14.EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0; + + return new int[] { + EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_DEPTH_SIZE, 0, + EGL_CONFIG_CAVEAT, EGL_NONE, + EGL_STENCIL_SIZE, stencilSize, + EGL_SURFACE_TYPE, EGL_WINDOW_BIT | swapBehavior, + EGL_NONE + }; + } + + void initCaches() { + if (GLES20Canvas.initCaches()) { + // Caches were (re)initialized, rebind atlas + initAtlas(); + } + } + + void initAtlas() { + IBinder binder = ServiceManager.getService("assetatlas"); + if (binder == null) return; + + IAssetAtlas atlas = IAssetAtlas.Stub.asInterface(binder); + try { + if (atlas.isCompatible(android.os.Process.myPpid())) { + GraphicBuffer buffer = atlas.getBuffer(); + if (buffer != null) { + long[] map = atlas.getMap(); + if (map != null) { + GLES20Canvas.initAtlas(buffer, map); + } + // If IAssetAtlas is not the same class as the IBinder + // we are using a remote service and we can safely + // destroy the graphic buffer + if (atlas.getClass() != binder.getClass()) { + buffer.destroy(); + } + } + } + } catch (RemoteException e) { + Log.w(LOG_TAG, "Could not acquire atlas", e); + } + } + + boolean canDraw() { + return mGl != null && mCanvas != null && mGlCanvas != null; + } + + int onPreDraw(Rect dirty) { + return mGlCanvas.onPreDraw(dirty); + } + + void onPostDraw() { + mGlCanvas.onPostDraw(); + } + + void drawProfileData(View.AttachInfo attachInfo) { + if (mDebugDataProvider != null) { + final GraphDataProvider provider = mDebugDataProvider; + initProfileDrawData(attachInfo, provider); + + final int height = provider.getVerticalUnitSize(); + final int margin = provider.getHorizontaUnitMargin(); + final int width = provider.getHorizontalUnitSize(); + + int x = 0; + int count = 0; + int current = 0; + + final float[] data = provider.getData(); + final int elementCount = provider.getElementCount(); + final int graphType = provider.getGraphType(); + + int totalCount = provider.getFrameCount() * elementCount; + if (graphType == GraphDataProvider.GRAPH_TYPE_LINES) { + totalCount -= elementCount; + } + + for (int i = 0; i < totalCount; i += elementCount) { + if (data[i] < 0.0f) break; + + int index = count * 4; + if (i == provider.getCurrentFrame() * elementCount) current = index; + + x += margin; + int x2 = x + width; + + int y2 = mHeight; + int y1 = (int) (y2 - data[i] * height); + + switch (graphType) { + case GraphDataProvider.GRAPH_TYPE_BARS: { + for (int j = 0; j < elementCount; j++) { + //noinspection MismatchedReadAndWriteOfArray + final float[] r = mProfileShapes[j]; + r[index] = x; + r[index + 1] = y1; + r[index + 2] = x2; + r[index + 3] = y2; + + y2 = y1; + if (j < elementCount - 1) { + y1 = (int) (y2 - data[i + j + 1] * height); + } + } + } break; + case GraphDataProvider.GRAPH_TYPE_LINES: { + for (int j = 0; j < elementCount; j++) { + //noinspection MismatchedReadAndWriteOfArray + final float[] r = mProfileShapes[j]; + r[index] = (x + x2) * 0.5f; + r[index + 1] = index == 0 ? y1 : r[index - 1]; + r[index + 2] = r[index] + width; + r[index + 3] = y1; + + y2 = y1; + if (j < elementCount - 1) { + y1 = (int) (y2 - data[i + j + 1] * height); + } + } + } break; + } + + + x += width; + count++; + } + + x += margin; + + drawGraph(graphType, count); + drawCurrentFrame(graphType, current); + drawThreshold(x, height); + } + } + + private void drawGraph(int graphType, int count) { + for (int i = 0; i < mProfileShapes.length; i++) { + mDebugDataProvider.setupGraphPaint(mProfilePaint, i); + switch (graphType) { + case GraphDataProvider.GRAPH_TYPE_BARS: + mGlCanvas.drawRects(mProfileShapes[i], count * 4, mProfilePaint); + break; + case GraphDataProvider.GRAPH_TYPE_LINES: + mGlCanvas.drawLines(mProfileShapes[i], 0, count * 4, mProfilePaint); + break; + } + } + } + + private void drawCurrentFrame(int graphType, int index) { + if (index >= 0) { + mDebugDataProvider.setupCurrentFramePaint(mProfilePaint); + switch (graphType) { + case GraphDataProvider.GRAPH_TYPE_BARS: + mGlCanvas.drawRect(mProfileShapes[2][index], mProfileShapes[2][index + 1], + mProfileShapes[2][index + 2], mProfileShapes[0][index + 3], + mProfilePaint); + break; + case GraphDataProvider.GRAPH_TYPE_LINES: + mGlCanvas.drawLine(mProfileShapes[2][index], mProfileShapes[2][index + 1], + mProfileShapes[2][index], mHeight, mProfilePaint); + break; + } + } + } + + private void drawThreshold(int x, int height) { + float threshold = mDebugDataProvider.getThreshold(); + if (threshold > 0.0f) { + mDebugDataProvider.setupThresholdPaint(mProfilePaint); + int y = (int) (mHeight - threshold * height); + mGlCanvas.drawLine(0.0f, y, x, y, mProfilePaint); + } + } + + private void initProfileDrawData(View.AttachInfo attachInfo, GraphDataProvider provider) { + if (mProfileShapes == null) { + final int elementCount = provider.getElementCount(); + final int frameCount = provider.getFrameCount(); + + mProfileShapes = new float[elementCount][]; + for (int i = 0; i < elementCount; i++) { + mProfileShapes[i] = new float[frameCount * 4]; + } + + mProfilePaint = new Paint(); + } + + mProfilePaint.reset(); + if (provider.getGraphType() == GraphDataProvider.GRAPH_TYPE_LINES) { + mProfilePaint.setAntiAlias(true); + } + + if (mDisplayMetrics == null) { + mDisplayMetrics = new DisplayMetrics(); + } + + attachInfo.mDisplay.getMetrics(mDisplayMetrics); + provider.prepare(mDisplayMetrics); + } + + @Override + void destroy(boolean full) { + try { + if (full && mCanvas != null) { + mCanvas = null; + } + + if (!isEnabled() || mDestroyed) { + setEnabled(false); + return; + } + + destroySurface(); + setEnabled(false); + + mDestroyed = true; + mGl = null; + } finally { + if (full && mGlCanvas != null) { + mGlCanvas = null; + } + } + } + + @Override + void pushLayerUpdate(HardwareLayer layer) { + mGlCanvas.pushLayerUpdate(layer); + } + + @Override + void flushLayerUpdates() { + if (validate()) { + flushLayerChanges(); + mGlCanvas.flushLayerUpdates(); + } + } + + @Override + HardwareLayer createTextureLayer() { + validate(); + return HardwareLayer.createTextureLayer(this); + } + + @Override + public HardwareLayer createDisplayListLayer(int width, int height) { + validate(); + return HardwareLayer.createDisplayListLayer(this, width, height); + } + + @Override + void onLayerCreated(HardwareLayer hardwareLayer) { + mAttachedLayers.add(hardwareLayer); + } + + boolean hasContext() { + return sEgl != null && mEglContext != null + && mEglContext.equals(sEgl.eglGetCurrentContext()); + } + + @Override + void onLayerDestroyed(HardwareLayer layer) { + if (mGlCanvas != null) { + mGlCanvas.cancelLayerUpdate(layer); + } + if (hasContext()) { + long backingLayer = layer.detachBackingLayer(); + nDestroyLayer(backingLayer); + } + mAttachedLayers.remove(layer); + } + + @Override + public SurfaceTexture createSurfaceTexture(HardwareLayer layer) { + return layer.createSurfaceTexture(); + } + + @Override + boolean copyLayerInto(HardwareLayer layer, Bitmap bitmap) { + if (!validate()) { + throw new IllegalStateException("Could not acquire hardware rendering context"); + } + layer.flushChanges(); + return GLES20Canvas.nCopyLayer(layer.getLayer(), bitmap.mNativeBitmap); + } + + @Override + boolean safelyRun(Runnable action) { + boolean needsContext = !isEnabled() || checkRenderContext() == SURFACE_STATE_ERROR; + + if (needsContext) { + GLRendererEglContext managedContext = + (GLRendererEglContext) sEglContextStorage.get(); + if (managedContext == null) return false; + usePbufferSurface(managedContext.getContext()); + } + + try { + action.run(); + } finally { + if (needsContext) { + sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, + EGL_NO_SURFACE, EGL_NO_CONTEXT); + } + } + + return true; + } + + @Override + void destroyHardwareResources(final View view) { + if (view != null) { + safelyRun(new Runnable() { + @Override + public void run() { + if (mCanvas != null) { + mCanvas.clearLayerUpdates(); + } + destroyResources(view); + GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS); + } + }); + } + } + + private static void destroyResources(View view) { + view.destroyHardwareResources(); + + if (view instanceof ViewGroup) { + ViewGroup group = (ViewGroup) view; + + int count = group.getChildCount(); + for (int i = 0; i < count; i++) { + destroyResources(group.getChildAt(i)); + } + } + } + + static void startTrimMemory(int level) { + if (sEgl == null || sEglConfig == null) return; + + GLRendererEglContext managedContext = + (GLRendererEglContext) sEglContextStorage.get(); + // We do not have OpenGL objects + if (managedContext == null) { + return; + } else { + usePbufferSurface(managedContext.getContext()); + } + + if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) { + GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_FULL); + } else if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) { + GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_MODERATE); + } + } + + static void endTrimMemory() { + if (sEgl != null && sEglDisplay != null) { + sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + } + } + + private static void usePbufferSurface(EGLContext eglContext) { + synchronized (sPbufferLock) { + // Create a temporary 1x1 pbuffer so we have a context + // to clear our OpenGL objects + if (sPbuffer == null) { + sPbuffer = sEgl.eglCreatePbufferSurface(sEglDisplay, sEglConfig, new int[] { + EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE + }); + } + } + sEgl.eglMakeCurrent(sEglDisplay, sPbuffer, sPbuffer, eglContext); + } + + GLRenderer(boolean translucent) { + mTranslucent = translucent; + + loadSystemProperties(); + } + + @Override + boolean loadSystemProperties() { + boolean value; + boolean changed = false; + + String profiling = SystemProperties.get(PROFILE_PROPERTY); + int graphType = search(VISUALIZERS, profiling); + value = graphType >= 0; + + if (graphType != mProfileVisualizerType) { + changed = true; + mProfileVisualizerType = graphType; + + mProfileShapes = null; + mProfilePaint = null; + + if (value) { + mDebugDataProvider = new DrawPerformanceDataProvider(graphType); + } else { + mDebugDataProvider = null; + } + } + + // If on-screen profiling is not enabled, we need to check whether + // console profiling only is enabled + if (!value) { + value = Boolean.parseBoolean(profiling); + } + + if (value != mProfileEnabled) { + changed = true; + mProfileEnabled = value; + + if (mProfileEnabled) { + Log.d(LOG_TAG, "Profiling hardware renderer"); + + int maxProfileFrames = SystemProperties.getInt(PROFILE_MAXFRAMES_PROPERTY, + PROFILE_MAX_FRAMES); + mProfileData = new float[maxProfileFrames * PROFILE_FRAME_DATA_COUNT]; + for (int i = 0; i < mProfileData.length; i += PROFILE_FRAME_DATA_COUNT) { + mProfileData[i] = mProfileData[i + 1] = mProfileData[i + 2] = -1; + } + + mProfileLock = new ReentrantLock(); + } else { + mProfileData = null; + mProfileLock = null; + mProfileVisualizerType = -1; + } + + mProfileCurrentFrame = -PROFILE_FRAME_DATA_COUNT; + } + + value = SystemProperties.getBoolean(DEBUG_DIRTY_REGIONS_PROPERTY, false); + if (value != mDebugDirtyRegions) { + changed = true; + mDebugDirtyRegions = value; + + if (mDebugDirtyRegions) { + Log.d(LOG_TAG, "Debugging dirty regions"); + } + } + + String overdraw = SystemProperties.get(HardwareRenderer.DEBUG_OVERDRAW_PROPERTY); + int debugOverdraw = search(OVERDRAW, overdraw); + if (debugOverdraw != mDebugOverdraw) { + changed = true; + mDebugOverdraw = debugOverdraw; + } + + if (loadProperties()) { + changed = true; + } + + return changed; + } + + private static int search(String[] values, String value) { + for (int i = 0; i < values.length; i++) { + if (values[i].equals(value)) return i; + } + return -1; + } + + @Override + void dumpGfxInfo(PrintWriter pw) { + if (mProfileEnabled) { + pw.printf("\n\tDraw\tProcess\tExecute\n"); + + mProfileLock.lock(); + try { + for (int i = 0; i < mProfileData.length; i += PROFILE_FRAME_DATA_COUNT) { + if (mProfileData[i] < 0) { + break; + } + pw.printf("\t%3.2f\t%3.2f\t%3.2f\n", mProfileData[i], mProfileData[i + 1], + mProfileData[i + 2]); + mProfileData[i] = mProfileData[i + 1] = mProfileData[i + 2] = -1; + } + mProfileCurrentFrame = mProfileData.length; + } finally { + mProfileLock.unlock(); + } + } + } + + @Override + long getFrameCount() { + return mFrameCount; + } + + /** + * Indicates whether this renderer instance can track and update dirty regions. + */ + boolean hasDirtyRegions() { + return mDirtyRegionsEnabled; + } + + /** + * Checks for OpenGL errors. If an error has occured, {@link #destroy(boolean)} + * is invoked and the requested flag is turned off. The error code is + * also logged as a warning. + */ + void checkEglErrors() { + if (isEnabled()) { + checkEglErrorsForced(); + } + } + + private void checkEglErrorsForced() { + int error = sEgl.eglGetError(); + if (error != EGL_SUCCESS) { + // something bad has happened revert to + // normal rendering. + Log.w(LOG_TAG, "EGL error: " + GLUtils.getEGLErrorString(error)); + fallback(error != EGL11.EGL_CONTEXT_LOST); + } + } + + private void fallback(boolean fallback) { + destroy(true); + if (fallback) { + // we'll try again if it was context lost + setRequested(false); + Log.w(LOG_TAG, "Mountain View, we've had a problem here. " + + "Switching back to software rendering."); + } + } + + @Override + boolean initialize(Surface surface) throws OutOfResourcesException { + if (isRequested() && !isEnabled()) { + boolean contextCreated = initializeEgl(); + mGl = createEglSurface(surface); + mDestroyed = false; + + if (mGl != null) { + int err = sEgl.eglGetError(); + if (err != EGL_SUCCESS) { + destroy(true); + setRequested(false); + } else { + if (mCanvas == null) { + mCanvas = createCanvas(); + } + setEnabled(true); + + if (contextCreated) { + initAtlas(); + } + } + + return mCanvas != null; + } + } + return false; + } + + @Override + void updateSurface(Surface surface) throws OutOfResourcesException { + if (isRequested() && isEnabled()) { + createEglSurface(surface); + } + } + + boolean initializeEgl() { + synchronized (sEglLock) { + if (sEgl == null && sEglConfig == null) { + sEgl = (EGL10) EGLContext.getEGL(); + + // Get to the default display. + sEglDisplay = sEgl.eglGetDisplay(EGL_DEFAULT_DISPLAY); + + if (sEglDisplay == EGL_NO_DISPLAY) { + throw new RuntimeException("eglGetDisplay failed " + + GLUtils.getEGLErrorString(sEgl.eglGetError())); + } + + // We can now initialize EGL for that display + int[] version = new int[2]; + if (!sEgl.eglInitialize(sEglDisplay, version)) { + throw new RuntimeException("eglInitialize failed " + + GLUtils.getEGLErrorString(sEgl.eglGetError())); + } + + checkEglErrorsForced(); + + sEglConfig = loadEglConfig(); + } + } + + ManagedEGLContext managedContext = sEglContextStorage.get(); + mEglContext = managedContext != null ? managedContext.getContext() : null; + mEglThread = Thread.currentThread(); + + if (mEglContext == null) { + mEglContext = createContext(sEgl, sEglDisplay, sEglConfig); + sEglContextStorage.set(createManagedContext(mEglContext)); + return true; + } + + return false; + } + + private EGLConfig loadEglConfig() { + EGLConfig eglConfig = chooseEglConfig(); + if (eglConfig == null) { + // We tried to use EGL_SWAP_BEHAVIOR_PRESERVED_BIT, try again without + if (sDirtyRegions) { + sDirtyRegions = false; + eglConfig = chooseEglConfig(); + if (eglConfig == null) { + throw new RuntimeException("eglConfig not initialized"); + } + } else { + throw new RuntimeException("eglConfig not initialized"); + } + } + return eglConfig; + } + + private EGLConfig chooseEglConfig() { + EGLConfig[] configs = new EGLConfig[1]; + int[] configsCount = new int[1]; + int[] configSpec = getConfig(sDirtyRegions); + + // Debug + final String debug = SystemProperties.get(PRINT_CONFIG_PROPERTY, ""); + if ("all".equalsIgnoreCase(debug)) { + sEgl.eglChooseConfig(sEglDisplay, configSpec, null, 0, configsCount); + + EGLConfig[] debugConfigs = new EGLConfig[configsCount[0]]; + sEgl.eglChooseConfig(sEglDisplay, configSpec, debugConfigs, + configsCount[0], configsCount); + + for (EGLConfig config : debugConfigs) { + printConfig(config); + } + } + + if (!sEgl.eglChooseConfig(sEglDisplay, configSpec, configs, 1, configsCount)) { + throw new IllegalArgumentException("eglChooseConfig failed " + + GLUtils.getEGLErrorString(sEgl.eglGetError())); + } else if (configsCount[0] > 0) { + if ("choice".equalsIgnoreCase(debug)) { + printConfig(configs[0]); + } + return configs[0]; + } + + return null; + } + + private static void printConfig(EGLConfig config) { + int[] value = new int[1]; + + Log.d(LOG_TAG, "EGL configuration " + config + ":"); + + sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_RED_SIZE, value); + Log.d(LOG_TAG, " RED_SIZE = " + value[0]); + + sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_GREEN_SIZE, value); + Log.d(LOG_TAG, " GREEN_SIZE = " + value[0]); + + sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_BLUE_SIZE, value); + Log.d(LOG_TAG, " BLUE_SIZE = " + value[0]); + + sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_ALPHA_SIZE, value); + Log.d(LOG_TAG, " ALPHA_SIZE = " + value[0]); + + sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_DEPTH_SIZE, value); + Log.d(LOG_TAG, " DEPTH_SIZE = " + value[0]); + + sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_STENCIL_SIZE, value); + Log.d(LOG_TAG, " STENCIL_SIZE = " + value[0]); + + sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_SAMPLE_BUFFERS, value); + Log.d(LOG_TAG, " SAMPLE_BUFFERS = " + value[0]); + + sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_SAMPLES, value); + Log.d(LOG_TAG, " SAMPLES = " + value[0]); + + sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_SURFACE_TYPE, value); + Log.d(LOG_TAG, " SURFACE_TYPE = 0x" + Integer.toHexString(value[0])); + + sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_CONFIG_CAVEAT, value); + Log.d(LOG_TAG, " CONFIG_CAVEAT = 0x" + Integer.toHexString(value[0])); + } + + GL createEglSurface(Surface surface) throws OutOfResourcesException { + // Check preconditions. + if (sEgl == null) { + throw new RuntimeException("egl not initialized"); + } + if (sEglDisplay == null) { + throw new RuntimeException("eglDisplay not initialized"); + } + if (sEglConfig == null) { + throw new RuntimeException("eglConfig not initialized"); + } + if (Thread.currentThread() != mEglThread) { + throw new IllegalStateException("HardwareRenderer cannot be used " + + "from multiple threads"); + } + + // In case we need to destroy an existing surface + destroySurface(); + + // Create an EGL surface we can render into. + if (!createSurface(surface)) { + return null; + } + + initCaches(); + + return mEglContext.getGL(); + } + + private void enableDirtyRegions() { + // If mDirtyRegions is set, this means we have an EGL configuration + // with EGL_SWAP_BEHAVIOR_PRESERVED_BIT set + if (sDirtyRegions) { + if (!(mDirtyRegionsEnabled = preserveBackBuffer())) { + Log.w(LOG_TAG, "Backbuffer cannot be preserved"); + } + } else if (sDirtyRegionsRequested) { + // If mDirtyRegions is not set, our EGL configuration does not + // have EGL_SWAP_BEHAVIOR_PRESERVED_BIT; however, the default + // swap behavior might be EGL_BUFFER_PRESERVED, which means we + // want to set mDirtyRegions. We try to do this only if dirty + // regions were initially requested as part of the device + // configuration (see RENDER_DIRTY_REGIONS) + mDirtyRegionsEnabled = isBackBufferPreserved(); + } + } + + EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) { + final int[] attribs = { EGL14.EGL_CONTEXT_CLIENT_VERSION, GL_VERSION, EGL_NONE }; + + EGLContext context = egl.eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, + attribs); + if (context == null || context == EGL_NO_CONTEXT) { + //noinspection ConstantConditions + throw new IllegalStateException( + "Could not create an EGL context. eglCreateContext failed with error: " + + GLUtils.getEGLErrorString(sEgl.eglGetError())); + } + + return context; + } + + void destroySurface() { + if (mEglSurface != null && mEglSurface != EGL_NO_SURFACE) { + if (mEglSurface.equals(sEgl.eglGetCurrentSurface(EGL_DRAW))) { + sEgl.eglMakeCurrent(sEglDisplay, + EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + } + sEgl.eglDestroySurface(sEglDisplay, mEglSurface); + mEglSurface = null; + } + } + + @Override + void invalidate(Surface surface) { + // Cancels any existing buffer to ensure we'll get a buffer + // of the right size before we call eglSwapBuffers + sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + + if (mEglSurface != null && mEglSurface != EGL_NO_SURFACE) { + sEgl.eglDestroySurface(sEglDisplay, mEglSurface); + mEglSurface = null; + setEnabled(false); + } + + if (surface.isValid()) { + if (!createSurface(surface)) { + return; + } + + mUpdateDirtyRegions = true; + + if (mCanvas != null) { + setEnabled(true); + } + } + } + + private boolean createSurface(Surface surface) { + mEglSurface = sEgl.eglCreateWindowSurface(sEglDisplay, sEglConfig, surface, null); + + if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) { + int error = sEgl.eglGetError(); + if (error == EGL_BAD_NATIVE_WINDOW) { + Log.e(LOG_TAG, "createWindowSurface returned EGL_BAD_NATIVE_WINDOW."); + return false; + } + throw new RuntimeException("createWindowSurface failed " + + GLUtils.getEGLErrorString(error)); + } + + if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, mEglContext)) { + throw new IllegalStateException("eglMakeCurrent failed " + + GLUtils.getEGLErrorString(sEgl.eglGetError())); + } + + enableDirtyRegions(); + + return true; + } + + boolean validate() { + return checkRenderContext() != SURFACE_STATE_ERROR; + } + + @Override + void setup(int width, int height) { + if (validate()) { + mCanvas.setViewport(width, height); + mWidth = width; + mHeight = height; + } + } + + @Override + int getWidth() { + return mWidth; + } + + @Override + int getHeight() { + return mHeight; + } + + @Override + void setName(String name) { + mName = name; + } + + class FunctorsRunnable implements Runnable { + View.AttachInfo attachInfo; + + @Override + public void run() { + final HardwareRenderer renderer = attachInfo.mHardwareRenderer; + if (renderer == null || !renderer.isEnabled() || renderer != GLRenderer.this) { + return; + } + + if (checkRenderContext() != SURFACE_STATE_ERROR) { + int status = mCanvas.invokeFunctors(mRedrawClip); + handleFunctorStatus(attachInfo, status); + } + } + } + + @Override + void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks, + Rect dirty) { + if (canDraw()) { + if (!hasDirtyRegions()) { + dirty = null; + } + attachInfo.mIgnoreDirtyState = true; + attachInfo.mDrawingTime = SystemClock.uptimeMillis(); + + view.mPrivateFlags |= View.PFLAG_DRAWN; + + // We are already on the correct thread + final int surfaceState = checkRenderContextUnsafe(); + if (surfaceState != SURFACE_STATE_ERROR) { + HardwareCanvas canvas = mCanvas; + + if (mProfileEnabled) { + mProfileLock.lock(); + } + + dirty = beginFrame(canvas, dirty, surfaceState); + + DisplayList displayList = buildDisplayList(view, canvas); + + flushLayerChanges(); + + // buildDisplayList() calls into user code which can cause + // an eglMakeCurrent to happen with a different surface/context. + // We must therefore check again here. + if (checkRenderContextUnsafe() == SURFACE_STATE_ERROR) { + return; + } + + int saveCount = 0; + int status = DisplayList.STATUS_DONE; + + long start = getSystemTime(); + try { + status = prepareFrame(dirty); + + saveCount = canvas.save(); + callbacks.onHardwarePreDraw(canvas); + + if (displayList != null) { + status |= drawDisplayList(attachInfo, canvas, displayList, status); + } else { + // Shouldn't reach here + view.draw(canvas); + } + } catch (Exception e) { + Log.e(LOG_TAG, "An error has occurred while drawing:", e); + } finally { + callbacks.onHardwarePostDraw(canvas); + canvas.restoreToCount(saveCount); + view.mRecreateDisplayList = false; + + mDrawDelta = getSystemTime() - start; + + if (mDrawDelta > 0) { + mFrameCount++; + + debugDirtyRegions(dirty, canvas); + drawProfileData(attachInfo); + } + } + + onPostDraw(); + + swapBuffers(status); + + if (mProfileEnabled) { + mProfileLock.unlock(); + } + + attachInfo.mIgnoreDirtyState = false; + } + } + } + + private void flushLayerChanges() { + // Loop through and apply any pending layer changes + for (int i = 0; i < mAttachedLayers.size(); i++) { + HardwareLayer layer = mAttachedLayers.get(i); + layer.flushChanges(); + if (!layer.isValid()) { + // The layer was removed from mAttachedLayers, rewind i by 1 + // Note that this shouldn't actually happen as View.getHardwareLayer() + // is already flushing for error checking reasons + i--; + } + } + } + + void setDisplayListData(long displayList, long newData) { + nSetDisplayListData(displayList, newData); + } + private static native void nSetDisplayListData(long displayList, long newData); + + private DisplayList buildDisplayList(View view, HardwareCanvas canvas) { + if (mDrawDelta <= 0) { + return view.mDisplayList; + } + + view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED) + == View.PFLAG_INVALIDATED; + view.mPrivateFlags &= ~View.PFLAG_INVALIDATED; + + long buildDisplayListStartTime = startBuildDisplayListProfiling(); + canvas.clearLayerUpdates(); + + Trace.traceBegin(Trace.TRACE_TAG_VIEW, "getDisplayList"); + DisplayList displayList = view.getDisplayList(); + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + + endBuildDisplayListProfiling(buildDisplayListStartTime); + + return displayList; + } + + private Rect beginFrame(HardwareCanvas canvas, Rect dirty, int surfaceState) { + // We had to change the current surface and/or context, redraw everything + if (surfaceState == SURFACE_STATE_UPDATED) { + dirty = null; + beginFrame(null); + } else { + int[] size = mSurfaceSize; + beginFrame(size); + + if (size[1] != mHeight || size[0] != mWidth) { + mWidth = size[0]; + mHeight = size[1]; + + canvas.setViewport(mWidth, mHeight); + + dirty = null; + } + } + + if (mDebugDataProvider != null) dirty = null; + + return dirty; + } + + private long startBuildDisplayListProfiling() { + if (mProfileEnabled) { + mProfileCurrentFrame += PROFILE_FRAME_DATA_COUNT; + if (mProfileCurrentFrame >= mProfileData.length) { + mProfileCurrentFrame = 0; + } + + return System.nanoTime(); + } + return 0; + } + + private void endBuildDisplayListProfiling(long getDisplayListStartTime) { + if (mProfileEnabled) { + long now = System.nanoTime(); + float total = (now - getDisplayListStartTime) * 0.000001f; + //noinspection PointlessArithmeticExpression + mProfileData[mProfileCurrentFrame] = total; + } + } + + private int prepareFrame(Rect dirty) { + int status; + Trace.traceBegin(Trace.TRACE_TAG_VIEW, "prepareFrame"); + try { + status = onPreDraw(dirty); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } + return status; + } + + private int drawDisplayList(View.AttachInfo attachInfo, HardwareCanvas canvas, + DisplayList displayList, int status) { + + long drawDisplayListStartTime = 0; + if (mProfileEnabled) { + drawDisplayListStartTime = System.nanoTime(); + } + + Trace.traceBegin(Trace.TRACE_TAG_VIEW, "drawDisplayList"); + try { + status |= canvas.drawDisplayList(displayList, mRedrawClip, + DisplayList.FLAG_CLIP_CHILDREN); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } + + if (mProfileEnabled) { + long now = System.nanoTime(); + float total = (now - drawDisplayListStartTime) * 0.000001f; + mProfileData[mProfileCurrentFrame + 1] = total; + } + + handleFunctorStatus(attachInfo, status); + return status; + } + + private void swapBuffers(int status) { + if ((status & DisplayList.STATUS_DREW) == DisplayList.STATUS_DREW) { + long eglSwapBuffersStartTime = 0; + if (mProfileEnabled) { + eglSwapBuffersStartTime = System.nanoTime(); + } + + sEgl.eglSwapBuffers(sEglDisplay, mEglSurface); + + if (mProfileEnabled) { + long now = System.nanoTime(); + float total = (now - eglSwapBuffersStartTime) * 0.000001f; + mProfileData[mProfileCurrentFrame + 2] = total; + } + + checkEglErrors(); + } + } + + private void debugDirtyRegions(Rect dirty, HardwareCanvas canvas) { + if (mDebugDirtyRegions) { + if (mDebugPaint == null) { + mDebugPaint = new Paint(); + mDebugPaint.setColor(0x7fff0000); + } + + if (dirty != null && (mFrameCount & 1) == 0) { + canvas.drawRect(dirty, mDebugPaint); + } + } + } + + private void handleFunctorStatus(View.AttachInfo attachInfo, int status) { + // If the draw flag is set, functors will be invoked while executing + // the tree of display lists + if ((status & DisplayList.STATUS_DRAW) != 0) { + if (mRedrawClip.isEmpty()) { + attachInfo.mViewRootImpl.invalidate(); + } else { + attachInfo.mViewRootImpl.invalidateChildInParent(null, mRedrawClip); + mRedrawClip.setEmpty(); + } + } + + if ((status & DisplayList.STATUS_INVOKE) != 0 || + attachInfo.mHandler.hasCallbacks(mFunctorsRunnable)) { + attachInfo.mHandler.removeCallbacks(mFunctorsRunnable); + mFunctorsRunnable.attachInfo = attachInfo; + attachInfo.mHandler.postDelayed(mFunctorsRunnable, FUNCTOR_PROCESS_DELAY); + } + } + + @Override + void detachFunctor(long functor) { + if (mCanvas != null) { + mCanvas.detachFunctor(functor); + } + } + + @Override + void attachFunctor(View.AttachInfo attachInfo, long functor) { + if (mCanvas != null) { + mCanvas.attachFunctor(functor); + mFunctorsRunnable.attachInfo = attachInfo; + attachInfo.mHandler.removeCallbacks(mFunctorsRunnable); + attachInfo.mHandler.postDelayed(mFunctorsRunnable, 0); + } + } + + /** + * Ensures the current EGL context and surface are the ones we expect. + * This method throws an IllegalStateException if invoked from a thread + * that did not initialize EGL. + * + * @return {@link #SURFACE_STATE_ERROR} if the correct EGL context cannot be made current, + * {@link #SURFACE_STATE_UPDATED} if the EGL context was changed or + * {@link #SURFACE_STATE_SUCCESS} if the EGL context was the correct one + * + * @see #checkRenderContextUnsafe() + */ + int checkRenderContext() { + if (mEglThread != Thread.currentThread()) { + throw new IllegalStateException("Hardware acceleration can only be used with a " + + "single UI thread.\nOriginal thread: " + mEglThread + "\n" + + "Current thread: " + Thread.currentThread()); + } + + return checkRenderContextUnsafe(); + } + + /** + * Ensures the current EGL context and surface are the ones we expect. + * This method does not check the current thread. + * + * @return {@link #SURFACE_STATE_ERROR} if the correct EGL context cannot be made current, + * {@link #SURFACE_STATE_UPDATED} if the EGL context was changed or + * {@link #SURFACE_STATE_SUCCESS} if the EGL context was the correct one + * + * @see #checkRenderContext() + */ + private int checkRenderContextUnsafe() { + if (!mEglSurface.equals(sEgl.eglGetCurrentSurface(EGL_DRAW)) || + !mEglContext.equals(sEgl.eglGetCurrentContext())) { + if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, mEglContext)) { + Log.e(LOG_TAG, "eglMakeCurrent failed " + + GLUtils.getEGLErrorString(sEgl.eglGetError())); + fallback(true); + return SURFACE_STATE_ERROR; + } else { + if (mUpdateDirtyRegions) { + enableDirtyRegions(); + mUpdateDirtyRegions = false; + } + return SURFACE_STATE_UPDATED; + } + } + return SURFACE_STATE_SUCCESS; + } + + private static int dpToPx(int dp, float density) { + return (int) (dp * density + 0.5f); + } + + static native boolean loadProperties(); + + static native void setupShadersDiskCache(String cacheFile); + + /** + * Notifies EGL that the frame is about to be rendered. + * @param size + */ + static native void beginFrame(int[] size); + + /** + * Returns the current system time according to the renderer. + * This method is used for debugging only and should not be used + * as a clock. + */ + static native long getSystemTime(); + + /** + * Preserves the back buffer of the current surface after a buffer swap. + * Calling this method sets the EGL_SWAP_BEHAVIOR attribute of the current + * surface to EGL_BUFFER_PRESERVED. Calling this method requires an EGL + * config that supports EGL_SWAP_BEHAVIOR_PRESERVED_BIT. + * + * @return True if the swap behavior was successfully changed, + * false otherwise. + */ + static native boolean preserveBackBuffer(); + + /** + * Indicates whether the current surface preserves its back buffer + * after a buffer swap. + * + * @return True, if the surface's EGL_SWAP_BEHAVIOR is EGL_BUFFER_PRESERVED, + * false otherwise + */ + static native boolean isBackBufferPreserved(); + + static native void nDestroyLayer(long layerPtr); + + class DrawPerformanceDataProvider extends GraphDataProvider { + private final int mGraphType; + + private int mVerticalUnit; + private int mHorizontalUnit; + private int mHorizontalMargin; + private int mThresholdStroke; + + DrawPerformanceDataProvider(int graphType) { + mGraphType = graphType; + } + + @Override + void prepare(DisplayMetrics metrics) { + final float density = metrics.density; + + mVerticalUnit = dpToPx(PROFILE_DRAW_DP_PER_MS, density); + mHorizontalUnit = dpToPx(PROFILE_DRAW_WIDTH, density); + mHorizontalMargin = dpToPx(PROFILE_DRAW_MARGIN, density); + mThresholdStroke = dpToPx(PROFILE_DRAW_THRESHOLD_STROKE_WIDTH, density); + } + + @Override + int getGraphType() { + return mGraphType; + } + + @Override + int getVerticalUnitSize() { + return mVerticalUnit; + } + + @Override + int getHorizontalUnitSize() { + return mHorizontalUnit; + } + + @Override + int getHorizontaUnitMargin() { + return mHorizontalMargin; + } + + @Override + float[] getData() { + return mProfileData; + } + + @Override + float getThreshold() { + return 16; + } + + @Override + int getFrameCount() { + return mProfileData.length / PROFILE_FRAME_DATA_COUNT; + } + + @Override + int getElementCount() { + return PROFILE_FRAME_DATA_COUNT; + } + + @Override + int getCurrentFrame() { + return mProfileCurrentFrame / PROFILE_FRAME_DATA_COUNT; + } + + @Override + void setupGraphPaint(Paint paint, int elementIndex) { + paint.setColor(PROFILE_DRAW_COLORS[elementIndex]); + if (mGraphType == GRAPH_TYPE_LINES) paint.setStrokeWidth(mThresholdStroke); + } + + @Override + void setupThresholdPaint(Paint paint) { + paint.setColor(PROFILE_DRAW_THRESHOLD_COLOR); + paint.setStrokeWidth(mThresholdStroke); + } + + @Override + void setupCurrentFramePaint(Paint paint) { + paint.setColor(PROFILE_DRAW_CURRENT_FRAME_COLOR); + if (mGraphType == GRAPH_TYPE_LINES) paint.setStrokeWidth(mThresholdStroke); + } + } +} diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java index 8f40260..26f47f9 100644 --- a/core/java/android/view/HapticFeedbackConstants.java +++ b/core/java/android/view/HapticFeedbackConstants.java @@ -41,6 +41,11 @@ public class HapticFeedbackConstants { public static final int KEYBOARD_TAP = 3; /** + * The user has pressed either an hour or minute tick of a Clock. + */ + public static final int CLOCK_TICK = 4; + + /** * This is a private constant. Feel free to renumber as desired. * @hide */ diff --git a/core/java/android/view/HardwareCanvas.java b/core/java/android/view/HardwareCanvas.java index 10f700c..a3c7b63 100644 --- a/core/java/android/view/HardwareCanvas.java +++ b/core/java/android/view/HardwareCanvas.java @@ -27,7 +27,6 @@ import android.graphics.Rect; * @hide */ public abstract class HardwareCanvas extends Canvas { - private String mName; @Override public boolean isHardwareAccelerated() { @@ -40,33 +39,6 @@ public abstract class HardwareCanvas extends Canvas { } /** - * Specifies the name of this canvas. Naming the canvas is entirely - * optional but can be useful for debugging purposes. - * - * @param name The name of the canvas, can be null - * - * @see #getName() - * - * @hide - */ - public void setName(String name) { - mName = name; - } - - /** - * Returns the name of this canvas. - * - * @return The name of the canvas or null - * - * @see #setName(String) - * - * @hide - */ - public String getName() { - return mName; - } - - /** * Invoked before any drawing operation is performed in this canvas. * * @param dirty The dirty rectangle to update, can be null. @@ -112,16 +84,6 @@ public abstract class HardwareCanvas extends Canvas { public abstract int drawDisplayList(DisplayList displayList, Rect dirty, int flags); /** - * Outputs the specified display list to the log. This method exists for use by - * tools to output display lists for selected nodes to the log. - * - * @param displayList The display list to be logged. - * - * @hide - */ - abstract void outputDisplayList(DisplayList displayList); - - /** * Draws the specified layer onto this canvas. * * @param layer The layer to composite on this canvas diff --git a/core/java/android/view/HardwareLayer.java b/core/java/android/view/HardwareLayer.java index 23383d9..46e2690 100644 --- a/core/java/android/view/HardwareLayer.java +++ b/core/java/android/view/HardwareLayer.java @@ -17,10 +17,10 @@ package android.view; import android.graphics.Bitmap; -import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Rect; +import android.graphics.SurfaceTexture; /** * A hardware layer can be used to render graphics operations into a hardware @@ -28,38 +28,35 @@ import android.graphics.Rect; * would use a Frame Buffer Object (FBO.) The hardware layer can be used as * a drawing cache when a complex set of graphics operations needs to be * drawn several times. + * + * @hide */ -abstract class HardwareLayer { - /** - * Indicates an unknown dimension (width or height.) - */ - static final int DIMENSION_UNDEFINED = -1; - - int mWidth; - int mHeight; - DisplayList mDisplayList; +final class HardwareLayer { + private static final int LAYER_TYPE_TEXTURE = 1; + private static final int LAYER_TYPE_DISPLAY_LIST = 2; - boolean mOpaque; + private HardwareRenderer mRenderer; + private Finalizer mFinalizer; + private DisplayList mDisplayList; + private final int mLayerType; - /** - * Creates a new hardware layer with undefined dimensions. - */ - HardwareLayer() { - this(DIMENSION_UNDEFINED, DIMENSION_UNDEFINED, false); + private HardwareLayer(HardwareRenderer renderer, long deferredUpdater, int type) { + if (renderer == null || deferredUpdater == 0) { + throw new IllegalArgumentException("Either hardware renderer: " + renderer + + " or deferredUpdater: " + deferredUpdater + " is invalid"); + } + mRenderer = renderer; + mLayerType = type; + mFinalizer = new Finalizer(deferredUpdater); + + // Layer is considered initialized at this point, notify the HardwareRenderer + mRenderer.onLayerCreated(this); } - /** - * Creates a new hardware layer at least as large as the supplied - * dimensions. - * - * @param width The minimum width of the layer - * @param height The minimum height of the layer - * @param isOpaque Whether the layer should be opaque or not - */ - HardwareLayer(int width, int height, boolean isOpaque) { - mWidth = width; - mHeight = height; - mOpaque = isOpaque; + private void assertType(int type) { + if (mLayerType != type) { + throw new IllegalAccessError("Method not appropriate for this layer type! " + mLayerType); + } } /** @@ -68,158 +65,244 @@ abstract class HardwareLayer { * @param paint The paint used when the layer is drawn into the destination canvas. * @see View#setLayerPaint(android.graphics.Paint) */ - void setLayerPaint(Paint paint) { } + public void setLayerPaint(Paint paint) { + nSetLayerPaint(mFinalizer.mDeferredUpdater, paint.mNativePaint); + } /** - * Returns the minimum width of the layer. - * - * @return The minimum desired width of the hardware layer + * Indicates whether this layer can be rendered. + * + * @return True if the layer can be rendered into, false otherwise */ - int getWidth() { - return mWidth; + public boolean isValid() { + return mFinalizer != null && mFinalizer.mDeferredUpdater != 0; } /** - * Returns the minimum height of the layer. - * - * @return The minimum desired height of the hardware layer + * Destroys resources without waiting for a GC. */ - int getHeight() { - return mHeight; + public void destroy() { + if (!isValid()) { + // Already destroyed + return; + } + + if (mDisplayList != null) { + mDisplayList.destroyDisplayListData(); + mDisplayList = null; + } + if (mRenderer != null) { + mRenderer.onLayerDestroyed(this); + mRenderer = null; + } + doDestroyLayerUpdater(); } - /** - * Returns the DisplayList for the layer. - * - * @return The DisplayList of the hardware layer - */ - DisplayList getDisplayList() { - return mDisplayList; + public long getDeferredLayerUpdater() { + return mFinalizer.mDeferredUpdater; } /** - * Sets the DisplayList for the layer. + * Destroys the deferred layer updater but not the backing layer. The + * backing layer is instead returned and is the caller's responsibility + * to destroy/recycle as appropriate. * - * @param displayList The new DisplayList for this layer + * It is safe to call this in onLayerDestroyed only */ - void setDisplayList(DisplayList displayList) { - mDisplayList = displayList; + public long detachBackingLayer() { + long backingLayer = nDetachBackingLayer(mFinalizer.mDeferredUpdater); + doDestroyLayerUpdater(); + return backingLayer; } - /** - * Returns whether or not this layer is opaque. - * - * @return True if the layer is opaque, false otherwise - */ - boolean isOpaque() { - return mOpaque; + private void doDestroyLayerUpdater() { + if (mFinalizer != null) { + mFinalizer.destroy(); + mFinalizer = null; + } } - /** - * Sets whether or not this layer should be considered opaque. - * - * @param isOpaque True if the layer is opaque, false otherwise - */ - abstract void setOpaque(boolean isOpaque); + public DisplayList startRecording() { + assertType(LAYER_TYPE_DISPLAY_LIST); - /** - * Indicates whether this layer can be rendered. - * - * @return True if the layer can be rendered into, false otherwise - */ - abstract boolean isValid(); + if (mDisplayList == null) { + mDisplayList = DisplayList.create("HardwareLayer"); + } + return mDisplayList; + } - /** - * Resize the layer, if necessary, to be at least as large - * as the supplied dimensions. - * - * @param width The new desired minimum width for this layer - * @param height The new desired minimum height for this layer - * @return True if the resulting layer is valid, false otherwise - */ - abstract boolean resize(int width, int height); + public void endRecording(Rect dirtyRect) { + nUpdateRenderLayer(mFinalizer.mDeferredUpdater, mDisplayList.getNativeDisplayList(), + dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom); + mRenderer.pushLayerUpdate(this); + } /** - * Returns a hardware canvas that can be used to render onto - * this layer. - * - * @return A hardware canvas, or null if a canvas cannot be created + * Copies this layer into the specified bitmap. * - * @see #start(android.graphics.Canvas) - * @see #end(android.graphics.Canvas) - */ - abstract HardwareCanvas getCanvas(); - - /** - * Destroys resources without waiting for a GC. + * @param bitmap The bitmap to copy they layer into + * + * @return True if the copy was successful, false otherwise */ - abstract void destroy(); + public boolean copyInto(Bitmap bitmap) { + return mRenderer.copyLayerInto(this, bitmap); + } /** - * This must be invoked before drawing onto this layer. + * Update the layer's properties. Note that after calling this isValid() may + * return false if the requested width/height cannot be satisfied + * + * @param width The new width of this layer + * @param height The new height of this layer + * @param isOpaque Whether this layer is opaque * - * @param currentCanvas The canvas whose rendering needs to be interrupted + * @return true if the layer's properties will change, false if they already + * match the desired values. */ - abstract HardwareCanvas start(Canvas currentCanvas); + public boolean prepare(int width, int height, boolean isOpaque) { + return nPrepare(mFinalizer.mDeferredUpdater, width, height, isOpaque); + } /** - * This must be invoked before drawing onto this layer. + * Sets an optional transform on this layer. * - * @param dirty The dirty area to repaint - * @param currentCanvas The canvas whose rendering needs to be interrupted + * @param matrix The transform to apply to the layer. */ - abstract HardwareCanvas start(Canvas currentCanvas, Rect dirty); + public void setTransform(Matrix matrix) { + nSetTransform(mFinalizer.mDeferredUpdater, matrix.native_instance); + } /** - * This must be invoked after drawing onto this layer. - * - * @param currentCanvas The canvas whose rendering needs to be resumed + * Indicates that this layer has lost its texture. */ - abstract void end(Canvas currentCanvas); + public void detachSurfaceTexture(final SurfaceTexture surface) { + assertType(LAYER_TYPE_TEXTURE); + mRenderer.safelyRun(new Runnable() { + @Override + public void run() { + surface.detachFromGLContext(); + // SurfaceTexture owns the texture name and detachFromGLContext + // should have deleted it + nOnTextureDestroyed(mFinalizer.mDeferredUpdater); + } + }); + } /** - * Copies this layer into the specified bitmap. - * - * @param bitmap The bitmap to copy they layer into - * - * @return True if the copy was successful, false otherwise + * This exists to minimize impact into the current HardwareLayer paths as + * some of the specifics of how to handle error cases in the fully + * deferred model will work */ - abstract boolean copyInto(Bitmap bitmap); + @Deprecated + public void flushChanges() { + if (HardwareRenderer.sUseRenderThread) { + // Not supported, don't try. + return; + } + + boolean success = nFlushChanges(mFinalizer.mDeferredUpdater); + if (!success) { + destroy(); + } + } + + public long getLayer() { + return nGetLayer(mFinalizer.mDeferredUpdater); + } + + public void setSurfaceTexture(SurfaceTexture surface) { + assertType(LAYER_TYPE_TEXTURE); + nSetSurfaceTexture(mFinalizer.mDeferredUpdater, surface, false); + } + + public void updateSurfaceTexture() { + assertType(LAYER_TYPE_TEXTURE); + nUpdateSurfaceTexture(mFinalizer.mDeferredUpdater); + } /** - * Update the layer's properties. This method should be used - * when the underlying storage is modified by an external entity. - * To change the underlying storage, use the {@link #resize(int, int)} - * method instead. - * - * @param width The new width of this layer - * @param height The new height of this layer - * @param isOpaque Whether this layer is opaque + * This should only be used by HardwareRenderer! Do not call directly */ - void update(int width, int height, boolean isOpaque) { - mWidth = width; - mHeight = height; - mOpaque = isOpaque; + SurfaceTexture createSurfaceTexture() { + assertType(LAYER_TYPE_TEXTURE); + SurfaceTexture st = new SurfaceTexture(nGetTexName(mFinalizer.mDeferredUpdater)); + nSetSurfaceTexture(mFinalizer.mDeferredUpdater, st, true); + return st; } /** - * Sets an optional transform on this layer. - * - * @param matrix The transform to apply to the layer. + * This should only be used by HardwareRenderer! Do not call directly */ - abstract void setTransform(Matrix matrix); + static HardwareLayer createTextureLayer(HardwareRenderer renderer) { + return new HardwareLayer(renderer, nCreateTextureLayer(), LAYER_TYPE_TEXTURE); + } + + static HardwareLayer adoptTextureLayer(HardwareRenderer renderer, long layer) { + return new HardwareLayer(renderer, layer, LAYER_TYPE_TEXTURE); + } /** - * Specifies the display list to use to refresh the layer. - * - * @param displayList The display list containing the drawing commands to - * execute in this layer - * @param dirtyRect The dirty region of the layer that needs to be redrawn + * This should only be used by HardwareRenderer! Do not call directly */ - abstract void redrawLater(DisplayList displayList, Rect dirtyRect); + static HardwareLayer createDisplayListLayer(HardwareRenderer renderer, + int width, int height) { + return new HardwareLayer(renderer, nCreateRenderLayer(width, height), LAYER_TYPE_DISPLAY_LIST); + } - /** - * Indicates that this layer has lost its underlying storage. + static HardwareLayer adoptDisplayListLayer(HardwareRenderer renderer, long layer) { + return new HardwareLayer(renderer, layer, LAYER_TYPE_DISPLAY_LIST); + } + + /** This also creates the underlying layer */ + private static native long nCreateTextureLayer(); + private static native long nCreateRenderLayer(int width, int height); + + private static native void nOnTextureDestroyed(long layerUpdater); + private static native long nDetachBackingLayer(long layerUpdater); + + /** This also destroys the underlying layer if it is still attached. + * Note it does not recycle the underlying layer, but instead queues it + * for deferred deletion. + * The HardwareRenderer should use detachBackingLayer() in the + * onLayerDestroyed() callback to do recycling if desired. */ - abstract void clearStorage(); + private static native void nDestroyLayerUpdater(long layerUpdater); + + private static native boolean nPrepare(long layerUpdater, int width, int height, boolean isOpaque); + private static native void nSetLayerPaint(long layerUpdater, long paint); + private static native void nSetTransform(long layerUpdater, long matrix); + private static native void nSetSurfaceTexture(long layerUpdater, + SurfaceTexture surface, boolean isAlreadyAttached); + private static native void nUpdateSurfaceTexture(long layerUpdater); + private static native void nUpdateRenderLayer(long layerUpdater, long displayList, + int left, int top, int right, int bottom); + + private static native boolean nFlushChanges(long layerUpdater); + + private static native long nGetLayer(long layerUpdater); + private static native int nGetTexName(long layerUpdater); + + private static class Finalizer { + private long mDeferredUpdater; + + public Finalizer(long deferredUpdater) { + mDeferredUpdater = deferredUpdater; + } + + @Override + protected void finalize() throws Throwable { + try { + destroy(); + } finally { + super.finalize(); + } + } + + void destroy() { + if (mDeferredUpdater != 0) { + nDestroyLayerUpdater(mDeferredUpdater); + mDeferredUpdater = 0; + } + } + } } diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index f09a111..34efcf5 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -16,41 +16,15 @@ package android.view; -import android.content.ComponentCallbacks2; -import android.graphics.Color; +import android.graphics.Bitmap; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.SurfaceTexture; -import android.opengl.EGL14; -import android.opengl.GLUtils; -import android.opengl.ManagedEGLContext; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.SystemClock; -import android.os.SystemProperties; -import android.os.Trace; import android.util.DisplayMetrics; -import android.util.Log; import android.view.Surface.OutOfResourcesException; -import com.google.android.gles_jni.EGLImpl; - -import javax.microedition.khronos.egl.EGL10; -import javax.microedition.khronos.egl.EGL11; -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.egl.EGLContext; -import javax.microedition.khronos.egl.EGLDisplay; -import javax.microedition.khronos.egl.EGLSurface; -import javax.microedition.khronos.opengles.GL; - import java.io.File; import java.io.PrintWriter; -import java.util.concurrent.locks.ReentrantLock; - -import static javax.microedition.khronos.egl.EGL10.*; /** * Interface for rendering a view hierarchy using hardware acceleration. @@ -66,13 +40,6 @@ public abstract class HardwareRenderer { private static final String CACHE_PATH_SHADERS = "com.android.opengl.shaders_cache"; /** - * Turn on to only refresh the parts of the screen that need updating. - * When turned on the property defined by {@link #RENDER_DIRTY_REGIONS_PROPERTY} - * must also have the value "true". - */ - static final boolean RENDER_DIRTY_REGIONS = true; - - /** * System property used to enable or disable dirty regions invalidation. * This property is only queried if {@link #RENDER_DIRTY_REGIONS} is true. * The default value of this property is assumed to be true. @@ -187,14 +154,6 @@ public abstract class HardwareRenderer { public static final String OVERDRAW_PROPERTY_SHOW = "show"; /** - * Value for {@link #DEBUG_OVERDRAW_PROPERTY}. When the property is set to this - * value, an overdraw counter will be shown on screen. - * - * @hide - */ - public static final String OVERDRAW_PROPERTY_COUNT = "count"; - - /** * Turn on to debug non-rectangular clip operations. * * Possible values: @@ -222,15 +181,8 @@ public abstract class HardwareRenderer { */ public static boolean sSystemRendererDisabled = false; - /** - * Number of frames to profile. - */ - private static final int PROFILE_MAX_FRAMES = 128; - - /** - * Number of floats per profiled frame. - */ - private static final int PROFILE_FRAME_DATA_COUNT = 3; + /** @hide */ + public static boolean sUseRenderThread = false; private boolean mEnabled; private boolean mRequested = true; @@ -282,13 +234,6 @@ public abstract class HardwareRenderer { abstract void updateSurface(Surface surface) throws OutOfResourcesException; /** - * Destroys the layers used by the specified view hierarchy. - * - * @param view The root of the view hierarchy - */ - abstract void destroyLayers(View view); - - /** * Destroys all hardware rendering resources associated with the specified * view hierarchy. * @@ -305,15 +250,6 @@ public abstract class HardwareRenderer { abstract void invalidate(Surface surface); /** - * This method should be invoked to ensure the hardware renderer is in - * valid state (for instance, to ensure the correct EGL context is bound - * to the current thread.) - * - * @return true if the renderer is now valid, false otherwise - */ - abstract boolean validate(); - - /** * This method ensures the hardware renderer is in a valid state * before executing the specified action. * @@ -350,13 +286,6 @@ public abstract class HardwareRenderer { abstract int getHeight(); /** - * Gets the current canvas associated with this HardwareRenderer. - * - * @return the current HardwareCanvas - */ - abstract HardwareCanvas getCanvas(); - - /** * Outputs extra debugging information in the specified file descriptor. * @param pw */ @@ -379,9 +308,7 @@ public abstract class HardwareRenderer { * * @return True if a property has changed. */ - abstract boolean loadSystemProperties(Surface surface); - - private static native boolean nLoadProperties(); + abstract boolean loadSystemProperties(); /** * Sets the directory to use as a persistent storage for hardware rendering @@ -392,60 +319,9 @@ public abstract class HardwareRenderer { * @hide */ public static void setupDiskCache(File cacheDir) { - nSetupShadersDiskCache(new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath()); + GLRenderer.setupShadersDiskCache(new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath()); } - private static native void nSetupShadersDiskCache(String cacheFile); - - /** - * Notifies EGL that the frame is about to be rendered. - * @param size - */ - static void beginFrame(int[] size) { - nBeginFrame(size); - } - - private static native void nBeginFrame(int[] size); - - /** - * Returns the current system time according to the renderer. - * This method is used for debugging only and should not be used - * as a clock. - */ - static long getSystemTime() { - return nGetSystemTime(); - } - - private static native long nGetSystemTime(); - - /** - * Preserves the back buffer of the current surface after a buffer swap. - * Calling this method sets the EGL_SWAP_BEHAVIOR attribute of the current - * surface to EGL_BUFFER_PRESERVED. Calling this method requires an EGL - * config that supports EGL_SWAP_BEHAVIOR_PRESERVED_BIT. - * - * @return True if the swap behavior was successfully changed, - * false otherwise. - */ - static boolean preserveBackBuffer() { - return nPreserveBackBuffer(); - } - - private static native boolean nPreserveBackBuffer(); - - /** - * Indicates whether the current surface preserves its back buffer - * after a buffer swap. - * - * @return True, if the surface's EGL_SWAP_BEHAVIOR is EGL_BUFFER_PRESERVED, - * false otherwise - */ - static boolean isBackBufferPreserved() { - return nIsBackBufferPreserved(); - } - - private static native boolean nIsBackBufferPreserved(); - /** * Indicates that the specified hardware layer needs to be updated * as soon as possible. @@ -453,19 +329,20 @@ public abstract class HardwareRenderer { * @param layer The hardware layer that needs an update * * @see #flushLayerUpdates() - * @see #cancelLayerUpdate(HardwareLayer) */ abstract void pushLayerUpdate(HardwareLayer layer); /** - * Cancels a queued layer update. If the specified layer was not - * queued for update, this method has no effect. - * - * @param layer The layer whose update to cancel - * - * @see #pushLayerUpdate(HardwareLayer) + * Tells the HardwareRenderer that a layer was created. The renderer should + * make sure to apply any pending layer changes at the start of a new frame */ - abstract void cancelLayerUpdate(HardwareLayer layer); + abstract void onLayerCreated(HardwareLayer hardwareLayer); + + /** + * Tells the HardwareRenderer that the layer is destroyed. The renderer + * should remove the layer from any update queues. + */ + abstract void onLayerDestroyed(HardwareLayer layer); /** * Forces all enqueued layer updates to be executed immediately. @@ -509,37 +386,22 @@ public abstract class HardwareRenderer { Rect dirty); /** - * Creates a new display list that can be used to record batches of - * drawing operations. - * - * @param name The name of the display list, used for debugging purpose. May be null. - * - * @return A new display list. - * - * @hide - */ - public abstract DisplayList createDisplayList(String name); - - /** * Creates a new hardware layer. A hardware layer built by calling this * method will be treated as a texture layer, instead of as a render target. * - * @param isOpaque Whether the layer should be opaque or not - * * @return A hardware layer */ - abstract HardwareLayer createHardwareLayer(boolean isOpaque); + abstract HardwareLayer createTextureLayer(); /** * Creates a new hardware layer. * * @param width The minimum width of the layer * @param height The minimum height of the layer - * @param isOpaque Whether the layer should be opaque or not * * @return A hardware layer */ - abstract HardwareLayer createHardwareLayer(int width, int height, boolean isOpaque); + abstract HardwareLayer createDisplayListLayer(int width, int height); /** * Creates a new {@link SurfaceTexture} that can be used to render into the @@ -551,14 +413,7 @@ public abstract class HardwareRenderer { */ abstract SurfaceTexture createSurfaceTexture(HardwareLayer layer); - /** - * Sets the {@link android.graphics.SurfaceTexture} that will be used to - * render into the specified hardware layer. - * - * @param layer The layer to render into using a {@link android.graphics.SurfaceTexture} - * @param surfaceTexture The {@link android.graphics.SurfaceTexture} to use for the layer - */ - abstract void setSurfaceTexture(HardwareLayer layer, SurfaceTexture surfaceTexture); + abstract boolean copyLayerInto(HardwareLayer layer, Bitmap bitmap); /** * Detaches the specified functor from the current functor execution queue. @@ -566,7 +421,7 @@ public abstract class HardwareRenderer { * @param functor The native functor to remove from the execution queue. * * @see HardwareCanvas#callDrawGLFunction(int) - * @see #attachFunctor(android.view.View.AttachInfo, int) + * @see #attachFunctor(android.view.View.AttachInfo, long) */ abstract void detachFunctor(long functor); @@ -577,11 +432,10 @@ public abstract class HardwareRenderer { * @param functor The native functor to insert in the execution queue. * * @see HardwareCanvas#callDrawGLFunction(int) - * @see #detachFunctor(int) + * @see #detachFunctor(long) * - * @return true if the functor was attached successfully */ - abstract boolean attachFunctor(View.AttachInfo attachInfo, long functor); + abstract void attachFunctor(View.AttachInfo attachInfo, long functor); /** * Initializes the hardware renderer for the specified surface and setup the @@ -621,17 +475,20 @@ public abstract class HardwareRenderer { /** * Creates a hardware renderer using OpenGL. * - * @param glVersion The version of OpenGL to use (1 for OpenGL 1, 11 for OpenGL 1.1, etc.) * @param translucent True if the surface is translucent, false otherwise * * @return A hardware renderer backed by OpenGL. */ - static HardwareRenderer createGlRenderer(int glVersion, boolean translucent) { - switch (glVersion) { - case 2: - return Gl20Renderer.create(translucent); + static HardwareRenderer create(boolean translucent) { + HardwareRenderer renderer = null; + if (GLES20Canvas.isAvailable()) { + if (sUseRenderThread) { + renderer = new ThreadedRenderer(translucent); + } else { + renderer = new GLRenderer(translucent); + } } - throw new IllegalArgumentException("Unknown GL version: " + glVersion); + return renderer; } /** @@ -656,7 +513,7 @@ public abstract class HardwareRenderer { * see {@link android.content.ComponentCallbacks} */ static void startTrimMemory(int level) { - Gl20Renderer.startTrimMemory(level); + GLRenderer.startTrimMemory(level); } /** @@ -664,7 +521,7 @@ public abstract class HardwareRenderer { * cleanup special resources used by the memory trimming process. */ static void endTrimMemory() { - Gl20Renderer.endTrimMemory(); + GLRenderer.endTrimMemory(); } /** @@ -705,6 +562,8 @@ public abstract class HardwareRenderer { mRequested = requested; } + abstract void setDisplayListData(long displayList, long newData); + /** * Describes a series of frames that should be drawn on screen as a graph. * Each frame is composed of 1 or more elements. @@ -798,1553 +657,4 @@ public abstract class HardwareRenderer { */ abstract void setupCurrentFramePaint(Paint paint); } - - @SuppressWarnings({"deprecation"}) - static abstract class GlRenderer extends HardwareRenderer { - static final int SURFACE_STATE_ERROR = 0; - static final int SURFACE_STATE_SUCCESS = 1; - static final int SURFACE_STATE_UPDATED = 2; - - static final int FUNCTOR_PROCESS_DELAY = 4; - - private static final int PROFILE_DRAW_MARGIN = 0; - private static final int PROFILE_DRAW_WIDTH = 3; - private static final int[] PROFILE_DRAW_COLORS = { 0xcf3e66cc, 0xcfdc3912, 0xcfe69800 }; - private static final int PROFILE_DRAW_CURRENT_FRAME_COLOR = 0xcf5faa4d; - private static final int PROFILE_DRAW_THRESHOLD_COLOR = 0xff5faa4d; - private static final int PROFILE_DRAW_THRESHOLD_STROKE_WIDTH = 2; - private static final int PROFILE_DRAW_DP_PER_MS = 7; - - private static final String[] VISUALIZERS = { - PROFILE_PROPERTY_VISUALIZE_BARS, - PROFILE_PROPERTY_VISUALIZE_LINES - }; - - private static final String[] OVERDRAW = { - OVERDRAW_PROPERTY_SHOW, - OVERDRAW_PROPERTY_COUNT - }; - private static final int OVERDRAW_TYPE_COUNT = 1; - - static EGL10 sEgl; - static EGLDisplay sEglDisplay; - static EGLConfig sEglConfig; - static final Object[] sEglLock = new Object[0]; - int mWidth = -1, mHeight = -1; - - static final ThreadLocal<ManagedEGLContext> sEglContextStorage - = new ThreadLocal<ManagedEGLContext>(); - - EGLContext mEglContext; - Thread mEglThread; - - EGLSurface mEglSurface; - - GL mGl; - HardwareCanvas mCanvas; - - String mName; - - long mFrameCount; - Paint mDebugPaint; - - static boolean sDirtyRegions; - static final boolean sDirtyRegionsRequested; - static { - String dirtyProperty = SystemProperties.get(RENDER_DIRTY_REGIONS_PROPERTY, "true"); - //noinspection PointlessBooleanExpression,ConstantConditions - sDirtyRegions = RENDER_DIRTY_REGIONS && "true".equalsIgnoreCase(dirtyProperty); - sDirtyRegionsRequested = sDirtyRegions; - } - - boolean mDirtyRegionsEnabled; - boolean mUpdateDirtyRegions; - - boolean mProfileEnabled; - int mProfileVisualizerType = -1; - float[] mProfileData; - ReentrantLock mProfileLock; - int mProfileCurrentFrame = -PROFILE_FRAME_DATA_COUNT; - - GraphDataProvider mDebugDataProvider; - float[][] mProfileShapes; - Paint mProfilePaint; - - boolean mDebugDirtyRegions; - int mDebugOverdraw = -1; - HardwareLayer mDebugOverdrawLayer; - Paint mDebugOverdrawPaint; - - final int mGlVersion; - final boolean mTranslucent; - - private boolean mDestroyed; - - private final Rect mRedrawClip = new Rect(); - - private final int[] mSurfaceSize = new int[2]; - private final FunctorsRunnable mFunctorsRunnable = new FunctorsRunnable(); - - private long mDrawDelta = Long.MAX_VALUE; - - GlRenderer(int glVersion, boolean translucent) { - mGlVersion = glVersion; - mTranslucent = translucent; - - loadSystemProperties(null); - } - - @Override - boolean loadSystemProperties(Surface surface) { - boolean value; - boolean changed = false; - - String profiling = SystemProperties.get(PROFILE_PROPERTY); - int graphType = search(VISUALIZERS, profiling); - value = graphType >= 0; - - if (graphType != mProfileVisualizerType) { - changed = true; - mProfileVisualizerType = graphType; - - mProfileShapes = null; - mProfilePaint = null; - - if (value) { - mDebugDataProvider = new DrawPerformanceDataProvider(graphType); - } else { - mDebugDataProvider = null; - } - } - - // If on-screen profiling is not enabled, we need to check whether - // console profiling only is enabled - if (!value) { - value = Boolean.parseBoolean(profiling); - } - - if (value != mProfileEnabled) { - changed = true; - mProfileEnabled = value; - - if (mProfileEnabled) { - Log.d(LOG_TAG, "Profiling hardware renderer"); - - int maxProfileFrames = SystemProperties.getInt(PROFILE_MAXFRAMES_PROPERTY, - PROFILE_MAX_FRAMES); - mProfileData = new float[maxProfileFrames * PROFILE_FRAME_DATA_COUNT]; - for (int i = 0; i < mProfileData.length; i += PROFILE_FRAME_DATA_COUNT) { - mProfileData[i] = mProfileData[i + 1] = mProfileData[i + 2] = -1; - } - - mProfileLock = new ReentrantLock(); - } else { - mProfileData = null; - mProfileLock = null; - mProfileVisualizerType = -1; - } - - mProfileCurrentFrame = -PROFILE_FRAME_DATA_COUNT; - } - - value = SystemProperties.getBoolean(DEBUG_DIRTY_REGIONS_PROPERTY, false); - if (value != mDebugDirtyRegions) { - changed = true; - mDebugDirtyRegions = value; - - if (mDebugDirtyRegions) { - Log.d(LOG_TAG, "Debugging dirty regions"); - } - } - - String overdraw = SystemProperties.get(HardwareRenderer.DEBUG_OVERDRAW_PROPERTY); - int debugOverdraw = search(OVERDRAW, overdraw); - if (debugOverdraw != mDebugOverdraw) { - changed = true; - mDebugOverdraw = debugOverdraw; - - if (mDebugOverdraw != OVERDRAW_TYPE_COUNT) { - if (mDebugOverdrawLayer != null) { - mDebugOverdrawLayer.destroy(); - mDebugOverdrawLayer = null; - mDebugOverdrawPaint = null; - } - } - } - - if (nLoadProperties()) { - changed = true; - } - - return changed; - } - - private static int search(String[] values, String value) { - for (int i = 0; i < values.length; i++) { - if (values[i].equals(value)) return i; - } - return -1; - } - - @Override - void dumpGfxInfo(PrintWriter pw) { - if (mProfileEnabled) { - pw.printf("\n\tDraw\tProcess\tExecute\n"); - - mProfileLock.lock(); - try { - for (int i = 0; i < mProfileData.length; i += PROFILE_FRAME_DATA_COUNT) { - if (mProfileData[i] < 0) { - break; - } - pw.printf("\t%3.2f\t%3.2f\t%3.2f\n", mProfileData[i], mProfileData[i + 1], - mProfileData[i + 2]); - mProfileData[i] = mProfileData[i + 1] = mProfileData[i + 2] = -1; - } - mProfileCurrentFrame = mProfileData.length; - } finally { - mProfileLock.unlock(); - } - } - } - - @Override - long getFrameCount() { - return mFrameCount; - } - - /** - * Indicates whether this renderer instance can track and update dirty regions. - */ - boolean hasDirtyRegions() { - return mDirtyRegionsEnabled; - } - - /** - * Checks for OpenGL errors. If an error has occured, {@link #destroy(boolean)} - * is invoked and the requested flag is turned off. The error code is - * also logged as a warning. - */ - void checkEglErrors() { - if (isEnabled()) { - checkEglErrorsForced(); - } - } - - private void checkEglErrorsForced() { - int error = sEgl.eglGetError(); - if (error != EGL_SUCCESS) { - // something bad has happened revert to - // normal rendering. - Log.w(LOG_TAG, "EGL error: " + GLUtils.getEGLErrorString(error)); - fallback(error != EGL11.EGL_CONTEXT_LOST); - } - } - - private void fallback(boolean fallback) { - destroy(true); - if (fallback) { - // we'll try again if it was context lost - setRequested(false); - Log.w(LOG_TAG, "Mountain View, we've had a problem here. " - + "Switching back to software rendering."); - } - } - - @Override - boolean initialize(Surface surface) throws OutOfResourcesException { - if (isRequested() && !isEnabled()) { - boolean contextCreated = initializeEgl(); - mGl = createEglSurface(surface); - mDestroyed = false; - - if (mGl != null) { - int err = sEgl.eglGetError(); - if (err != EGL_SUCCESS) { - destroy(true); - setRequested(false); - } else { - if (mCanvas == null) { - mCanvas = createCanvas(); - mCanvas.setName(mName); - } - setEnabled(true); - - if (contextCreated) { - initAtlas(); - } - } - - return mCanvas != null; - } - } - return false; - } - - @Override - void updateSurface(Surface surface) throws OutOfResourcesException { - if (isRequested() && isEnabled()) { - createEglSurface(surface); - } - } - - abstract HardwareCanvas createCanvas(); - - abstract int[] getConfig(boolean dirtyRegions); - - boolean initializeEgl() { - synchronized (sEglLock) { - if (sEgl == null && sEglConfig == null) { - sEgl = (EGL10) EGLContext.getEGL(); - - // Get to the default display. - sEglDisplay = sEgl.eglGetDisplay(EGL_DEFAULT_DISPLAY); - - if (sEglDisplay == EGL_NO_DISPLAY) { - throw new RuntimeException("eglGetDisplay failed " - + GLUtils.getEGLErrorString(sEgl.eglGetError())); - } - - // We can now initialize EGL for that display - int[] version = new int[2]; - if (!sEgl.eglInitialize(sEglDisplay, version)) { - throw new RuntimeException("eglInitialize failed " + - GLUtils.getEGLErrorString(sEgl.eglGetError())); - } - - checkEglErrorsForced(); - - sEglConfig = loadEglConfig(); - } - } - - ManagedEGLContext managedContext = sEglContextStorage.get(); - mEglContext = managedContext != null ? managedContext.getContext() : null; - mEglThread = Thread.currentThread(); - - if (mEglContext == null) { - mEglContext = createContext(sEgl, sEglDisplay, sEglConfig); - sEglContextStorage.set(createManagedContext(mEglContext)); - return true; - } - - return false; - } - - private EGLConfig loadEglConfig() { - EGLConfig eglConfig = chooseEglConfig(); - if (eglConfig == null) { - // We tried to use EGL_SWAP_BEHAVIOR_PRESERVED_BIT, try again without - if (sDirtyRegions) { - sDirtyRegions = false; - eglConfig = chooseEglConfig(); - if (eglConfig == null) { - throw new RuntimeException("eglConfig not initialized"); - } - } else { - throw new RuntimeException("eglConfig not initialized"); - } - } - return eglConfig; - } - - abstract ManagedEGLContext createManagedContext(EGLContext eglContext); - - private EGLConfig chooseEglConfig() { - EGLConfig[] configs = new EGLConfig[1]; - int[] configsCount = new int[1]; - int[] configSpec = getConfig(sDirtyRegions); - - // Debug - final String debug = SystemProperties.get(PRINT_CONFIG_PROPERTY, ""); - if ("all".equalsIgnoreCase(debug)) { - sEgl.eglChooseConfig(sEglDisplay, configSpec, null, 0, configsCount); - - EGLConfig[] debugConfigs = new EGLConfig[configsCount[0]]; - sEgl.eglChooseConfig(sEglDisplay, configSpec, debugConfigs, - configsCount[0], configsCount); - - for (EGLConfig config : debugConfigs) { - printConfig(config); - } - } - - if (!sEgl.eglChooseConfig(sEglDisplay, configSpec, configs, 1, configsCount)) { - throw new IllegalArgumentException("eglChooseConfig failed " + - GLUtils.getEGLErrorString(sEgl.eglGetError())); - } else if (configsCount[0] > 0) { - if ("choice".equalsIgnoreCase(debug)) { - printConfig(configs[0]); - } - return configs[0]; - } - - return null; - } - - private static void printConfig(EGLConfig config) { - int[] value = new int[1]; - - Log.d(LOG_TAG, "EGL configuration " + config + ":"); - - sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_RED_SIZE, value); - Log.d(LOG_TAG, " RED_SIZE = " + value[0]); - - sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_GREEN_SIZE, value); - Log.d(LOG_TAG, " GREEN_SIZE = " + value[0]); - - sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_BLUE_SIZE, value); - Log.d(LOG_TAG, " BLUE_SIZE = " + value[0]); - - sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_ALPHA_SIZE, value); - Log.d(LOG_TAG, " ALPHA_SIZE = " + value[0]); - - sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_DEPTH_SIZE, value); - Log.d(LOG_TAG, " DEPTH_SIZE = " + value[0]); - - sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_STENCIL_SIZE, value); - Log.d(LOG_TAG, " STENCIL_SIZE = " + value[0]); - - sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_SAMPLE_BUFFERS, value); - Log.d(LOG_TAG, " SAMPLE_BUFFERS = " + value[0]); - - sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_SAMPLES, value); - Log.d(LOG_TAG, " SAMPLES = " + value[0]); - - sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_SURFACE_TYPE, value); - Log.d(LOG_TAG, " SURFACE_TYPE = 0x" + Integer.toHexString(value[0])); - - sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_CONFIG_CAVEAT, value); - Log.d(LOG_TAG, " CONFIG_CAVEAT = 0x" + Integer.toHexString(value[0])); - } - - GL createEglSurface(Surface surface) throws OutOfResourcesException { - // Check preconditions. - if (sEgl == null) { - throw new RuntimeException("egl not initialized"); - } - if (sEglDisplay == null) { - throw new RuntimeException("eglDisplay not initialized"); - } - if (sEglConfig == null) { - throw new RuntimeException("eglConfig not initialized"); - } - if (Thread.currentThread() != mEglThread) { - throw new IllegalStateException("HardwareRenderer cannot be used " - + "from multiple threads"); - } - - // In case we need to destroy an existing surface - destroySurface(); - - // Create an EGL surface we can render into. - if (!createSurface(surface)) { - return null; - } - - initCaches(); - - return mEglContext.getGL(); - } - - private void enableDirtyRegions() { - // If mDirtyRegions is set, this means we have an EGL configuration - // with EGL_SWAP_BEHAVIOR_PRESERVED_BIT set - if (sDirtyRegions) { - if (!(mDirtyRegionsEnabled = preserveBackBuffer())) { - Log.w(LOG_TAG, "Backbuffer cannot be preserved"); - } - } else if (sDirtyRegionsRequested) { - // If mDirtyRegions is not set, our EGL configuration does not - // have EGL_SWAP_BEHAVIOR_PRESERVED_BIT; however, the default - // swap behavior might be EGL_BUFFER_PRESERVED, which means we - // want to set mDirtyRegions. We try to do this only if dirty - // regions were initially requested as part of the device - // configuration (see RENDER_DIRTY_REGIONS) - mDirtyRegionsEnabled = isBackBufferPreserved(); - } - } - - abstract void initCaches(); - abstract void initAtlas(); - - EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) { - int[] attribs = { EGL14.EGL_CONTEXT_CLIENT_VERSION, mGlVersion, EGL_NONE }; - - EGLContext context = egl.eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, - mGlVersion != 0 ? attribs : null); - if (context == null || context == EGL_NO_CONTEXT) { - //noinspection ConstantConditions - throw new IllegalStateException( - "Could not create an EGL context. eglCreateContext failed with error: " + - GLUtils.getEGLErrorString(sEgl.eglGetError())); - } - - return context; - } - - @Override - void destroy(boolean full) { - if (full && mCanvas != null) { - mCanvas = null; - } - - if (!isEnabled() || mDestroyed) { - setEnabled(false); - return; - } - - destroySurface(); - setEnabled(false); - - mDestroyed = true; - mGl = null; - } - - void destroySurface() { - if (mEglSurface != null && mEglSurface != EGL_NO_SURFACE) { - if (mEglSurface.equals(sEgl.eglGetCurrentSurface(EGL_DRAW))) { - sEgl.eglMakeCurrent(sEglDisplay, - EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - } - sEgl.eglDestroySurface(sEglDisplay, mEglSurface); - mEglSurface = null; - } - } - - @Override - void invalidate(Surface surface) { - // Cancels any existing buffer to ensure we'll get a buffer - // of the right size before we call eglSwapBuffers - sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - - if (mEglSurface != null && mEglSurface != EGL_NO_SURFACE) { - sEgl.eglDestroySurface(sEglDisplay, mEglSurface); - mEglSurface = null; - setEnabled(false); - } - - if (surface.isValid()) { - if (!createSurface(surface)) { - return; - } - - mUpdateDirtyRegions = true; - - if (mCanvas != null) { - setEnabled(true); - } - } - } - - private boolean createSurface(Surface surface) { - mEglSurface = sEgl.eglCreateWindowSurface(sEglDisplay, sEglConfig, surface, null); - - if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) { - int error = sEgl.eglGetError(); - if (error == EGL_BAD_NATIVE_WINDOW) { - Log.e(LOG_TAG, "createWindowSurface returned EGL_BAD_NATIVE_WINDOW."); - return false; - } - throw new RuntimeException("createWindowSurface failed " - + GLUtils.getEGLErrorString(error)); - } - - if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, mEglContext)) { - throw new IllegalStateException("eglMakeCurrent failed " + - GLUtils.getEGLErrorString(sEgl.eglGetError())); - } - - enableDirtyRegions(); - - return true; - } - - @Override - boolean validate() { - return checkRenderContext() != SURFACE_STATE_ERROR; - } - - @Override - void setup(int width, int height) { - if (validate()) { - mCanvas.setViewport(width, height); - mWidth = width; - mHeight = height; - } - } - - @Override - int getWidth() { - return mWidth; - } - - @Override - int getHeight() { - return mHeight; - } - - @Override - HardwareCanvas getCanvas() { - return mCanvas; - } - - @Override - void setName(String name) { - mName = name; - } - - boolean canDraw() { - return mGl != null && mCanvas != null; - } - - int onPreDraw(Rect dirty) { - return DisplayList.STATUS_DONE; - } - - void onPostDraw() { - } - - class FunctorsRunnable implements Runnable { - View.AttachInfo attachInfo; - - @Override - public void run() { - final HardwareRenderer renderer = attachInfo.mHardwareRenderer; - if (renderer == null || !renderer.isEnabled() || renderer != GlRenderer.this) { - return; - } - - if (checkRenderContext() != SURFACE_STATE_ERROR) { - int status = mCanvas.invokeFunctors(mRedrawClip); - handleFunctorStatus(attachInfo, status); - } - } - } - - @Override - void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks, - Rect dirty) { - if (canDraw()) { - if (!hasDirtyRegions()) { - dirty = null; - } - attachInfo.mIgnoreDirtyState = true; - attachInfo.mDrawingTime = SystemClock.uptimeMillis(); - - view.mPrivateFlags |= View.PFLAG_DRAWN; - - // We are already on the correct thread - final int surfaceState = checkRenderContextUnsafe(); - if (surfaceState != SURFACE_STATE_ERROR) { - HardwareCanvas canvas = mCanvas; - attachInfo.mHardwareCanvas = canvas; - - if (mProfileEnabled) { - mProfileLock.lock(); - } - - dirty = beginFrame(canvas, dirty, surfaceState); - - DisplayList displayList = buildDisplayList(view, canvas); - - // buildDisplayList() calls into user code which can cause - // an eglMakeCurrent to happen with a different surface/context. - // We must therefore check again here. - if (checkRenderContextUnsafe() == SURFACE_STATE_ERROR) { - return; - } - - int saveCount = 0; - int status = DisplayList.STATUS_DONE; - - long start = getSystemTime(); - try { - status = prepareFrame(dirty); - - saveCount = canvas.save(); - callbacks.onHardwarePreDraw(canvas); - - if (displayList != null) { - status |= drawDisplayList(attachInfo, canvas, displayList, status); - } else { - // Shouldn't reach here - view.draw(canvas); - } - } catch (Exception e) { - Log.e(LOG_TAG, "An error has occurred while drawing:", e); - } finally { - callbacks.onHardwarePostDraw(canvas); - canvas.restoreToCount(saveCount); - view.mRecreateDisplayList = false; - - mDrawDelta = getSystemTime() - start; - - if (mDrawDelta > 0) { - mFrameCount++; - - debugOverdraw(attachInfo, dirty, canvas, displayList); - debugDirtyRegions(dirty, canvas); - drawProfileData(attachInfo); - } - } - - onPostDraw(); - - swapBuffers(status); - - if (mProfileEnabled) { - mProfileLock.unlock(); - } - - attachInfo.mIgnoreDirtyState = false; - } - } - } - - abstract void countOverdraw(HardwareCanvas canvas); - abstract float getOverdraw(HardwareCanvas canvas); - - private void debugOverdraw(View.AttachInfo attachInfo, Rect dirty, - HardwareCanvas canvas, DisplayList displayList) { - - if (mDebugOverdraw == OVERDRAW_TYPE_COUNT) { - if (mDebugOverdrawLayer == null) { - mDebugOverdrawLayer = createHardwareLayer(mWidth, mHeight, true); - } else if (mDebugOverdrawLayer.getWidth() != mWidth || - mDebugOverdrawLayer.getHeight() != mHeight) { - mDebugOverdrawLayer.resize(mWidth, mHeight); - } - - if (!mDebugOverdrawLayer.isValid()) { - mDebugOverdraw = -1; - return; - } - - HardwareCanvas layerCanvas = mDebugOverdrawLayer.start(canvas, dirty); - countOverdraw(layerCanvas); - final int restoreCount = layerCanvas.save(); - layerCanvas.drawDisplayList(displayList, null, DisplayList.FLAG_CLIP_CHILDREN); - layerCanvas.restoreToCount(restoreCount); - mDebugOverdrawLayer.end(canvas); - - float overdraw = getOverdraw(layerCanvas); - DisplayMetrics metrics = attachInfo.mRootView.getResources().getDisplayMetrics(); - - drawOverdrawCounter(canvas, overdraw, metrics.density); - } - } - - private void drawOverdrawCounter(HardwareCanvas canvas, float overdraw, float density) { - final String text = String.format("%.2fx", overdraw); - final Paint paint = setupPaint(density); - // HSBtoColor will clamp the values in the 0..1 range - paint.setColor(Color.HSBtoColor(0.28f - 0.28f * overdraw / 3.5f, 0.8f, 1.0f)); - - canvas.drawText(text, density * 4.0f, mHeight - paint.getFontMetrics().bottom, paint); - } - - private Paint setupPaint(float density) { - if (mDebugOverdrawPaint == null) { - mDebugOverdrawPaint = new Paint(); - mDebugOverdrawPaint.setAntiAlias(true); - mDebugOverdrawPaint.setShadowLayer(density * 3.0f, 0.0f, 0.0f, 0xff000000); - mDebugOverdrawPaint.setTextSize(density * 20.0f); - } - return mDebugOverdrawPaint; - } - - private DisplayList buildDisplayList(View view, HardwareCanvas canvas) { - if (mDrawDelta <= 0) { - return view.mDisplayList; - } - - view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED) - == View.PFLAG_INVALIDATED; - view.mPrivateFlags &= ~View.PFLAG_INVALIDATED; - - long buildDisplayListStartTime = startBuildDisplayListProfiling(); - canvas.clearLayerUpdates(); - - Trace.traceBegin(Trace.TRACE_TAG_VIEW, "getDisplayList"); - DisplayList displayList = view.getDisplayList(); - Trace.traceEnd(Trace.TRACE_TAG_VIEW); - - endBuildDisplayListProfiling(buildDisplayListStartTime); - - return displayList; - } - - abstract void drawProfileData(View.AttachInfo attachInfo); - - private Rect beginFrame(HardwareCanvas canvas, Rect dirty, int surfaceState) { - // We had to change the current surface and/or context, redraw everything - if (surfaceState == SURFACE_STATE_UPDATED) { - dirty = null; - beginFrame(null); - } else { - int[] size = mSurfaceSize; - beginFrame(size); - - if (size[1] != mHeight || size[0] != mWidth) { - mWidth = size[0]; - mHeight = size[1]; - - canvas.setViewport(mWidth, mHeight); - - dirty = null; - } - } - - if (mDebugDataProvider != null) dirty = null; - - return dirty; - } - - private long startBuildDisplayListProfiling() { - if (mProfileEnabled) { - mProfileCurrentFrame += PROFILE_FRAME_DATA_COUNT; - if (mProfileCurrentFrame >= mProfileData.length) { - mProfileCurrentFrame = 0; - } - - return System.nanoTime(); - } - return 0; - } - - private void endBuildDisplayListProfiling(long getDisplayListStartTime) { - if (mProfileEnabled) { - long now = System.nanoTime(); - float total = (now - getDisplayListStartTime) * 0.000001f; - //noinspection PointlessArithmeticExpression - mProfileData[mProfileCurrentFrame] = total; - } - } - - private int prepareFrame(Rect dirty) { - int status; - Trace.traceBegin(Trace.TRACE_TAG_VIEW, "prepareFrame"); - try { - status = onPreDraw(dirty); - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIEW); - } - return status; - } - - private int drawDisplayList(View.AttachInfo attachInfo, HardwareCanvas canvas, - DisplayList displayList, int status) { - - long drawDisplayListStartTime = 0; - if (mProfileEnabled) { - drawDisplayListStartTime = System.nanoTime(); - } - - Trace.traceBegin(Trace.TRACE_TAG_VIEW, "drawDisplayList"); - try { - status |= canvas.drawDisplayList(displayList, mRedrawClip, - DisplayList.FLAG_CLIP_CHILDREN); - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIEW); - } - - if (mProfileEnabled) { - long now = System.nanoTime(); - float total = (now - drawDisplayListStartTime) * 0.000001f; - mProfileData[mProfileCurrentFrame + 1] = total; - } - - handleFunctorStatus(attachInfo, status); - return status; - } - - private void swapBuffers(int status) { - if ((status & DisplayList.STATUS_DREW) == DisplayList.STATUS_DREW) { - long eglSwapBuffersStartTime = 0; - if (mProfileEnabled) { - eglSwapBuffersStartTime = System.nanoTime(); - } - - sEgl.eglSwapBuffers(sEglDisplay, mEglSurface); - - if (mProfileEnabled) { - long now = System.nanoTime(); - float total = (now - eglSwapBuffersStartTime) * 0.000001f; - mProfileData[mProfileCurrentFrame + 2] = total; - } - - checkEglErrors(); - } - } - - private void debugDirtyRegions(Rect dirty, HardwareCanvas canvas) { - if (mDebugDirtyRegions) { - if (mDebugPaint == null) { - mDebugPaint = new Paint(); - mDebugPaint.setColor(0x7fff0000); - } - - if (dirty != null && (mFrameCount & 1) == 0) { - canvas.drawRect(dirty, mDebugPaint); - } - } - } - - private void handleFunctorStatus(View.AttachInfo attachInfo, int status) { - // If the draw flag is set, functors will be invoked while executing - // the tree of display lists - if ((status & DisplayList.STATUS_DRAW) != 0) { - if (mRedrawClip.isEmpty()) { - attachInfo.mViewRootImpl.invalidate(); - } else { - attachInfo.mViewRootImpl.invalidateChildInParent(null, mRedrawClip); - mRedrawClip.setEmpty(); - } - } - - if ((status & DisplayList.STATUS_INVOKE) != 0 || - attachInfo.mHandler.hasCallbacks(mFunctorsRunnable)) { - attachInfo.mHandler.removeCallbacks(mFunctorsRunnable); - mFunctorsRunnable.attachInfo = attachInfo; - attachInfo.mHandler.postDelayed(mFunctorsRunnable, FUNCTOR_PROCESS_DELAY); - } - } - - @Override - void detachFunctor(long functor) { - if (mCanvas != null) { - mCanvas.detachFunctor(functor); - } - } - - @Override - boolean attachFunctor(View.AttachInfo attachInfo, long functor) { - if (mCanvas != null) { - mCanvas.attachFunctor(functor); - mFunctorsRunnable.attachInfo = attachInfo; - attachInfo.mHandler.removeCallbacks(mFunctorsRunnable); - attachInfo.mHandler.postDelayed(mFunctorsRunnable, 0); - return true; - } - return false; - } - - /** - * Ensures the current EGL context and surface are the ones we expect. - * This method throws an IllegalStateException if invoked from a thread - * that did not initialize EGL. - * - * @return {@link #SURFACE_STATE_ERROR} if the correct EGL context cannot be made current, - * {@link #SURFACE_STATE_UPDATED} if the EGL context was changed or - * {@link #SURFACE_STATE_SUCCESS} if the EGL context was the correct one - * - * @see #checkRenderContextUnsafe() - */ - int checkRenderContext() { - if (mEglThread != Thread.currentThread()) { - throw new IllegalStateException("Hardware acceleration can only be used with a " + - "single UI thread.\nOriginal thread: " + mEglThread + "\n" + - "Current thread: " + Thread.currentThread()); - } - - return checkRenderContextUnsafe(); - } - - /** - * Ensures the current EGL context and surface are the ones we expect. - * This method does not check the current thread. - * - * @return {@link #SURFACE_STATE_ERROR} if the correct EGL context cannot be made current, - * {@link #SURFACE_STATE_UPDATED} if the EGL context was changed or - * {@link #SURFACE_STATE_SUCCESS} if the EGL context was the correct one - * - * @see #checkRenderContext() - */ - private int checkRenderContextUnsafe() { - if (!mEglSurface.equals(sEgl.eglGetCurrentSurface(EGL_DRAW)) || - !mEglContext.equals(sEgl.eglGetCurrentContext())) { - if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, mEglContext)) { - Log.e(LOG_TAG, "eglMakeCurrent failed " + - GLUtils.getEGLErrorString(sEgl.eglGetError())); - fallback(true); - return SURFACE_STATE_ERROR; - } else { - if (mUpdateDirtyRegions) { - enableDirtyRegions(); - mUpdateDirtyRegions = false; - } - return SURFACE_STATE_UPDATED; - } - } - return SURFACE_STATE_SUCCESS; - } - - private static int dpToPx(int dp, float density) { - return (int) (dp * density + 0.5f); - } - - class DrawPerformanceDataProvider extends GraphDataProvider { - private final int mGraphType; - - private int mVerticalUnit; - private int mHorizontalUnit; - private int mHorizontalMargin; - private int mThresholdStroke; - - DrawPerformanceDataProvider(int graphType) { - mGraphType = graphType; - } - - @Override - void prepare(DisplayMetrics metrics) { - final float density = metrics.density; - - mVerticalUnit = dpToPx(PROFILE_DRAW_DP_PER_MS, density); - mHorizontalUnit = dpToPx(PROFILE_DRAW_WIDTH, density); - mHorizontalMargin = dpToPx(PROFILE_DRAW_MARGIN, density); - mThresholdStroke = dpToPx(PROFILE_DRAW_THRESHOLD_STROKE_WIDTH, density); - } - - @Override - int getGraphType() { - return mGraphType; - } - - @Override - int getVerticalUnitSize() { - return mVerticalUnit; - } - - @Override - int getHorizontalUnitSize() { - return mHorizontalUnit; - } - - @Override - int getHorizontaUnitMargin() { - return mHorizontalMargin; - } - - @Override - float[] getData() { - return mProfileData; - } - - @Override - float getThreshold() { - return 16; - } - - @Override - int getFrameCount() { - return mProfileData.length / PROFILE_FRAME_DATA_COUNT; - } - - @Override - int getElementCount() { - return PROFILE_FRAME_DATA_COUNT; - } - - @Override - int getCurrentFrame() { - return mProfileCurrentFrame / PROFILE_FRAME_DATA_COUNT; - } - - @Override - void setupGraphPaint(Paint paint, int elementIndex) { - paint.setColor(PROFILE_DRAW_COLORS[elementIndex]); - if (mGraphType == GRAPH_TYPE_LINES) paint.setStrokeWidth(mThresholdStroke); - } - - @Override - void setupThresholdPaint(Paint paint) { - paint.setColor(PROFILE_DRAW_THRESHOLD_COLOR); - paint.setStrokeWidth(mThresholdStroke); - } - - @Override - void setupCurrentFramePaint(Paint paint) { - paint.setColor(PROFILE_DRAW_CURRENT_FRAME_COLOR); - if (mGraphType == GRAPH_TYPE_LINES) paint.setStrokeWidth(mThresholdStroke); - } - } - } - - /** - * Hardware renderer using OpenGL ES 2.0. - */ - static class Gl20Renderer extends GlRenderer { - private GLES20Canvas mGlCanvas; - - private DisplayMetrics mDisplayMetrics; - - private static EGLSurface sPbuffer; - private static final Object[] sPbufferLock = new Object[0]; - - static class Gl20RendererEglContext extends ManagedEGLContext { - final Handler mHandler = new Handler(); - - public Gl20RendererEglContext(EGLContext context) { - super(context); - } - - @Override - public void onTerminate(final EGLContext eglContext) { - // Make sure we do this on the correct thread. - if (mHandler.getLooper() != Looper.myLooper()) { - mHandler.post(new Runnable() { - @Override - public void run() { - onTerminate(eglContext); - } - }); - return; - } - - synchronized (sEglLock) { - if (sEgl == null) return; - - if (EGLImpl.getInitCount(sEglDisplay) == 1) { - usePbufferSurface(eglContext); - GLES20Canvas.terminateCaches(); - - sEgl.eglDestroyContext(sEglDisplay, eglContext); - sEglContextStorage.set(null); - sEglContextStorage.remove(); - - sEgl.eglDestroySurface(sEglDisplay, sPbuffer); - sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, - EGL_NO_SURFACE, EGL_NO_CONTEXT); - - sEgl.eglReleaseThread(); - sEgl.eglTerminate(sEglDisplay); - - sEgl = null; - sEglDisplay = null; - sEglConfig = null; - sPbuffer = null; - } - } - } - } - - Gl20Renderer(boolean translucent) { - super(2, translucent); - } - - @Override - HardwareCanvas createCanvas() { - return mGlCanvas = new GLES20Canvas(mTranslucent); - } - - @Override - ManagedEGLContext createManagedContext(EGLContext eglContext) { - return new Gl20Renderer.Gl20RendererEglContext(mEglContext); - } - - @Override - int[] getConfig(boolean dirtyRegions) { - //noinspection PointlessBooleanExpression,ConstantConditions - final int stencilSize = GLES20Canvas.getStencilSize(); - final int swapBehavior = dirtyRegions ? EGL14.EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0; - - return new int[] { - EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, - EGL_RED_SIZE, 8, - EGL_GREEN_SIZE, 8, - EGL_BLUE_SIZE, 8, - EGL_ALPHA_SIZE, 8, - EGL_DEPTH_SIZE, 0, - EGL_CONFIG_CAVEAT, EGL_NONE, - EGL_STENCIL_SIZE, stencilSize, - EGL_SURFACE_TYPE, EGL_WINDOW_BIT | swapBehavior, - EGL_NONE - }; - } - - @Override - void initCaches() { - if (GLES20Canvas.initCaches()) { - // Caches were (re)initialized, rebind atlas - initAtlas(); - } - } - - @Override - void initAtlas() { - IBinder binder = ServiceManager.getService("assetatlas"); - if (binder == null) return; - - IAssetAtlas atlas = IAssetAtlas.Stub.asInterface(binder); - try { - if (atlas.isCompatible(android.os.Process.myPpid())) { - GraphicBuffer buffer = atlas.getBuffer(); - if (buffer != null) { - long[] map = atlas.getMap(); - if (map != null) { - GLES20Canvas.initAtlas(buffer, map); - } - // If IAssetAtlas is not the same class as the IBinder - // we are using a remote service and we can safely - // destroy the graphic buffer - if (atlas.getClass() != binder.getClass()) { - buffer.destroy(); - } - } - } - } catch (RemoteException e) { - Log.w(LOG_TAG, "Could not acquire atlas", e); - } - } - - @Override - boolean canDraw() { - return super.canDraw() && mGlCanvas != null; - } - - @Override - int onPreDraw(Rect dirty) { - return mGlCanvas.onPreDraw(dirty); - } - - @Override - void onPostDraw() { - mGlCanvas.onPostDraw(); - } - - @Override - void drawProfileData(View.AttachInfo attachInfo) { - if (mDebugDataProvider != null) { - final GraphDataProvider provider = mDebugDataProvider; - initProfileDrawData(attachInfo, provider); - - final int height = provider.getVerticalUnitSize(); - final int margin = provider.getHorizontaUnitMargin(); - final int width = provider.getHorizontalUnitSize(); - - int x = 0; - int count = 0; - int current = 0; - - final float[] data = provider.getData(); - final int elementCount = provider.getElementCount(); - final int graphType = provider.getGraphType(); - - int totalCount = provider.getFrameCount() * elementCount; - if (graphType == GraphDataProvider.GRAPH_TYPE_LINES) { - totalCount -= elementCount; - } - - for (int i = 0; i < totalCount; i += elementCount) { - if (data[i] < 0.0f) break; - - int index = count * 4; - if (i == provider.getCurrentFrame() * elementCount) current = index; - - x += margin; - int x2 = x + width; - - int y2 = mHeight; - int y1 = (int) (y2 - data[i] * height); - - switch (graphType) { - case GraphDataProvider.GRAPH_TYPE_BARS: { - for (int j = 0; j < elementCount; j++) { - //noinspection MismatchedReadAndWriteOfArray - final float[] r = mProfileShapes[j]; - r[index] = x; - r[index + 1] = y1; - r[index + 2] = x2; - r[index + 3] = y2; - - y2 = y1; - if (j < elementCount - 1) { - y1 = (int) (y2 - data[i + j + 1] * height); - } - } - } break; - case GraphDataProvider.GRAPH_TYPE_LINES: { - for (int j = 0; j < elementCount; j++) { - //noinspection MismatchedReadAndWriteOfArray - final float[] r = mProfileShapes[j]; - r[index] = (x + x2) * 0.5f; - r[index + 1] = index == 0 ? y1 : r[index - 1]; - r[index + 2] = r[index] + width; - r[index + 3] = y1; - - y2 = y1; - if (j < elementCount - 1) { - y1 = (int) (y2 - data[i + j + 1] * height); - } - } - } break; - } - - - x += width; - count++; - } - - x += margin; - - drawGraph(graphType, count); - drawCurrentFrame(graphType, current); - drawThreshold(x, height); - } - } - - private void drawGraph(int graphType, int count) { - for (int i = 0; i < mProfileShapes.length; i++) { - mDebugDataProvider.setupGraphPaint(mProfilePaint, i); - switch (graphType) { - case GraphDataProvider.GRAPH_TYPE_BARS: - mGlCanvas.drawRects(mProfileShapes[i], count * 4, mProfilePaint); - break; - case GraphDataProvider.GRAPH_TYPE_LINES: - mGlCanvas.drawLines(mProfileShapes[i], 0, count * 4, mProfilePaint); - break; - } - } - } - - private void drawCurrentFrame(int graphType, int index) { - if (index >= 0) { - mDebugDataProvider.setupCurrentFramePaint(mProfilePaint); - switch (graphType) { - case GraphDataProvider.GRAPH_TYPE_BARS: - mGlCanvas.drawRect(mProfileShapes[2][index], mProfileShapes[2][index + 1], - mProfileShapes[2][index + 2], mProfileShapes[0][index + 3], - mProfilePaint); - break; - case GraphDataProvider.GRAPH_TYPE_LINES: - mGlCanvas.drawLine(mProfileShapes[2][index], mProfileShapes[2][index + 1], - mProfileShapes[2][index], mHeight, mProfilePaint); - break; - } - } - } - - private void drawThreshold(int x, int height) { - float threshold = mDebugDataProvider.getThreshold(); - if (threshold > 0.0f) { - mDebugDataProvider.setupThresholdPaint(mProfilePaint); - int y = (int) (mHeight - threshold * height); - mGlCanvas.drawLine(0.0f, y, x, y, mProfilePaint); - } - } - - private void initProfileDrawData(View.AttachInfo attachInfo, GraphDataProvider provider) { - if (mProfileShapes == null) { - final int elementCount = provider.getElementCount(); - final int frameCount = provider.getFrameCount(); - - mProfileShapes = new float[elementCount][]; - for (int i = 0; i < elementCount; i++) { - mProfileShapes[i] = new float[frameCount * 4]; - } - - mProfilePaint = new Paint(); - } - - mProfilePaint.reset(); - if (provider.getGraphType() == GraphDataProvider.GRAPH_TYPE_LINES) { - mProfilePaint.setAntiAlias(true); - } - - if (mDisplayMetrics == null) { - mDisplayMetrics = new DisplayMetrics(); - } - - attachInfo.mDisplay.getMetrics(mDisplayMetrics); - provider.prepare(mDisplayMetrics); - } - - @Override - void destroy(boolean full) { - try { - super.destroy(full); - } finally { - if (full && mGlCanvas != null) { - mGlCanvas = null; - } - } - } - - @Override - void pushLayerUpdate(HardwareLayer layer) { - mGlCanvas.pushLayerUpdate(layer); - } - - @Override - void cancelLayerUpdate(HardwareLayer layer) { - mGlCanvas.cancelLayerUpdate(layer); - } - - @Override - void flushLayerUpdates() { - mGlCanvas.flushLayerUpdates(); - } - - @Override - public DisplayList createDisplayList(String name) { - return new GLES20DisplayList(name); - } - - @Override - HardwareLayer createHardwareLayer(boolean isOpaque) { - return new GLES20TextureLayer(isOpaque); - } - - @Override - public HardwareLayer createHardwareLayer(int width, int height, boolean isOpaque) { - return new GLES20RenderLayer(width, height, isOpaque); - } - - @Override - void countOverdraw(HardwareCanvas canvas) { - ((GLES20Canvas) canvas).setCountOverdrawEnabled(true); - } - - @Override - float getOverdraw(HardwareCanvas canvas) { - return ((GLES20Canvas) canvas).getOverdraw(); - } - - @Override - public SurfaceTexture createSurfaceTexture(HardwareLayer layer) { - return ((GLES20TextureLayer) layer).getSurfaceTexture(); - } - - @Override - void setSurfaceTexture(HardwareLayer layer, SurfaceTexture surfaceTexture) { - ((GLES20TextureLayer) layer).setSurfaceTexture(surfaceTexture); - } - - @Override - boolean safelyRun(Runnable action) { - boolean needsContext = !isEnabled() || checkRenderContext() == SURFACE_STATE_ERROR; - - if (needsContext) { - Gl20RendererEglContext managedContext = - (Gl20RendererEglContext) sEglContextStorage.get(); - if (managedContext == null) return false; - usePbufferSurface(managedContext.getContext()); - } - - try { - action.run(); - } finally { - if (needsContext) { - sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, - EGL_NO_SURFACE, EGL_NO_CONTEXT); - } - } - - return true; - } - - @Override - void destroyLayers(final View view) { - if (view != null) { - safelyRun(new Runnable() { - @Override - public void run() { - if (mCanvas != null) { - mCanvas.clearLayerUpdates(); - } - destroyHardwareLayer(view); - GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS); - } - }); - } - } - - private static void destroyHardwareLayer(View view) { - view.destroyLayer(true); - - if (view instanceof ViewGroup) { - ViewGroup group = (ViewGroup) view; - - int count = group.getChildCount(); - for (int i = 0; i < count; i++) { - destroyHardwareLayer(group.getChildAt(i)); - } - } - } - - @Override - void destroyHardwareResources(final View view) { - if (view != null) { - safelyRun(new Runnable() { - @Override - public void run() { - if (mCanvas != null) { - mCanvas.clearLayerUpdates(); - } - destroyResources(view); - GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS); - } - }); - } - } - - private static void destroyResources(View view) { - view.destroyHardwareResources(); - - if (view instanceof ViewGroup) { - ViewGroup group = (ViewGroup) view; - - int count = group.getChildCount(); - for (int i = 0; i < count; i++) { - destroyResources(group.getChildAt(i)); - } - } - } - - static HardwareRenderer create(boolean translucent) { - if (GLES20Canvas.isAvailable()) { - return new Gl20Renderer(translucent); - } - return null; - } - - static void startTrimMemory(int level) { - if (sEgl == null || sEglConfig == null) return; - - Gl20RendererEglContext managedContext = - (Gl20RendererEglContext) sEglContextStorage.get(); - // We do not have OpenGL objects - if (managedContext == null) { - return; - } else { - usePbufferSurface(managedContext.getContext()); - } - - if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) { - GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_FULL); - } else if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) { - GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_MODERATE); - } - } - - static void endTrimMemory() { - if (sEgl != null && sEglDisplay != null) { - sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - } - } - - private static void usePbufferSurface(EGLContext eglContext) { - synchronized (sPbufferLock) { - // Create a temporary 1x1 pbuffer so we have a context - // to clear our OpenGL objects - if (sPbuffer == null) { - sPbuffer = sEgl.eglCreatePbufferSurface(sEglDisplay, sEglConfig, new int[] { - EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE - }); - } - } - sEgl.eglMakeCurrent(sEglDisplay, sPbuffer, sPbuffer, eglContext); - } - } } diff --git a/core/java/android/view/InputQueue.java b/core/java/android/view/InputQueue.java index d5cec49..aebc601 100644 --- a/core/java/android/view/InputQueue.java +++ b/core/java/android/view/InputQueue.java @@ -18,7 +18,6 @@ package android.view; import dalvik.system.CloseGuard; -import android.os.Handler; import android.os.Looper; import android.os.MessageQueue; import android.util.Pools.Pool; diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java index 6a6c127..c183f08 100644 --- a/core/java/android/view/KeyEvent.java +++ b/core/java/android/view/KeyEvent.java @@ -20,7 +20,6 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.method.MetaKeyKeyListener; import android.util.Log; -import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; import android.view.KeyCharacterMap; @@ -1169,25 +1168,25 @@ public class KeyEvent extends InputEvent implements Parcelable { * This mask is set if the device woke because of this key event. */ public static final int FLAG_WOKE_HERE = 0x1; - + /** * This mask is set if the key event was generated by a software keyboard. */ public static final int FLAG_SOFT_KEYBOARD = 0x2; - + /** * This mask is set if we don't want the key event to cause us to leave * touch mode. */ public static final int FLAG_KEEP_TOUCH_MODE = 0x4; - + /** * This mask is set if an event was known to come from a trusted part * of the system. That is, the event is known to come from the user, * and could not have been spoofed by a third party component. */ public static final int FLAG_FROM_SYSTEM = 0x8; - + /** * This mask is used for compatibility, to identify enter keys that are * coming from an IME whose enter key has been auto-labelled "next" or @@ -1196,7 +1195,7 @@ public class KeyEvent extends InputEvent implements Parcelable { * receiving them. */ public static final int FLAG_EDITOR_ACTION = 0x10; - + /** * When associated with up key events, this indicates that the key press * has been canceled. Typically this is used with virtual touch screen @@ -1205,29 +1204,29 @@ public class KeyEvent extends InputEvent implements Parcelable { * event and should not perform the action normally associated with the * key. Note that for this to work, the application can not perform an * action for a key until it receives an up or the long press timeout has - * expired. + * expired. */ public static final int FLAG_CANCELED = 0x20; - + /** * This key event was generated by a virtual (on-screen) hard key area. * Typically this is an area of the touchscreen, outside of the regular * display, dedicated to "hardware" buttons. */ public static final int FLAG_VIRTUAL_HARD_KEY = 0x40; - + /** * This flag is set for the first key repeat that occurs after the * long press timeout. */ public static final int FLAG_LONG_PRESS = 0x80; - + /** * Set when a key event has {@link #FLAG_CANCELED} set because a long - * press action was executed while it was down. + * press action was executed while it was down. */ public static final int FLAG_CANCELED_LONG_PRESS = 0x100; - + /** * Set for {@link #ACTION_UP} when this event's key code is still being * tracked from its initial down. That is, somebody requested that tracking @@ -1284,7 +1283,7 @@ public class KeyEvent extends InputEvent implements Parcelable { public static int getDeadChar(int accent, int c) { return KeyCharacterMap.getDeadChar(accent, c); } - + static final boolean DEBUG = false; static final String TAG = "KeyEvent"; @@ -1314,10 +1313,10 @@ public class KeyEvent extends InputEvent implements Parcelable { * KeyEvent.startTracking()} to have the framework track the event * through its {@link #onKeyUp(int, KeyEvent)} and also call your * {@link #onKeyLongPress(int, KeyEvent)} if it occurs. - * + * * @param keyCode The value in event.getKeyCode(). * @param event Description of the key event. - * + * * @return If you handled the event, return true. If you want to allow * the event to be handled by the next receiver, return false. */ @@ -1330,10 +1329,10 @@ public class KeyEvent extends InputEvent implements Parcelable { * order to receive this callback, someone in the event change * <em>must</em> return true from {@link #onKeyDown} <em>and</em> * call {@link KeyEvent#startTracking()} on the event. - * + * * @param keyCode The value in event.getKeyCode(). * @param event Description of the key event. - * + * * @return If you handled the event, return true. If you want to allow * the event to be handled by the next receiver, return false. */ @@ -1341,10 +1340,10 @@ public class KeyEvent extends InputEvent implements Parcelable { /** * Called when a key up event has occurred. - * + * * @param keyCode The value in event.getKeyCode(). * @param event Description of the key event. - * + * * @return If you handled the event, return true. If you want to allow * the event to be handled by the next receiver, return false. */ @@ -1353,11 +1352,11 @@ public class KeyEvent extends InputEvent implements Parcelable { /** * Called when multiple down/up pairs of the same key have occurred * in a row. - * + * * @param keyCode The value in event.getKeyCode(). * @param count Number of pairs as returned by event.getRepeatCount(). * @param event Description of the key event. - * + * * @return If you handled the event, return true. If you want to allow * the event to be handled by the next receiver, return false. */ @@ -1373,7 +1372,7 @@ public class KeyEvent extends InputEvent implements Parcelable { /** * Create a new key event. - * + * * @param action Action code: either {@link #ACTION_DOWN}, * {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}. * @param code The key code. @@ -1387,7 +1386,7 @@ public class KeyEvent extends InputEvent implements Parcelable { /** * Create a new key event. - * + * * @param downTime The time (in {@link android.os.SystemClock#uptimeMillis}) * at which this key code originally went down. * @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis}) @@ -1410,7 +1409,7 @@ public class KeyEvent extends InputEvent implements Parcelable { /** * Create a new key event. - * + * * @param downTime The time (in {@link android.os.SystemClock#uptimeMillis}) * at which this key code originally went down. * @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis}) @@ -1435,7 +1434,7 @@ public class KeyEvent extends InputEvent implements Parcelable { /** * Create a new key event. - * + * * @param downTime The time (in {@link android.os.SystemClock#uptimeMillis}) * at which this key code originally went down. * @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis}) @@ -1464,7 +1463,7 @@ public class KeyEvent extends InputEvent implements Parcelable { /** * Create a new key event. - * + * * @param downTime The time (in {@link android.os.SystemClock#uptimeMillis}) * at which this key code originally went down. * @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis}) @@ -1495,7 +1494,7 @@ public class KeyEvent extends InputEvent implements Parcelable { /** * Create a new key event. - * + * * @param downTime The time (in {@link android.os.SystemClock#uptimeMillis}) * at which this key code originally went down. * @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis}) @@ -1531,7 +1530,7 @@ public class KeyEvent extends InputEvent implements Parcelable { * action, repeat count and source will automatically be set to * {@link #KEYCODE_UNKNOWN}, {@link #ACTION_MULTIPLE}, 0, and * {@link InputDevice#SOURCE_KEYBOARD} for you. - * + * * @param time The time (in {@link android.os.SystemClock#uptimeMillis}) * at which this event occured. * @param characters The string of characters. @@ -1569,10 +1568,10 @@ public class KeyEvent extends InputEvent implements Parcelable { /** * Copy an existing key event, modifying its time and repeat count. - * + * * @deprecated Use {@link #changeTimeRepeat(KeyEvent, long, int)} * instead. - * + * * @param origEvent The existing event to be copied. * @param eventTime The new event time * (in {@link android.os.SystemClock#uptimeMillis}) of the event. @@ -1688,7 +1687,7 @@ public class KeyEvent extends InputEvent implements Parcelable { /** * Create a new key event that is the same as the given one, but whose * event time and repeat count are replaced with the given value. - * + * * @param event The existing event to be copied. This is not modified. * @param eventTime The new event time * (in {@link android.os.SystemClock#uptimeMillis}) of the event. @@ -1698,11 +1697,11 @@ public class KeyEvent extends InputEvent implements Parcelable { int newRepeat) { return new KeyEvent(event, eventTime, newRepeat); } - + /** * Create a new key event that is the same as the given one, but whose * event time and repeat count are replaced with the given value. - * + * * @param event The existing event to be copied. This is not modified. * @param eventTime The new event time * (in {@link android.os.SystemClock#uptimeMillis}) of the event. @@ -1718,10 +1717,10 @@ public class KeyEvent extends InputEvent implements Parcelable { ret.mFlags = newFlags; return ret; } - + /** * Copy an existing key event, modifying its action. - * + * * @param origEvent The existing event to be copied. * @param action The new action code of the event. */ @@ -1743,18 +1742,18 @@ public class KeyEvent extends InputEvent implements Parcelable { /** * Create a new key event that is the same as the given one, but whose * action is replaced with the given value. - * + * * @param event The existing event to be copied. This is not modified. * @param action The new action code of the event. */ public static KeyEvent changeAction(KeyEvent event, int action) { return new KeyEvent(event, action); } - + /** * Create a new key event that is the same as the given one, but whose * flags are replaced with the given value. - * + * * @param event The existing event to be copied. This is not modified. * @param flags The new flags constant. */ @@ -1779,7 +1778,7 @@ public class KeyEvent extends InputEvent implements Parcelable { /** * Don't use in new code, instead explicitly check * {@link #getAction()}. - * + * * @return If the action is ACTION_DOWN, returns true; else false. * * @deprecated @@ -1791,7 +1790,7 @@ public class KeyEvent extends InputEvent implements Parcelable { /** * Is this a system key? System keys can not be used for menu shortcuts. - * + * * TODO: this information should come from a table somewhere. * TODO: should the dpad keys be here? arguably, because they also shouldn't be menu shortcuts */ @@ -1860,6 +1859,30 @@ public class KeyEvent extends InputEvent implements Parcelable { } } + /** + * Whether this key is a media key, which can be send to apps that are + * interested in media key events. + * + * @hide + */ + public static final boolean isMediaKey(int keyCode) { + switch (keyCode) { + case KeyEvent.KEYCODE_MEDIA_PLAY: + case KeyEvent.KEYCODE_MEDIA_PAUSE: + case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: + case KeyEvent.KEYCODE_MUTE: + case KeyEvent.KEYCODE_HEADSETHOOK: + case KeyEvent.KEYCODE_MEDIA_STOP: + case KeyEvent.KEYCODE_MEDIA_NEXT: + case KeyEvent.KEYCODE_MEDIA_PREVIOUS: + case KeyEvent.KEYCODE_MEDIA_REWIND: + case KeyEvent.KEYCODE_MEDIA_RECORD: + case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: + return true; + } + return false; + } + /** {@inheritDoc} */ @Override public final int getDeviceId() { @@ -2329,7 +2352,7 @@ public class KeyEvent extends InputEvent implements Parcelable { /** * Retrieve the action of this key event. May be either * {@link #ACTION_DOWN}, {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}. - * + * * @return The event action: ACTION_DOWN, ACTION_UP, or ACTION_MULTIPLE. */ public final int getAction() { @@ -2343,7 +2366,7 @@ public class KeyEvent extends InputEvent implements Parcelable { public final boolean isCanceled() { return (mFlags&FLAG_CANCELED) != 0; } - + /** * Call this during {@link Callback#onKeyDown} to have the system track * the key through its final up (possibly including a long press). Note @@ -2354,7 +2377,7 @@ public class KeyEvent extends InputEvent implements Parcelable { public final void startTracking() { mFlags |= FLAG_START_TRACKING; } - + /** * For {@link #ACTION_UP} events, indicates that the event is still being * tracked from its initial down event as per @@ -2363,7 +2386,7 @@ public class KeyEvent extends InputEvent implements Parcelable { public final boolean isTracking() { return (mFlags&FLAG_TRACKING) != 0; } - + /** * For {@link #ACTION_DOWN} events, indicates that the event has been * canceled as per {@link #FLAG_LONG_PRESS}. @@ -2371,11 +2394,11 @@ public class KeyEvent extends InputEvent implements Parcelable { public final boolean isLongPress() { return (mFlags&FLAG_LONG_PRESS) != 0; } - + /** * Retrieve the key code of the key event. This is the physical key that * was pressed, <em>not</em> the Unicode character. - * + * * @return The key code of the event. */ public final int getKeyCode() { @@ -2386,14 +2409,14 @@ public class KeyEvent extends InputEvent implements Parcelable { * For the special case of a {@link #ACTION_MULTIPLE} event with key * code of {@link #KEYCODE_UNKNOWN}, this is a raw string of characters * associated with the event. In all other cases it is null. - * + * * @return Returns a String of 1 or more characters associated with * the event. */ public final String getCharacters() { return mCharacters; } - + /** * Retrieve the hardware key id of this key event. These values are not * reliable and vary from device to device. @@ -2410,7 +2433,7 @@ public class KeyEvent extends InputEvent implements Parcelable { * events, this is the number of times the key has repeated with the first * down starting at 0 and counting up from there. For multiple key * events, this is the number of down/up pairs that have occurred. - * + * * @return The number of times the key has repeated. */ public final int getRepeatCount() { @@ -2424,7 +2447,7 @@ public class KeyEvent extends InputEvent implements Parcelable { * Note that when chording keys, this value is the down time of the * most recently pressed key, which may <em>not</em> be the same physical * key of this event. - * + * * @return Returns the most recent key down time, in the * {@link android.os.SystemClock#uptimeMillis} time base */ @@ -2436,7 +2459,7 @@ public class KeyEvent extends InputEvent implements Parcelable { * Retrieve the time this event occurred, * in the {@link android.os.SystemClock#uptimeMillis} time base. * - * @return Returns the time this event occurred, + * @return Returns the time this event occurred, * in the {@link android.os.SystemClock#uptimeMillis} time base. */ @Override @@ -2465,7 +2488,7 @@ public class KeyEvent extends InputEvent implements Parcelable { /** * Renamed to {@link #getDeviceId}. - * + * * @hide * @deprecated use {@link #getDeviceId()} instead. */ @@ -2497,7 +2520,7 @@ public class KeyEvent extends InputEvent implements Parcelable { public char getDisplayLabel() { return getKeyCharacterMap().getDisplayLabel(mKeyCode); } - + /** * Gets the Unicode character generated by the specified key and meta * key state combination. @@ -2520,7 +2543,7 @@ public class KeyEvent extends InputEvent implements Parcelable { public int getUnicodeChar() { return getUnicodeChar(mMetaState); } - + /** * Gets the Unicode character generated by the specified key and meta * key state combination. @@ -2544,7 +2567,7 @@ public class KeyEvent extends InputEvent implements Parcelable { public int getUnicodeChar(int metaState) { return getKeyCharacterMap().get(mKeyCode, metaState); } - + /** * Get the character conversion data for a given key code. * @@ -2559,7 +2582,7 @@ public class KeyEvent extends InputEvent implements Parcelable { public boolean getKeyData(KeyData results) { return getKeyCharacterMap().getKeyData(mKeyCode, results); } - + /** * Gets the first character in the character array that can be generated * by the specified key code. @@ -2574,7 +2597,7 @@ public class KeyEvent extends InputEvent implements Parcelable { public char getMatch(char[] chars) { return getMatch(chars, 0); } - + /** * Gets the first character in the character array that can be generated * by the specified key code. If there are multiple choices, prefers @@ -2587,7 +2610,7 @@ public class KeyEvent extends InputEvent implements Parcelable { public char getMatch(char[] chars, int metaState) { return getKeyCharacterMap().getMatch(mKeyCode, chars, metaState); } - + /** * Gets the number or symbol associated with the key. * <p> @@ -2611,7 +2634,7 @@ public class KeyEvent extends InputEvent implements Parcelable { public char getNumber() { return getKeyCharacterMap().getNumber(mKeyCode); } - + /** * Returns true if this key produces a glyph. * @@ -2620,7 +2643,7 @@ public class KeyEvent extends InputEvent implements Parcelable { public boolean isPrintingKey() { return getKeyCharacterMap().isPrintingKey(mKeyCode); } - + /** * @deprecated Use {@link #dispatch(Callback, DispatcherState, Object)} instead. */ @@ -2628,16 +2651,16 @@ public class KeyEvent extends InputEvent implements Parcelable { public final boolean dispatch(Callback receiver) { return dispatch(receiver, null, null); } - + /** * Deliver this key event to a {@link Callback} interface. If this is * an ACTION_MULTIPLE event and it is not handled, then an attempt will * be made to deliver a single normal event. - * + * * @param receiver The Callback that will be given the event. * @param state State information retained across events. * @param target The target of the dispatch, for use in tracking. - * + * * @return The return value from the Callback method that was called. */ public final boolean dispatch(Callback receiver, DispatcherState state, @@ -2703,7 +2726,7 @@ public class KeyEvent extends InputEvent implements Parcelable { int mDownKeyCode; Object mDownTarget; SparseIntArray mActiveLongPresses = new SparseIntArray(); - + /** * Reset back to initial state. */ @@ -2713,7 +2736,7 @@ public class KeyEvent extends InputEvent implements Parcelable { mDownTarget = null; mActiveLongPresses.clear(); } - + /** * Stop any tracking associated with this target. */ @@ -2724,14 +2747,14 @@ public class KeyEvent extends InputEvent implements Parcelable { mDownTarget = null; } } - + /** * Start tracking the key code associated with the given event. This * can only be called on a key down. It will allow you to see any * long press associated with the key, and will result in * {@link KeyEvent#isTracking} return true on the long press and up * events. - * + * * <p>This is only needed if you are directly dispatching events, rather * than handling them in {@link Callback#onKeyDown}. */ @@ -2744,7 +2767,7 @@ public class KeyEvent extends InputEvent implements Parcelable { mDownKeyCode = event.getKeyCode(); mDownTarget = target; } - + /** * Return true if the key event is for a key code that is currently * being tracked by the dispatcher. @@ -2752,7 +2775,7 @@ public class KeyEvent extends InputEvent implements Parcelable { public boolean isTracking(KeyEvent event) { return mDownKeyCode == event.getKeyCode(); } - + /** * Keep track of the given event's key code as having performed an * action with a long press, so no action should occur on the up. @@ -2762,7 +2785,7 @@ public class KeyEvent extends InputEvent implements Parcelable { public void performedLongPress(KeyEvent event) { mActiveLongPresses.put(event.getKeyCode(), 1); } - + /** * Handle key up event to stop tracking. This resets the dispatcher state, * and updates the key event state based on it. @@ -2917,12 +2940,12 @@ public class KeyEvent extends InputEvent implements Parcelable { return new KeyEvent[size]; } }; - + /** @hide */ public static KeyEvent createFromParcelBody(Parcel in) { return new KeyEvent(in); } - + private KeyEvent(Parcel in) { mDeviceId = in.readInt(); mSource = in.readInt(); diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java index aa43bad..c4fac46 100644 --- a/core/java/android/view/LayoutInflater.java +++ b/core/java/android/view/LayoutInflater.java @@ -90,6 +90,10 @@ public abstract class LayoutInflater { private static final String TAG_INCLUDE = "include"; private static final String TAG_1995 = "blink"; private static final String TAG_REQUEST_FOCUS = "requestFocus"; + private static final String TAG_TAG = "tag"; + + private static final int[] ATTRS_THEME = new int[] { + com.android.internal.R.attr.theme }; /** * Hook to allow clients of the LayoutInflater to restrict the set of Views that are allowed @@ -459,15 +463,10 @@ public abstract class LayoutInflater { + "ViewGroup root and attachToRoot=true"); } - rInflate(parser, root, attrs, false); + rInflate(parser, root, attrs, false, false); } else { // Temp is the root view that was found in the xml - View temp; - if (TAG_1995.equals(name)) { - temp = new BlinkLayout(mContext, attrs); - } else { - temp = createViewFromTag(root, name, attrs); - } + final View temp = createViewFromTag(root, name, attrs, false); ViewGroup.LayoutParams params = null; @@ -489,7 +488,7 @@ public abstract class LayoutInflater { System.out.println("-----> start inflating children"); } // Inflate all children under temp - rInflate(parser, temp, attrs, true); + rInflate(parser, temp, attrs, true, true); if (DEBUG) { System.out.println("-----> done inflating children"); } @@ -669,31 +668,68 @@ public abstract class LayoutInflater { return onCreateView(name, attrs); } - /* - * default visibility so the BridgeInflater can override it. + /** + * Creates a view from a tag name using the supplied attribute set. + * <p> + * If {@code inheritContext} is true and the parent is non-null, the view + * will be inflated in parent view's context. If the view specifies a + * <theme> attribute, the inflation context will be wrapped with the + * specified theme. + * <p> + * Note: Default visibility so the BridgeInflater can override it. */ - View createViewFromTag(View parent, String name, AttributeSet attrs) { + View createViewFromTag(View parent, String name, AttributeSet attrs, boolean inheritContext) { if (name.equals("view")) { name = attrs.getAttributeValue(null, "class"); } + Context viewContext; + if (parent != null && inheritContext) { + viewContext = parent.getContext(); + } else { + viewContext = mContext; + } + + // Apply a theme wrapper, if requested. + final TypedArray ta = viewContext.obtainStyledAttributes(attrs, ATTRS_THEME); + final int themeResId = ta.getResourceId(0, 0); + if (themeResId != 0) { + viewContext = new ContextThemeWrapper(viewContext, themeResId); + } + ta.recycle(); + + if (name.equals(TAG_1995)) { + // Let's party like it's 1995! + return new BlinkLayout(viewContext, attrs); + } + if (DEBUG) System.out.println("******** Creating view: " + name); try { View view; - if (mFactory2 != null) view = mFactory2.onCreateView(parent, name, mContext, attrs); - else if (mFactory != null) view = mFactory.onCreateView(name, mContext, attrs); - else view = null; + if (mFactory2 != null) { + view = mFactory2.onCreateView(parent, name, viewContext, attrs); + } else if (mFactory != null) { + view = mFactory.onCreateView(name, viewContext, attrs); + } else { + view = null; + } if (view == null && mPrivateFactory != null) { - view = mPrivateFactory.onCreateView(parent, name, mContext, attrs); + view = mPrivateFactory.onCreateView(parent, name, viewContext, attrs); } - + if (view == null) { - if (-1 == name.indexOf('.')) { - view = onCreateView(parent, name, attrs); - } else { - view = createView(name, null, attrs); + final Object lastContext = mConstructorArgs[0]; + mConstructorArgs[0] = viewContext; + try { + if (-1 == name.indexOf('.')) { + view = onCreateView(parent, name, attrs); + } else { + view = createView(name, null, attrs); + } + } finally { + mConstructorArgs[0] = lastContext; } } @@ -720,9 +756,14 @@ public abstract class LayoutInflater { /** * Recursive method used to descend down the xml hierarchy and instantiate * views, instantiate their children, and then call onFinishInflate(). + * + * @param inheritContext Whether the root view should be inflated in its + * parent's context. This should be true when called inflating + * child views recursively, or false otherwise. */ void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs, - boolean finishInflate) throws XmlPullParserException, IOException { + boolean finishInflate, boolean inheritContext) throws XmlPullParserException, + IOException { final int depth = parser.getDepth(); int type; @@ -738,24 +779,20 @@ public abstract class LayoutInflater { if (TAG_REQUEST_FOCUS.equals(name)) { parseRequestFocus(parser, parent); + } else if (TAG_TAG.equals(name)) { + parseViewTag(parser, parent, attrs); } else if (TAG_INCLUDE.equals(name)) { if (parser.getDepth() == 0) { throw new InflateException("<include /> cannot be the root element"); } - parseInclude(parser, parent, attrs); + parseInclude(parser, parent, attrs, inheritContext); } else if (TAG_MERGE.equals(name)) { throw new InflateException("<merge /> must be the root element"); - } else if (TAG_1995.equals(name)) { - final View view = new BlinkLayout(mContext, attrs); - final ViewGroup viewGroup = (ViewGroup) parent; - final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); - rInflate(parser, view, attrs, true); - viewGroup.addView(view, params); } else { - final View view = createViewFromTag(parent, name, attrs); + final View view = createViewFromTag(parent, name, attrs, inheritContext); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); - rInflate(parser, view, attrs, true); + rInflate(parser, view, attrs, true, true); viewGroup.addView(view, params); } } @@ -763,10 +800,14 @@ public abstract class LayoutInflater { if (finishInflate) parent.onFinishInflate(); } - private void parseRequestFocus(XmlPullParser parser, View parent) + /** + * Parses a <code><request-focus></code> element and requests focus on + * the containing View. + */ + private void parseRequestFocus(XmlPullParser parser, View view) throws XmlPullParserException, IOException { int type; - parent.requestFocus(); + view.requestFocus(); final int currentDepth = parser.getDepth(); while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) { @@ -774,9 +815,30 @@ public abstract class LayoutInflater { } } - private void parseInclude(XmlPullParser parser, View parent, AttributeSet attrs) + /** + * Parses a <code><tag></code> element and sets a keyed tag on the + * containing View. + */ + private void parseViewTag(XmlPullParser parser, View view, AttributeSet attrs) throws XmlPullParserException, IOException { + int type; + + final TypedArray ta = mContext.obtainStyledAttributes( + attrs, com.android.internal.R.styleable.ViewTag); + final int key = ta.getResourceId(com.android.internal.R.styleable.ViewTag_id, 0); + final CharSequence value = ta.getText(com.android.internal.R.styleable.ViewTag_value); + view.setTag(key, value); + ta.recycle(); + + final int currentDepth = parser.getDepth(); + while (((type = parser.next()) != XmlPullParser.END_TAG || + parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) { + // Empty + } + } + private void parseInclude(XmlPullParser parser, View parent, AttributeSet attrs, + boolean inheritContext) throws XmlPullParserException, IOException { int type; if (parent instanceof ViewGroup) { @@ -811,9 +873,10 @@ public abstract class LayoutInflater { if (TAG_MERGE.equals(childName)) { // Inflate all children. - rInflate(childParser, parent, childAttrs, false); + rInflate(childParser, parent, childAttrs, false, inheritContext); } else { - final View view = createViewFromTag(parent, childName, childAttrs); + final View view = createViewFromTag(parent, childName, childAttrs, + inheritContext); final ViewGroup group = (ViewGroup) parent; // We try to load the layout params set in the <include /> tag. If @@ -836,7 +899,7 @@ public abstract class LayoutInflater { } // Inflate all children. - rInflate(childParser, view, childAttrs, true); + rInflate(childParser, view, childAttrs, true, true); // Attempt to override the included layout's android:id with the // one set on the <include /> tag itself. diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java index bb7ed41..063a08d 100644 --- a/core/java/android/view/PointerIcon.java +++ b/core/java/android/view/PointerIcon.java @@ -140,7 +140,7 @@ public final class PointerIcon implements Parcelable { if ((resourceId & 0xff000000) == 0x01000000) { icon.mSystemIconResourceId = resourceId; } else { - icon.loadResource(context.getResources(), resourceId); + icon.loadResource(context, context.getResources(), resourceId); } return icon; } @@ -198,7 +198,7 @@ public final class PointerIcon implements Parcelable { } PointerIcon icon = new PointerIcon(STYLE_CUSTOM); - icon.loadResource(resources, resourceId); + icon.loadResource(null, resources, resourceId); return icon; } @@ -224,7 +224,7 @@ public final class PointerIcon implements Parcelable { PointerIcon result = new PointerIcon(mStyle); result.mSystemIconResourceId = mSystemIconResourceId; - result.loadResource(context.getResources(), mSystemIconResourceId); + result.loadResource(context, context.getResources(), mSystemIconResourceId); return result; } @@ -373,7 +373,7 @@ public final class PointerIcon implements Parcelable { return true; } - private void loadResource(Resources resources, int resourceId) { + private void loadResource(Context context, Resources resources, int resourceId) { XmlResourceParser parser = resources.getXml(resourceId); final int bitmapRes; final float hotSpotX; @@ -397,7 +397,12 @@ public final class PointerIcon implements Parcelable { throw new IllegalArgumentException("<pointer-icon> is missing bitmap attribute."); } - Drawable drawable = resources.getDrawable(bitmapRes); + Drawable drawable; + if (context == null) { + drawable = resources.getDrawable(bitmapRes); + } else { + drawable = context.getDrawable(bitmapRes); + } if (!(drawable instanceof BitmapDrawable)) { throw new IllegalArgumentException("<pointer-icon> bitmap attribute must " + "refer to a bitmap drawable."); diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java index 91645e7..fdaae01 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -16,6 +16,7 @@ package android.view; +import android.annotation.IntDef; import android.content.res.CompatibilityInfo.Translator; import android.graphics.Canvas; import android.graphics.Matrix; @@ -24,6 +25,10 @@ import android.graphics.SurfaceTexture; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + import dalvik.system.CloseGuard; /** @@ -80,6 +85,11 @@ public class Surface implements Parcelable { // non compatibility mode. private Matrix mCompatibleMatrix; + /** @hide */ + @IntDef({ROTATION_0, ROTATION_90, ROTATION_180, ROTATION_270}) + @Retention(RetentionPolicy.SOURCE) + public @interface Rotation {} + /** * Rotation constant: 0 degree rotation (natural orientation) */ diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index eea5884..e693b9e 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -22,7 +22,6 @@ import android.graphics.Rect; import android.graphics.Region; import android.view.Surface; import android.os.IBinder; -import android.os.SystemProperties; import android.util.Log; import android.view.Surface.OutOfResourcesException; @@ -40,9 +39,11 @@ public class SurfaceControl { private static native void nativeDestroy(long nativeObject); private static native Bitmap nativeScreenshot(IBinder displayToken, - int width, int height, int minLayer, int maxLayer, boolean allLayers); + int width, int height, int minLayer, int maxLayer, boolean allLayers, + boolean useIdentityTransform); private static native void nativeScreenshot(IBinder displayToken, Surface consumer, - int width, int height, int minLayer, int maxLayer, boolean allLayers); + int width, int height, int minLayer, int maxLayer, boolean allLayers, + boolean useIdentityTransform); private static native void nativeOpenTransaction(); private static native void nativeCloseTransaction(); @@ -166,6 +167,13 @@ public class SurfaceControl { public static final int FX_SURFACE_DIM = 0x00020000; /** + * Surface creation flag: Creates a video plane Surface. + * This surface is backed by a hardware video plane. It is an error to lock + * a video plane surface, since it doesn't have a backing store. + */ + public static final int FX_SURFACE_VIDEO_PLANE = 0x00040000; + + /** * Mask used for FX values above. * */ @@ -178,13 +186,13 @@ public class SurfaceControl { * Equivalent to calling hide(). * Updates the value set during Surface creation (see {@link #HIDDEN}). */ - public static final int SURFACE_HIDDEN = 0x01; + private static final int SURFACE_HIDDEN = 0x01; /** * Surface flag: composite without blending when possible. * Updates the value set during Surface creation (see {@link #OPAQUE}). */ - public static final int SURFACE_OPAQUE = 0x02; + private static final int SURFACE_OPAQUE = 0x02; /* built-in physical display ids (keep in sync with ISurfaceComposer.h) @@ -192,13 +200,13 @@ public class SurfaceControl { /** * Built-in physical display id: Main display. - * Use only with {@link SurfaceControl#getBuiltInDisplay()}. + * Use only with {@link SurfaceControl#getBuiltInDisplay(int)}. */ public static final int BUILT_IN_DISPLAY_ID_MAIN = 0; /** * Built-in physical display id: Attached HDMI display. - * Use only with {@link SurfaceControl#getBuiltInDisplay()}. + * Use only with {@link SurfaceControl#getBuiltInDisplay(int)}. */ public static final int BUILT_IN_DISPLAY_ID_HDMI = 1; @@ -370,18 +378,6 @@ public class SurfaceControl { nativeSetMatrix(mNativeObject, dsdx, dtdx, dsdy, dtdy); } - /** - * Sets and clears flags, such as {@link #SURFACE_HIDDEN}. The new value will be: - * <p> - * <code>newFlags = (oldFlags & ~mask) | (flags & mask)</code> - * <p> - * Note this does not take the same set of flags as the constructor. - */ - public void setFlags(int flags, int mask) { - checkNotReleased(); - nativeSetFlags(mNativeObject, flags, mask); - } - public void setWindowCrop(Rect crop) { checkNotReleased(); if (crop != null) { @@ -567,10 +563,15 @@ public class SurfaceControl { * include in the screenshot. * @param maxLayer The highest (top-most Z order) surface layer to * include in the screenshot. + * @param useIdentityTransform Replace whatever transformation (rotation, + * scaling, translation) the surface layers are currently using with the + * identity transformation while taking the screenshot. */ public static void screenshot(IBinder display, Surface consumer, - int width, int height, int minLayer, int maxLayer) { - screenshot(display, consumer, width, height, minLayer, maxLayer, false); + int width, int height, int minLayer, int maxLayer, + boolean useIdentityTransform) { + screenshot(display, consumer, width, height, minLayer, maxLayer, false, + useIdentityTransform); } /** @@ -585,7 +586,7 @@ public class SurfaceControl { */ public static void screenshot(IBinder display, Surface consumer, int width, int height) { - screenshot(display, consumer, width, height, 0, 0, true); + screenshot(display, consumer, width, height, 0, 0, true, false); } /** @@ -595,7 +596,7 @@ public class SurfaceControl { * @param consumer The {@link Surface} to take the screenshot into. */ public static void screenshot(IBinder display, Surface consumer) { - screenshot(display, consumer, 0, 0, 0, 0, true); + screenshot(display, consumer, 0, 0, 0, 0, true, false); } @@ -615,15 +616,20 @@ public class SurfaceControl { * include in the screenshot. * @param maxLayer The highest (top-most Z order) surface layer to * include in the screenshot. + * @param useIdentityTransform Replace whatever transformation (rotation, + * scaling, translation) the surface layers are currently using with the + * identity transformation while taking the screenshot. * @return Returns a Bitmap containing the screen contents, or null * if an error occurs. Make sure to call Bitmap.recycle() as soon as * possible, once its content is not needed anymore. */ - public static Bitmap screenshot(int width, int height, int minLayer, int maxLayer) { + public static Bitmap screenshot(int width, int height, int minLayer, int maxLayer, + boolean useIdentityTransform) { // TODO: should take the display as a parameter IBinder displayToken = SurfaceControl.getBuiltInDisplay( SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN); - return nativeScreenshot(displayToken, width, height, minLayer, maxLayer, false); + return nativeScreenshot(displayToken, width, height, minLayer, maxLayer, false, + useIdentityTransform); } /** @@ -642,17 +648,19 @@ public class SurfaceControl { // TODO: should take the display as a parameter IBinder displayToken = SurfaceControl.getBuiltInDisplay( SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN); - return nativeScreenshot(displayToken, width, height, 0, 0, true); + return nativeScreenshot(displayToken, width, height, 0, 0, true, false); } private static void screenshot(IBinder display, Surface consumer, - int width, int height, int minLayer, int maxLayer, boolean allLayers) { + int width, int height, int minLayer, int maxLayer, boolean allLayers, + boolean useIdentityTransform) { if (display == null) { throw new IllegalArgumentException("displayToken must not be null"); } if (consumer == null) { throw new IllegalArgumentException("consumer must not be null"); } - nativeScreenshot(display, consumer, width, height, minLayer, maxLayer, allLayers); + nativeScreenshot(display, consumer, width, height, minLayer, maxLayer, allLayers, + useIdentityTransform); } } diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 22d4c9b..1f211c2 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -188,8 +188,13 @@ public class SurfaceView extends View { init(); } - public SurfaceView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); init(); } @@ -250,8 +255,9 @@ public class SurfaceView extends View { updateWindow(false, false); } + /** @hide */ @Override - protected void onDetachedFromWindow() { + protected void onDetachedFromWindowInternal() { if (mGlobalListenersAdded) { ViewTreeObserver observer = getViewTreeObserver(); observer.removeOnScrollChangedListener(mScrollChangedListener); @@ -273,7 +279,7 @@ public class SurfaceView extends View { mSession = null; mLayout.token = null; - super.onDetachedFromWindow(); + super.onDetachedFromWindowInternal(); } @Override @@ -416,7 +422,10 @@ public class SurfaceView extends View { mWindowType = type; } - private void updateWindow(boolean force, boolean redrawNeeded) { + /** + * @hide + */ + protected void updateWindow(boolean force, boolean redrawNeeded) { if (!mHaveFrame) { return; } diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java index b78af2e..3cfe5e9 100644 --- a/core/java/android/view/TextureView.java +++ b/core/java/android/view/TextureView.java @@ -156,14 +156,32 @@ public class TextureView extends View { * * @param context The context to associate this view with. * @param attrs The attributes of the XML tag that is inflating the view. - * @param defStyle The default style to apply to this view. If 0, no style - * will be applied (beyond what is included in the theme). This may - * either be an attribute resource, whose value will be retrieved - * from the current theme, or an explicit style resource. + * @param defStyleAttr An attribute in the current theme that contains a + * reference to a style resource that supplies default values for + * the view. Can be 0 to not look for defaults. */ @SuppressWarnings({"UnusedDeclaration"}) - public TextureView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public TextureView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + /** + * Creates a new TextureView. + * + * @param context The context to associate this view with. + * @param attrs The attributes of the XML tag that is inflating the view. + * @param defStyleAttr An attribute in the current theme that contains a + * reference to a style resource that supplies default values for + * the view. Can be 0 to not look for defaults. + * @param defStyleRes A resource identifier of a style resource that + * supplies default values for the view, used only if + * defStyleAttr is 0 or can not be found in the theme. Can be 0 + * to not look for defaults. + */ + @SuppressWarnings({"UnusedDeclaration"}) + public TextureView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); init(); } @@ -210,29 +228,16 @@ public class TextureView extends View { } } + /** @hide */ @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - if (mLayer != null) { - boolean success = executeHardwareAction(new Runnable() { - @Override - public void run() { - destroySurface(); - } - }); - - if (!success) { - Log.w(LOG_TAG, "TextureView was not able to destroy its surface: " + this); - } - } + protected void onDetachedFromWindowInternal() { + destroySurface(); + super.onDetachedFromWindowInternal(); } private void destroySurface() { if (mLayer != null) { - mSurface.detachFromGLContext(); - // SurfaceTexture owns the texture name and detachFromGLContext - // should have deleted it - mLayer.clearStorage(); + mLayer.detachSurfaceTexture(mSurface); boolean shouldRelease = true; if (mListener != null) { @@ -357,7 +362,7 @@ public class TextureView extends View { return null; } - mLayer = mAttachInfo.mHardwareRenderer.createHardwareLayer(mOpaque); + mLayer = mAttachInfo.mHardwareRenderer.createTextureLayer(); if (!mUpdateSurface) { // Create a new SurfaceTexture for the layer. mSurface = mAttachInfo.mHardwareRenderer.createSurfaceTexture(mLayer); @@ -398,7 +403,7 @@ public class TextureView extends View { updateLayer(); mMatrixChanged = true; - mAttachInfo.mHardwareRenderer.setSurfaceTexture(mLayer, mSurface); + mLayer.setSurfaceTexture(mSurface); mSurface.setDefaultBufferSize(getWidth(), getHeight()); } @@ -451,7 +456,8 @@ public class TextureView extends View { } } - mLayer.update(getWidth(), getHeight(), mOpaque); + mLayer.prepare(getWidth(), getHeight(), mOpaque); + mLayer.updateSurfaceTexture(); if (mListener != null) { mListener.onSurfaceTextureUpdated(mSurface); @@ -589,14 +595,6 @@ public class TextureView extends View { */ public Bitmap getBitmap(Bitmap bitmap) { if (bitmap != null && isAvailable()) { - AttachInfo info = mAttachInfo; - if (info != null && info.mHardwareRenderer != null && - info.mHardwareRenderer.isEnabled()) { - if (!info.mHardwareRenderer.validate()) { - throw new IllegalStateException("Could not acquire hardware rendering context"); - } - } - applyUpdate(); applyTransformMatrix(); diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java new file mode 100644 index 0000000..a1fb123 --- /dev/null +++ b/core/java/android/view/ThreadedRenderer.java @@ -0,0 +1,274 @@ +/* + * 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.view; + +import android.graphics.Bitmap; +import android.graphics.Rect; +import android.graphics.SurfaceTexture; +import android.os.SystemClock; +import android.os.Trace; +import android.view.Surface.OutOfResourcesException; +import android.view.View.AttachInfo; + +import java.io.PrintWriter; + +/** + * Hardware renderer that proxies the rendering to a render thread. Most calls + * are currently synchronous. + * TODO: Make draw() async. + * TODO: Figure out how to share the DisplayList between two threads (global lock?) + * + * The UI thread can block on the RenderThread, but RenderThread must never + * block on the UI thread. + * + * ThreadedRenderer creates an instance of RenderProxy. RenderProxy in turn creates + * and manages a CanvasContext on the RenderThread. The CanvasContext is fully managed + * by the lifecycle of the RenderProxy. + * + * Note that although currently the EGL context & surfaces are created & managed + * by the render thread, the goal is to move that into a shared structure that can + * be managed by both threads. EGLSurface creation & deletion should ideally be + * done on the UI thread and not the RenderThread to avoid stalling the + * RenderThread with surface buffer allocation. + * + * @hide + */ +public class ThreadedRenderer extends HardwareRenderer { + private static final String LOGTAG = "ThreadedRenderer"; + + private static final Rect NULL_RECT = new Rect(-1, -1, -1, -1); + + private int mWidth, mHeight; + private long mNativeProxy; + + ThreadedRenderer(boolean translucent) { + mNativeProxy = nCreateProxy(translucent); + setEnabled(mNativeProxy != 0); + } + + @Override + void destroy(boolean full) { + nDestroyCanvas(mNativeProxy); + } + + @Override + boolean initialize(Surface surface) throws OutOfResourcesException { + return nInitialize(mNativeProxy, surface); + } + + @Override + void updateSurface(Surface surface) throws OutOfResourcesException { + nUpdateSurface(mNativeProxy, surface); + } + + @Override + void destroyHardwareResources(View view) { + destroyResources(view); + // TODO: GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS); + } + + private static void destroyResources(View view) { + view.destroyHardwareResources(); + + if (view instanceof ViewGroup) { + ViewGroup group = (ViewGroup) view; + + int count = group.getChildCount(); + for (int i = 0; i < count; i++) { + destroyResources(group.getChildAt(i)); + } + } + } + + @Override + void invalidate(Surface surface) { + updateSurface(surface); + } + + @Override + boolean safelyRun(Runnable action) { + nRunWithGlContext(mNativeProxy, action); + return true; + } + + @Override + void setup(int width, int height) { + mWidth = width; + mHeight = height; + nSetup(mNativeProxy, width, height); + } + + @Override + int getWidth() { + return mWidth; + } + + @Override + int getHeight() { + return mHeight; + } + + @Override + void dumpGfxInfo(PrintWriter pw) { + // TODO Auto-generated method stub + } + + @Override + long getFrameCount() { + // TODO Auto-generated method stub + return 0; + } + + @Override + boolean loadSystemProperties() { + return false; + } + + /** + * TODO: Remove + * Temporary hack to allow RenderThreadTest prototype app to trigger + * replaying a DisplayList after modifying the displaylist properties + * + * @hide */ + public void repeatLastDraw() { + } + + @Override + void setDisplayListData(long displayList, long newData) { + nSetDisplayListData(mNativeProxy, displayList, newData); + } + + @Override + void draw(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks, Rect dirty) { + attachInfo.mIgnoreDirtyState = true; + attachInfo.mDrawingTime = SystemClock.uptimeMillis(); + view.mPrivateFlags |= View.PFLAG_DRAWN; + + view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED) + == View.PFLAG_INVALIDATED; + view.mPrivateFlags &= ~View.PFLAG_INVALIDATED; + + Trace.traceBegin(Trace.TRACE_TAG_VIEW, "getDisplayList"); + DisplayList displayList = view.getDisplayList(); + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + + view.mRecreateDisplayList = false; + + if (dirty == null) { + dirty = NULL_RECT; + } + nDrawDisplayList(mNativeProxy, displayList.getNativeDisplayList(), + dirty.left, dirty.top, dirty.right, dirty.bottom); + } + + @Override + void detachFunctor(long functor) { + nDetachFunctor(mNativeProxy, functor); + } + + @Override + void attachFunctor(AttachInfo attachInfo, long functor) { + nAttachFunctor(mNativeProxy, functor); + } + + @Override + HardwareLayer createDisplayListLayer(int width, int height) { + long layer = nCreateDisplayListLayer(mNativeProxy, width, height); + return HardwareLayer.adoptDisplayListLayer(this, layer); + } + + @Override + HardwareLayer createTextureLayer() { + long layer = nCreateTextureLayer(mNativeProxy); + return HardwareLayer.adoptTextureLayer(this, layer); + } + + @Override + SurfaceTexture createSurfaceTexture(final HardwareLayer layer) { + final SurfaceTexture[] ret = new SurfaceTexture[1]; + nRunWithGlContext(mNativeProxy, new Runnable() { + @Override + public void run() { + ret[0] = layer.createSurfaceTexture(); + } + }); + return ret[0]; + } + + @Override + boolean copyLayerInto(final HardwareLayer layer, final Bitmap bitmap) { + return nCopyLayerInto(mNativeProxy, + layer.getDeferredLayerUpdater(), bitmap.mNativeBitmap); + } + + @Override + void pushLayerUpdate(HardwareLayer layer) { + // TODO: Remove this, it's not needed outside of GLRenderer + } + + @Override + void onLayerCreated(HardwareLayer layer) { + // TODO: Is this actually useful? + } + + @Override + void flushLayerUpdates() { + // TODO: Figure out what this should do or remove it + } + + @Override + void onLayerDestroyed(HardwareLayer layer) { + nDestroyLayer(mNativeProxy, layer.getDeferredLayerUpdater()); + } + + @Override + void setName(String name) { + } + + @Override + protected void finalize() throws Throwable { + try { + nDeleteProxy(mNativeProxy); + } finally { + super.finalize(); + } + } + + /** @hide */ + public static native void postToRenderThread(Runnable runnable); + + private static native long nCreateProxy(boolean translucent); + private static native void nDeleteProxy(long nativeProxy); + + private static native boolean nInitialize(long nativeProxy, Surface window); + private static native void nUpdateSurface(long nativeProxy, Surface window); + private static native void nSetup(long nativeProxy, int width, int height); + private static native void nSetDisplayListData(long nativeProxy, long displayList, + long newData); + private static native void nDrawDisplayList(long nativeProxy, long displayList, + int dirtyLeft, int dirtyTop, int dirtyRight, int dirtyBottom); + private static native void nRunWithGlContext(long nativeProxy, Runnable runnable); + private static native void nDestroyCanvas(long nativeProxy); + + private static native void nAttachFunctor(long nativeProxy, long functor); + private static native void nDetachFunctor(long nativeProxy, long functor); + + private static native long nCreateDisplayListLayer(long nativeProxy, int width, int height); + private static native long nCreateTextureLayer(long nativeProxy); + private static native boolean nCopyLayerInto(long nativeProxy, long layer, long bitmap); + private static native void nDestroyLayer(long nativeProxy, long layer); +} diff --git a/core/java/android/view/VideoPlaneView.java b/core/java/android/view/VideoPlaneView.java new file mode 100644 index 0000000..81dcf9d --- /dev/null +++ b/core/java/android/view/VideoPlaneView.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.content.Context; +import android.util.AttributeSet; + +/** + * Provides a dedicated surface embedded inside of a view hierarchy much like a + * {@link SurfaceView}, but the surface is actually backed by a hardware video + * plane. + * + * TODO: Eventually this should be separate from SurfaceView. + * + * @hide + */ +public class VideoPlaneView extends SurfaceView { + public VideoPlaneView(Context context) { + super(context); + } + + public VideoPlaneView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public VideoPlaneView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public VideoPlaneView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected void updateWindow(boolean force, boolean redrawNeeded) { + mLayout.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_VIDEO_PLANE; + super.updateWindow(force, redrawNeeded); + } +} diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 0b8a40f..7a58d06 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -16,6 +16,9 @@ package android.view; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.ClipData; import android.content.Context; import android.content.res.Configuration; @@ -29,6 +32,7 @@ import android.graphics.Interpolator; import android.graphics.LinearGradient; import android.graphics.Matrix; import android.graphics.Paint; +import android.graphics.Path; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.PorterDuff; @@ -86,6 +90,8 @@ import com.android.internal.view.menu.MenuBuilder; import com.google.android.collect.Lists; import com.google.android.collect.Maps; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -95,7 +101,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicInteger; @@ -658,6 +666,7 @@ import java.util.concurrent.atomic.AtomicInteger; * @attr ref android.R.styleable#View_scrollbarTrackVertical * @attr ref android.R.styleable#View_scrollbarAlwaysDrawHorizontalTrack * @attr ref android.R.styleable#View_scrollbarAlwaysDrawVerticalTrack + * @attr ref android.R.styleable#View_sharedElementName * @attr ref android.R.styleable#View_soundEffectsEnabled * @attr ref android.R.styleable#View_tag * @attr ref android.R.styleable#View_textAlignment @@ -666,6 +675,7 @@ import java.util.concurrent.atomic.AtomicInteger; * @attr ref android.R.styleable#View_transformPivotY * @attr ref android.R.styleable#View_translationX * @attr ref android.R.styleable#View_translationY + * @attr ref android.R.styleable#View_translationZ * @attr ref android.R.styleable#View_visibility * * @see android.view.ViewGroup @@ -729,6 +739,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ private static final int FITS_SYSTEM_WINDOWS = 0x00000002; + /** @hide */ + @IntDef({VISIBLE, INVISIBLE, GONE}) + @Retention(RetentionPolicy.SOURCE) + public @interface Visibility {} + /** * This view is visible. * Use with {@link #setVisibility} and <a href="#attr_android:visibility">{@code @@ -896,6 +911,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ static final int FOCUSABLE_IN_TOUCH_MODE = 0x00040000; + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({DRAWING_CACHE_QUALITY_LOW, DRAWING_CACHE_QUALITY_HIGH, DRAWING_CACHE_QUALITY_AUTO}) + public @interface DrawingCacheQuality {} + /** * <p>Enables low quality mode for the drawing cache.</p> */ @@ -940,6 +960,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ static final int DUPLICATE_PARENT_STATE = 0x00400000; + /** @hide */ + @IntDef({ + SCROLLBARS_INSIDE_OVERLAY, + SCROLLBARS_INSIDE_INSET, + SCROLLBARS_OUTSIDE_OVERLAY, + SCROLLBARS_OUTSIDE_INSET + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ScrollBarStyle {} + /** * The scrollbar style to display the scrollbars inside the content area, * without increasing the padding. The scrollbars will be overlaid with @@ -1020,6 +1050,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ static final int PARENT_SAVE_DISABLED_MASK = 0x20000000; + /** @hide */ + @IntDef(flag = true, + value = { + FOCUSABLES_ALL, + FOCUSABLES_TOUCH_MODE + }) + @Retention(RetentionPolicy.SOURCE) + public @interface FocusableMode {} + /** * View flag indicating whether {@link #addFocusables(ArrayList, int, int)} * should add all focusable Views regardless if they are focusable in touch mode. @@ -1032,6 +1071,28 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ public static final int FOCUSABLES_TOUCH_MODE = 0x00000001; + /** @hide */ + @IntDef({ + FOCUS_BACKWARD, + FOCUS_FORWARD, + FOCUS_LEFT, + FOCUS_UP, + FOCUS_RIGHT, + FOCUS_DOWN + }) + @Retention(RetentionPolicy.SOURCE) + public @interface FocusDirection {} + + /** @hide */ + @IntDef({ + FOCUS_LEFT, + FOCUS_UP, + FOCUS_RIGHT, + FOCUS_DOWN + }) + @Retention(RetentionPolicy.SOURCE) + public @interface FocusRealDirection {} // Like @FocusDirection, but without forward/backward + /** * Use with {@link #focusSearch(int)}. Move focus to the previous selectable * item. @@ -1587,7 +1648,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #setTag(Object) * @see #getTag() */ - protected Object mTag; + protected Object mTag = null; // for mPrivateFlags: /** {@hide} */ @@ -1804,6 +1865,25 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ static final int PFLAG2_DRAG_HOVERED = 0x00000002; + /** @hide */ + @IntDef({ + LAYOUT_DIRECTION_LTR, + LAYOUT_DIRECTION_RTL, + LAYOUT_DIRECTION_INHERIT, + LAYOUT_DIRECTION_LOCALE + }) + @Retention(RetentionPolicy.SOURCE) + // Not called LayoutDirection to avoid conflict with android.util.LayoutDirection + public @interface LayoutDir {} + + /** @hide */ + @IntDef({ + LAYOUT_DIRECTION_LTR, + LAYOUT_DIRECTION_RTL + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ResolvedLayoutDir {} + /** * Horizontal layout direction of this view is from Left to Right. * Use with {@link #setLayoutDirection}. @@ -1982,7 +2062,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback, static final int PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT = TEXT_DIRECTION_RESOLVED_DEFAULT << PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT; - /* + /** @hide */ + @IntDef({ + TEXT_ALIGNMENT_INHERIT, + TEXT_ALIGNMENT_GRAVITY, + TEXT_ALIGNMENT_CENTER, + TEXT_ALIGNMENT_TEXT_START, + TEXT_ALIGNMENT_TEXT_END, + TEXT_ALIGNMENT_VIEW_START, + TEXT_ALIGNMENT_VIEW_END + }) + @Retention(RetentionPolicy.SOURCE) + public @interface TextAlignment {} + + /** * Default text alignment. The text alignment of this View is inherited from its parent. * Use with {@link #setTextAlignment(int)} */ @@ -2234,7 +2327,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /* End of masks for mPrivateFlags2 */ - /* Masks for mPrivateFlags3 */ + /** + * Masks for mPrivateFlags3, as generated by dumpFlags(): + * + * |-------|-------|-------|-------| + * 1 PFLAG3_VIEW_IS_ANIMATING_TRANSFORM + * 1 PFLAG3_VIEW_IS_ANIMATING_ALPHA + * 1 PFLAG3_IS_LAID_OUT + * 1 PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT + * 1 PFLAG3_CALLED_SUPER + * |-------|-------|-------|-------| + */ /** * Flag indicating that view has a transform animation set on it. This is used to track whether @@ -2268,6 +2371,31 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ static final int PFLAG3_CALLED_SUPER = 0x10; + /** + * Flag indicating that an view will be clipped to its outline. + */ + static final int PFLAG3_CLIP_TO_OUTLINE = 0x20; + + /** + * Flag indicating that we're in the process of applying window insets. + */ + static final int PFLAG3_APPLYING_INSETS = 0x40; + + /** + * Flag indicating that we're in the process of fitting system windows using the old method. + */ + static final int PFLAG3_FITTING_SYSTEM_WINDOWS = 0x80; + + /** + * Flag indicating that an view will cast a shadow onto the Z=0 plane if elevated. + */ + static final int PFLAG3_CASTS_SHADOW = 0x100; + + /** + * Flag indicating that view will be transformed by the global camera if rotated in 3d, or given + * a non-0 Z translation. + */ + static final int PFLAG3_USES_GLOBAL_CAMERA = 0x200; /* End of masks for mPrivateFlags3 */ @@ -2664,6 +2792,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; + /** @hide */ + @IntDef(flag = true, + value = { FIND_VIEWS_WITH_TEXT, FIND_VIEWS_WITH_CONTENT_DESCRIPTION }) + @Retention(RetentionPolicy.SOURCE) + public @interface FindViewFlags {} + /** * Find views that render the specified text. * @@ -2685,7 +2819,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * added and it is a responsibility of the client to call the APIs of * the provider to determine whether the virtual tree rooted at this View * contains the text, i.e. getting the list of {@link AccessibilityNodeInfo}s - * represeting the virtual views with this text. + * representing the virtual views with this text. * * @see #findViewsWithText(ArrayList, CharSequence, int) * @@ -2887,6 +3021,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @ViewDebug.ExportedProperty float mTranslationY = 0f; + @ViewDebug.ExportedProperty + float mTranslationZ = 0f; + /** * The amount of scale in the x direction around the pivot point. A * value of 1 means no scaling is applied. @@ -3125,6 +3262,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @ViewDebug.ExportedProperty(deepExport = true, prefix = "bg_") private Drawable mBackground; + /** + * Display list used for backgrounds. + * <p> + * When non-null and valid, this is expected to contain an up-to-date copy + * of the background drawable. It is cleared on temporary detach and reset + * on cleanup. + */ + private DisplayList mBackgroundDisplayList; + private int mBackgroundResource; private boolean mBackgroundSizeChanged; @@ -3178,6 +3324,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private OnDragListener mOnDragListener; private OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener; + + OnApplyWindowInsetsListener mOnApplyWindowInsetsListener; } ListenerInfo mListenerInfo; @@ -3196,6 +3344,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private int[] mDrawableState = null; /** + * Stores the outline of the view, passed down to the DisplayList level for + * defining shadow shape and clipping. + */ + private Path mOutline; + + /** * When this view has focus and the next focus is {@link #FOCUS_LEFT}, * the user may specify which view to go to next. */ @@ -3398,6 +3552,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private Bitmap mDrawingCache; private Bitmap mUnscaledDrawingCache; + /** + * Display list used for the View content. + * <p> + * When non-null and valid, this is expected to contain an up-to-date copy + * of the View content. It is cleared on temporary detach and reset on + * cleanup. + */ DisplayList mDisplayList; /** @@ -3485,27 +3646,64 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Perform inflation from XML and apply a class-specific base style. This - * constructor of View allows subclasses to use their own base style when - * they are inflating. For example, a Button class's constructor would call - * this version of the super class constructor and supply - * <code>R.attr.buttonStyle</code> for <var>defStyle</var>; this allows - * the theme's button style to modify all of the base view attributes (in - * particular its background) as well as the Button class's attributes. + * Perform inflation from XML and apply a class-specific base style from a + * theme attribute. This constructor of View allows subclasses to use their + * own base style when they are inflating. For example, a Button class's + * constructor would call this version of the super class constructor and + * supply <code>R.attr.buttonStyle</code> for <var>defStyleAttr</var>; this + * allows the theme's button style to modify all of the base view attributes + * (in particular its background) as well as the Button class's attributes. * * @param context The Context the view is running in, through which it can * access the current theme, resources, etc. * @param attrs The attributes of the XML tag that is inflating the view. * @param defStyleAttr An attribute in the current theme that contains a - * reference to a style resource to apply to this view. If 0, no - * default style will be applied. + * reference to a style resource that supplies default values for + * the view. Can be 0 to not look for defaults. * @see #View(Context, AttributeSet) */ public View(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + /** + * Perform inflation from XML and apply a class-specific base style from a + * theme attribute or style resource. This constructor of View allows + * subclasses to use their own base style when they are inflating. + * <p> + * When determining the final value of a particular attribute, there are + * four inputs that come into play: + * <ol> + * <li>Any attribute values in the given AttributeSet. + * <li>The style resource specified in the AttributeSet (named "style"). + * <li>The default style specified by <var>defStyleAttr</var>. + * <li>The default style specified by <var>defStyleRes</var>. + * <li>The base values in this theme. + * </ol> + * <p> + * Each of these inputs is considered in-order, with the first listed taking + * precedence over the following ones. In other words, if in the + * AttributeSet you have supplied <code><Button * textColor="#ff000000"></code> + * , then the button's text will <em>always</em> be black, regardless of + * what is specified in any of the styles. + * + * @param context The Context the view is running in, through which it can + * access the current theme, resources, etc. + * @param attrs The attributes of the XML tag that is inflating the view. + * @param defStyleAttr An attribute in the current theme that contains a + * reference to a style resource that supplies default values for + * the view. Can be 0 to not look for defaults. + * @param defStyleRes A resource identifier of a style resource that + * supplies default values for the view, used only if + * defStyleAttr is 0 or can not be found in the theme. Can be 0 + * to not look for defaults. + * @see #View(Context, AttributeSet, int) + */ + public View(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { this(context); - TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View, - defStyleAttr, 0); + final TypedArray a = context.obtainStyledAttributes( + attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes); Drawable background = null; @@ -3528,6 +3726,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, float tx = 0; float ty = 0; + float tz = 0; float rotation = 0; float rotationX = 0; float rotationY = 0; @@ -3607,6 +3806,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, ty = a.getDimensionPixelOffset(attr, 0); transformSet = true; break; + case com.android.internal.R.styleable.View_translationZ: + tz = a.getDimensionPixelOffset(attr, 0); + transformSet = true; + break; case com.android.internal.R.styleable.View_rotation: rotation = a.getFloat(attr, 0); transformSet = true; @@ -3836,6 +4039,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, case R.styleable.View_layerType: setLayerType(a.getInt(attr, LAYER_TYPE_NONE), null); break; + case R.styleable.View_castsShadow: + if (a.getBoolean(attr, false)) { + mPrivateFlags3 |= PFLAG3_CASTS_SHADOW; + } + break; case R.styleable.View_textDirection: // Clear any text direction flag already set mPrivateFlags2 &= ~PFLAG2_TEXT_DIRECTION_MASK; @@ -3859,6 +4067,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, case R.styleable.View_accessibilityLiveRegion: setAccessibilityLiveRegion(a.getInt(attr, ACCESSIBILITY_LIVE_REGION_DEFAULT)); break; + case R.styleable.View_sharedElementName: + setSharedElementName(a.getString(attr)); + break; } } @@ -3948,6 +4159,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (transformSet) { setTranslationX(tx); setTranslationY(ty); + setTranslationZ(tz); setRotation(rotation); setRotationX(rotationX); setRotationY(rotationY); @@ -4596,7 +4808,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param previouslyFocusedRect The rectangle of the view that had focus * prior in this View's coordinate system. */ - void handleFocusGainInternal(int direction, Rect previouslyFocusedRect) { + void handleFocusGainInternal(@FocusRealDirection int direction, Rect previouslyFocusedRect) { if (DBG) { System.out.println(this + " requestFocus()"); } @@ -4614,12 +4826,43 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this); } + manageFocusHotspot(true, oldFocus); onFocusChanged(true, direction, previouslyFocusedRect); refreshDrawableState(); } } /** + * Forwards focus information to the background drawable, if necessary. When + * the view is gaining focus, <code>v</code> is the previous focus holder. + * When the view is losing focus, <code>v</code> is the next focus holder. + * + * @param focused whether this view is focused + * @param v previous or the next focus holder, or null if none + */ + private void manageFocusHotspot(boolean focused, View v) { + if (mBackground != null && mBackground.supportsHotspots()) { + final Rect r = new Rect(); + if (v != null) { + v.getBoundsOnScreen(r); + final int[] location = new int[2]; + getLocationOnScreen(location); + r.offset(-location[0], -location[1]); + } else { + r.set(mLeft, mTop, mRight, mBottom); + } + + final float x = r.exactCenterX(); + final float y = r.exactCenterY(); + mBackground.setHotspot(Drawable.HOTSPOT_FOCUS, x, y); + + if (!focused) { + mBackground.removeHotspot(Drawable.HOTSPOT_FOCUS); + } + } + } + + /** * Request that a rectangle of this view be visible on the screen, * scrolling if necessary just enough. * @@ -4705,7 +4948,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, System.out.println(this + " clearFocus()"); } - clearFocusInternal(true, true); + clearFocusInternal(null, true, true); } /** @@ -4717,10 +4960,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param refocus when propagate is true, specifies whether to request the * root view place new focus */ - void clearFocusInternal(boolean propagate, boolean refocus) { + void clearFocusInternal(View focused, boolean propagate, boolean refocus) { if ((mPrivateFlags & PFLAG_FOCUSED) != 0) { mPrivateFlags &= ~PFLAG_FOCUSED; + if (hasFocus()) { + manageFocusHotspot(false, focused); + } + if (propagate && mParent != null) { mParent.clearChildFocus(this); } @@ -4754,12 +5001,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * after calling this method. Otherwise, the view hierarchy may be left in * an inconstent state. */ - void unFocus() { + void unFocus(View focused) { if (DBG) { System.out.println(this + " unFocus()"); } - clearFocusInternal(false, false); + clearFocusInternal(focused, false, false); } /** @@ -4807,7 +5054,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * passed in as finer grained information about where the focus is coming * from (in addition to direction). Will be <code>null</code> otherwise. */ - protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { + protected void onFocusChanged(boolean gainFocus, @FocusDirection int direction, + @Nullable Rect previouslyFocusedRect) { if (gainFocus) { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); } else { @@ -5667,6 +5915,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @attr ref android.R.styleable#View_drawingCacheQuality */ + @DrawingCacheQuality public int getDrawingCacheQuality() { return mViewFlags & DRAWING_CACHE_QUALITY_MASK; } @@ -5684,7 +5933,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @attr ref android.R.styleable#View_drawingCacheQuality */ - public void setDrawingCacheQuality(int quality) { + public void setDrawingCacheQuality(@DrawingCacheQuality int quality) { setFlags(quality, DRAWING_CACHE_QUALITY_MASK); } @@ -5901,10 +6150,33 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @return {@code true} if this view applied the insets and it should not * continue propagating further down the hierarchy, {@code false} otherwise. * @see #getFitsSystemWindows() - * @see #setFitsSystemWindows(boolean) + * @see #setFitsSystemWindows(boolean) * @see #setSystemUiVisibility(int) + * + * @deprecated As of API XX use {@link #dispatchApplyWindowInsets(WindowInsets)} to apply + * insets to views. Views should override {@link #onApplyWindowInsets(WindowInsets)} or use + * {@link #setOnApplyWindowInsetsListener(android.view.View.OnApplyWindowInsetsListener)} + * to implement handling their own insets. */ protected boolean fitSystemWindows(Rect insets) { + if ((mPrivateFlags3 & PFLAG3_APPLYING_INSETS) == 0) { + // If we're not in the process of dispatching the newer apply insets call, + // that means we're not in the compatibility path. Dispatch into the newer + // apply insets path and take things from there. + try { + mPrivateFlags3 |= PFLAG3_FITTING_SYSTEM_WINDOWS; + return !dispatchApplyWindowInsets(new WindowInsets(insets)).hasInsets(); + } finally { + mPrivateFlags3 &= ~PFLAG3_FITTING_SYSTEM_WINDOWS; + } + } else { + // We're being called from the newer apply insets path. + // Perform the standard fallback behavior. + return fitSystemWindowsInt(insets); + } + } + + private boolean fitSystemWindowsInt(Rect insets) { if ((mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS) { mUserPaddingStart = UNDEFINED_PADDING; mUserPaddingEnd = UNDEFINED_PADDING; @@ -5924,6 +6196,97 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Called when the view should apply {@link WindowInsets} according to its internal policy. + * + * <p>This method should be overridden by views that wish to apply a policy different from or + * in addition to the default behavior. Clients that wish to force a view subtree + * to apply insets should call {@link #dispatchApplyWindowInsets(WindowInsets)}.</p> + * + * <p>Clients may supply an {@link OnApplyWindowInsetsListener} to a view. If one is set + * it will be called during dispatch instead of this method. The listener may optionally + * call this method from its own implementation if it wishes to apply the view's default + * insets policy in addition to its own.</p> + * + * <p>Implementations of this method should either return the insets parameter unchanged + * or a new {@link WindowInsets} cloned from the supplied insets with any insets consumed + * that this view applied itself. This allows new inset types added in future platform + * versions to pass through existing implementations unchanged without being erroneously + * consumed.</p> + * + * <p>By default if a view's {@link #setFitsSystemWindows(boolean) fitsSystemWindows} + * property is set then the view will consume the system window insets and apply them + * as padding for the view.</p> + * + * @param insets Insets to apply + * @return The supplied insets with any applied insets consumed + */ + public WindowInsets onApplyWindowInsets(WindowInsets insets) { + if ((mPrivateFlags3 & PFLAG3_FITTING_SYSTEM_WINDOWS) == 0) { + // We weren't called from within a direct call to fitSystemWindows, + // call into it as a fallback in case we're in a class that overrides it + // and has logic to perform. + if (fitSystemWindows(insets.getSystemWindowInsets())) { + return insets.cloneWithSystemWindowInsetsConsumed(); + } + } else { + // We were called from within a direct call to fitSystemWindows. + if (fitSystemWindowsInt(insets.getSystemWindowInsets())) { + return insets.cloneWithSystemWindowInsetsConsumed(); + } + } + return insets; + } + + /** + * Set an {@link OnApplyWindowInsetsListener} to take over the policy for applying + * window insets to this view. The listener's + * {@link OnApplyWindowInsetsListener#onApplyWindowInsets(View, WindowInsets) onApplyWindowInsets} + * method will be called instead of the view's + * {@link #onApplyWindowInsets(WindowInsets) onApplyWindowInsets} method. + * + * @param listener Listener to set + * + * @see #onApplyWindowInsets(WindowInsets) + */ + public void setOnApplyWindowInsetsListener(OnApplyWindowInsetsListener listener) { + getListenerInfo().mOnApplyWindowInsetsListener = listener; + } + + /** + * Request to apply the given window insets to this view or another view in its subtree. + * + * <p>This method should be called by clients wishing to apply insets corresponding to areas + * obscured by window decorations or overlays. This can include the status and navigation bars, + * action bars, input methods and more. New inset categories may be added in the future. + * The method returns the insets provided minus any that were applied by this view or its + * children.</p> + * + * <p>Clients wishing to provide custom behavior should override the + * {@link #onApplyWindowInsets(WindowInsets)} method or alternatively provide a + * {@link OnApplyWindowInsetsListener} via the + * {@link #setOnApplyWindowInsetsListener(View.OnApplyWindowInsetsListener) setOnApplyWindowInsetsListener} + * method.</p> + * + * <p>This method replaces the older {@link #fitSystemWindows(Rect) fitSystemWindows} method. + * </p> + * + * @param insets Insets to apply + * @return The provided insets minus the insets that were consumed + */ + public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) { + try { + mPrivateFlags3 |= PFLAG3_APPLYING_INSETS; + if (mListenerInfo != null && mListenerInfo.mOnApplyWindowInsetsListener != null) { + return mListenerInfo.mOnApplyWindowInsetsListener.onApplyWindowInsets(this, insets); + } else { + return onApplyWindowInsets(insets); + } + } finally { + mPrivateFlags3 &= ~PFLAG3_APPLYING_INSETS; + } + } + + /** * @hide Compute the insets that should be consumed by this view and the ones * that should propagate to those under it. */ @@ -5995,6 +6358,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Ask that a new dispatch of {@link #fitSystemWindows(Rect)} be performed. + * @deprecated Use {@link #requestApplyInsets()} for newer platform versions. */ public void requestFitSystemWindows() { if (mParent != null) { @@ -6003,6 +6367,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Ask that a new dispatch of {@link #onApplyWindowInsets(WindowInsets)} be performed. + */ + public void requestApplyInsets() { + requestFitSystemWindows(); + } + + /** * For use by PhoneWindow to make its own system window fitting optional. * @hide */ @@ -6021,6 +6392,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @ViewDebug.IntToString(from = INVISIBLE, to = "INVISIBLE"), @ViewDebug.IntToString(from = GONE, to = "GONE") }) + @Visibility public int getVisibility() { return mViewFlags & VISIBILITY_MASK; } @@ -6032,7 +6404,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @attr ref android.R.styleable#View_visibility */ @RemotableViewMethod - public void setVisibility(int visibility) { + public void setVisibility(@Visibility int visibility) { setFlags(visibility, VISIBILITY_MASK); if (mBackground != null) mBackground.setVisible(visibility == VISIBLE, false); } @@ -6191,6 +6563,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @ViewDebug.IntToString(from = LAYOUT_DIRECTION_INHERIT, to = "INHERIT"), @ViewDebug.IntToString(from = LAYOUT_DIRECTION_LOCALE, to = "LOCALE") }) + @LayoutDir public int getRawLayoutDirection() { return (mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_MASK) >> PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT; } @@ -6213,7 +6586,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @attr ref android.R.styleable#View_layoutDirection */ @RemotableViewMethod - public void setLayoutDirection(int layoutDirection) { + public void setLayoutDirection(@LayoutDir int layoutDirection) { if (getRawLayoutDirection() != layoutDirection) { // Reset the current layout direction and the resolved one mPrivateFlags2 &= ~PFLAG2_LAYOUT_DIRECTION_MASK; @@ -6243,6 +6616,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @ViewDebug.IntToString(from = LAYOUT_DIRECTION_LTR, to = "RESOLVED_DIRECTION_LTR"), @ViewDebug.IntToString(from = LAYOUT_DIRECTION_RTL, to = "RESOLVED_DIRECTION_RTL") }) + @ResolvedLayoutDir public int getLayoutDirection() { final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion; if (targetSdkVersion < JELLY_BEAN_MR1) { @@ -6612,7 +6986,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @return The nearest focusable in the specified direction, or null if none * can be found. */ - public View focusSearch(int direction) { + public View focusSearch(@FocusRealDirection int direction) { if (mParent != null) { return mParent.focusSearch(this, direction); } else { @@ -6631,7 +7005,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT. * @return True if the this view consumed this unhandled move. */ - public boolean dispatchUnhandledMove(View focused, int direction) { + public boolean dispatchUnhandledMove(View focused, @FocusRealDirection int direction) { return false; } @@ -6643,7 +7017,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * or FOCUS_BACKWARD. * @return The user specified next view, or null if there is none. */ - View findUserSetNextFocus(View root, int direction) { + View findUserSetNextFocus(View root, @FocusDirection int direction) { switch (direction) { case FOCUS_LEFT: if (mNextFocusLeftId == View.NO_ID) return null; @@ -6693,7 +7067,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param direction The direction of the focus * @return A list of focusable views */ - public ArrayList<View> getFocusables(int direction) { + public ArrayList<View> getFocusables(@FocusDirection int direction) { ArrayList<View> result = new ArrayList<View>(24); addFocusables(result, direction); return result; @@ -6707,7 +7081,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param views Focusable views found so far * @param direction The direction of the focus */ - public void addFocusables(ArrayList<View> views, int direction) { + public void addFocusables(ArrayList<View> views, @FocusDirection int direction) { addFocusables(views, direction, FOCUSABLES_TOUCH_MODE); } @@ -6727,7 +7101,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #FOCUSABLES_ALL * @see #FOCUSABLES_TOUCH_MODE */ - public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { + public void addFocusables(ArrayList<View> views, @FocusDirection int direction, + @FocusableMode int focusableMode) { if (views == null) { return; } @@ -6756,7 +7131,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #FIND_VIEWS_WITH_CONTENT_DESCRIPTION * @see #setContentDescription(CharSequence) */ - public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) { + public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, + @FindViewFlags int flags) { if (getAccessibilityNodeProvider() != null) { if ((flags & FIND_VIEWS_WITH_ACCESSIBILITY_NODE_PROVIDERS) != 0) { outViews.add(this); @@ -6803,7 +7179,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * Returns whether this View is accessibility focused. * * @return True if this View is accessibility focused. - * @hide */ public boolean isAccessibilityFocused() { return (mPrivateFlags2 & PFLAG2_ACCESSIBILITY_FOCUSED) != 0; @@ -7151,11 +7526,38 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Gets whether this view should be exposed for accessibility. + * Computes whether this view should be exposed for accessibility. In + * general, views that are interactive or provide information are exposed + * while views that serve only as containers are hidden. + * <p> + * If an ancestor of this view has importance + * {@link #IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS}, this method + * returns <code>false</code>. + * <p> + * Otherwise, the value is computed according to the view's + * {@link #getImportantForAccessibility()} value: + * <ol> + * <li>{@link #IMPORTANT_FOR_ACCESSIBILITY_NO} or + * {@link #IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS}, return <code>false + * </code> + * <li>{@link #IMPORTANT_FOR_ACCESSIBILITY_YES}, return <code>true</code> + * <li>{@link #IMPORTANT_FOR_ACCESSIBILITY_AUTO}, return <code>true</code> if + * view satisfies any of the following: + * <ul> + * <li>Is actionable, e.g. {@link #isClickable()}, + * {@link #isLongClickable()}, or {@link #isFocusable()} + * <li>Has an {@link AccessibilityDelegate} + * <li>Has an interaction listener, e.g. {@link OnTouchListener}, + * {@link OnKeyListener}, etc. + * <li>Is an accessibility live region, e.g. + * {@link #getAccessibilityLiveRegion()} is not + * {@link #ACCESSIBILITY_LIVE_REGION_NONE}. + * </ul> + * </ol> * * @return Whether the view is exposed for accessibility. - * - * @hide + * @see #setImportantForAccessibility(int) + * @see #getImportantForAccessibility() */ public boolean isImportantForAccessibility() { final int mode = (mPrivateFlags2 & PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK) @@ -7208,9 +7610,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param children The list of children for accessibility. */ public void addChildrenForAccessibility(ArrayList<View> children) { - if (includeForAccessibility()) { - children.add(this); - } + } /** @@ -7224,7 +7624,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @hide */ public boolean includeForAccessibility() { - //noinspection SimplifiableIfStatement if (mAttachInfo != null) { return (mAttachInfo.mAccessibilityFetchFlags & AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0 @@ -7247,7 +7646,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Returns whether the View has registered callbacks wich makes it + * Returns whether the View has registered callbacks which makes it * important for accessibility. * * @return True if the view is actionable for accessibility. @@ -7266,7 +7665,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * notification is at at most once every * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()} * to avoid unnecessary load to the system. Also once a view has a pending - * notifucation this method is a NOP until the notification has been sent. + * notification this method is a NOP until the notification has been sent. * * @hide */ @@ -7584,8 +7983,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @hide */ public void dispatchStartTemporaryDetach() { - clearDisplayList(); - onStartTemporaryDetach(); } @@ -7946,7 +8343,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param visibility The new visibility of changedView: {@link #VISIBLE}, * {@link #INVISIBLE} or {@link #GONE}. */ - protected void dispatchVisibilityChanged(View changedView, int visibility) { + protected void dispatchVisibilityChanged(@NonNull View changedView, + @Visibility int visibility) { onVisibilityChanged(changedView, visibility); } @@ -7957,7 +8355,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param visibility The new visibility of changedView: {@link #VISIBLE}, * {@link #INVISIBLE} or {@link #GONE}. */ - protected void onVisibilityChanged(View changedView, int visibility) { + protected void onVisibilityChanged(@NonNull View changedView, @Visibility int visibility) { if (visibility == VISIBLE) { if (mAttachInfo != null) { initialAwakenScrollBars(); @@ -7976,7 +8374,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param hint A hint about whether or not this view is displayed: * {@link #VISIBLE} or {@link #INVISIBLE}. */ - public void dispatchDisplayHint(int hint) { + public void dispatchDisplayHint(@Visibility int hint) { onDisplayHint(hint); } @@ -7989,7 +8387,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param hint A hint about whether or not this view is displayed: * {@link #VISIBLE} or {@link #INVISIBLE}. */ - protected void onDisplayHint(int hint) { + protected void onDisplayHint(@Visibility int hint) { } /** @@ -8000,7 +8398,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @see #onWindowVisibilityChanged(int) */ - public void dispatchWindowVisibilityChanged(int visibility) { + public void dispatchWindowVisibilityChanged(@Visibility int visibility) { onWindowVisibilityChanged(visibility); } @@ -8014,7 +8412,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @param visibility The new visibility of the window. */ - protected void onWindowVisibilityChanged(int visibility) { + protected void onWindowVisibilityChanged(@Visibility int visibility) { if (visibility == VISIBLE) { initialAwakenScrollBars(); } @@ -8026,6 +8424,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @return Returns the current visibility of the view's window. */ + @Visibility public int getWindowVisibility() { return mAttachInfo != null ? mAttachInfo.mWindowVisibility : GONE; } @@ -8306,7 +8705,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * <p>When implementing this, you probably also want to implement * {@link #onCheckIsTextEditor()} to indicate you will return a - * non-null InputConnection. + * non-null InputConnection.</p> + * + * <p>Also, take good care to fill in the {@link android.view.inputmethod.EditorInfo} + * object correctly and in its entirety, so that the connected IME can rely + * on its values. For example, {@link android.view.inputmethod.EditorInfo#initialSelStart} + * and {@link android.view.inputmethod.EditorInfo#initialSelEnd} members + * must be filled in with the correct cursor position for IMEs to work correctly + * with your application.</p> * * @param outAttrs Fill in with attribute information about the connection. */ @@ -8742,12 +9148,49 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } break; } + + if (mBackground != null && mBackground.supportsHotspots()) { + manageTouchHotspot(event); + } + return true; } return false; } + private void manageTouchHotspot(MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_POINTER_DOWN: { + final int index = event.getActionIndex(); + setPointerHotspot(event, index); + } break; + case MotionEvent.ACTION_MOVE: { + final int count = event.getPointerCount(); + for (int index = 0; index < count; index++) { + setPointerHotspot(event, index); + } + } break; + case MotionEvent.ACTION_POINTER_UP: { + final int actionIndex = event.getActionIndex(); + final int pointerId = event.getPointerId(actionIndex); + mBackground.removeHotspot(pointerId); + } break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + mBackground.clearHotspots(); + break; + } + } + + private void setPointerHotspot(MotionEvent event, int index) { + final int id = event.getPointerId(index); + final float x = event.getX(index); + final float y = event.getY(index); + mBackground.setHotspot(id, x, y); + } + /** * @hide */ @@ -8947,7 +9390,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if ((changed & VISIBILITY_MASK) != 0) { // If the view is invisible, cleanup its display list to free up resources - if (newVisibility != VISIBLE) { + if (newVisibility != VISIBLE && mAttachInfo != null) { cleanupDraw(); } @@ -10344,6 +10787,224 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * The depth location of this view relative to its parent. + * + * @return The depth of this view relative to its parent. + */ + @ViewDebug.ExportedProperty(category = "drawing") + public float getTranslationZ() { + return mTransformationInfo != null ? mTransformationInfo.mTranslationZ : 0; + } + + /** + * Sets the depth location of this view relative to its parent. + * + * @attr ref android.R.styleable#View_translationZ + */ + public void setTranslationZ(float translationZ) { + ensureTransformationInfo(); + final TransformationInfo info = mTransformationInfo; + if (info.mTranslationZ != translationZ) { + invalidateViewProperty(true, false); + info.mTranslationZ = translationZ; + info.mMatrixDirty = true; + invalidateViewProperty(false, true); + if (mDisplayList != null) { + mDisplayList.setTranslationZ(translationZ); + } + if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) { + // View was rejected last time it was drawn by its parent; this may have changed + invalidateParentIfNeeded(); + } + } + } + + /** + * Copies the Outline of the View into the Path parameter. + * <p> + * If the outline is not set, the parameter Path is set to empty. + * + * @param outline Path into which View's outline will be copied. Must be non-null. + * + * @see #setOutline(Path) + * @see #getClipToOutline() + * @see #setClipToOutline(boolean) + */ + public final void getOutline(@NonNull Path outline) { + if (outline == null) { + throw new IllegalArgumentException("Path must be non-null"); + } + if (mOutline == null) { + outline.reset(); + } else { + outline.set(mOutline); + } + } + + /** + * Sets the outline of the view, which defines the shape of the shadow it + * casts, and can used for clipping. + * <p> + * The outline path of a View must be {@link android.graphics.Path#isConvex() convex}. + * <p> + * If the outline is not set, or {@link Path#isEmpty()}, shadows will be + * cast from the bounds of the View, and clipToOutline will be ignored. + * + * @param outline The new outline of the view. Must be non-null, and convex. + * + * @see #setCastsShadow(boolean) + * @see #getOutline(Path) + * @see #getClipToOutline() + * @see #setClipToOutline(boolean) + */ + public void setOutline(@NonNull Path outline) { + if (outline == null) { + throw new IllegalArgumentException("Path must be non-null"); + } + if (!outline.isConvex()) { + throw new IllegalArgumentException("Path must be convex"); + } + // always copy the path since caller may reuse + if (mOutline == null) { + mOutline = new Path(outline); + } else { + mOutline.set(outline); + } + + if (mDisplayList != null) { + mDisplayList.setOutline(outline); + } + } + + /** + * Returns whether the outline of the View will be used for clipping. + * + * @see #getOutline(Path) + * @see #setOutline(Path) + */ + public final boolean getClipToOutline() { + return ((mPrivateFlags3 & PFLAG3_CLIP_TO_OUTLINE) != 0); + } + + /** + * Sets whether the outline of the View will be used for clipping. + * <p> + * The current implementation of outline clipping uses Canvas#clipPath(), + * and thus does not support anti-aliasing, and is expensive in terms of + * graphics performance. Therefore, it is strongly recommended that this + * property only be set temporarily, as in an animation. For the same + * reasons, there is no parallel XML attribute for this property. + * <p> + * If the outline of the view is not set or is empty, no clipping will be + * performed. + * + * @see #getOutline(Path) + * @see #setOutline(Path) + */ + public void setClipToOutline(boolean clipToOutline) { + // TODO : Add a fast invalidation here. + if (getClipToOutline() != clipToOutline) { + if (clipToOutline) { + mPrivateFlags3 |= PFLAG3_CLIP_TO_OUTLINE; + } else { + mPrivateFlags3 &= ~PFLAG3_CLIP_TO_OUTLINE; + } + if (mDisplayList != null) { + mDisplayList.setClipToOutline(clipToOutline); + } + } + } + + /** + * Returns whether the View will cast shadows when its + * {@link #setTranslationZ(float) z translation} is greater than 0, or it is + * rotated in 3D. + * + * @see #setTranslationZ(float) + * @see #setRotationX(float) + * @see #setRotationY(float) + * @see #setCastsShadow(boolean) + * @attr ref android.R.styleable#View_castsShadow + */ + public final boolean getCastsShadow() { + return ((mPrivateFlags3 & PFLAG3_CASTS_SHADOW) != 0); + } + + /** + * Set to true to enable this View to cast shadows. + * <p> + * If enabled, and the View has a z translation greater than 0, or is + * rotated in 3D, the shadow will be cast onto its parent at the z = 0 + * plane. + * <p> + * The shape of the shadow being cast is defined by the + * {@link #setOutline(Path) outline} of the view, or the rectangular bounds + * of the view if the outline is not set or is empty. + * + * @see #setTranslationZ(float) + * @see #getCastsShadow() + * @attr ref android.R.styleable#View_castsShadow + */ + public void setCastsShadow(boolean castsShadow) { + // TODO : Add a fast invalidation here. + if (getCastsShadow() != castsShadow) { + if (castsShadow) { + mPrivateFlags3 |= PFLAG3_CASTS_SHADOW; + } else { + mPrivateFlags3 &= ~PFLAG3_CASTS_SHADOW; + } + if (mDisplayList != null) { + mDisplayList.setCastsShadow(castsShadow); + } + } + } + + /** + * Returns whether the View will be transformed by the global camera. + * + * @see #setUsesGlobalCamera(boolean) + * + * @hide + */ + public final boolean getUsesGlobalCamera() { + return ((mPrivateFlags3 & PFLAG3_USES_GLOBAL_CAMERA) != 0); + } + + /** + * Sets whether the View should be transformed by the global camera. + * <p> + * If the view has a Z translation or 3D rotation, perspective from the + * global camera will be applied. This enables an app to transform multiple + * views in 3D with coherent perspective projection among them all. + * <p> + * Setting this to true will cause {@link #setCameraDistance() camera distance} + * to be ignored, as the global camera's position will dictate perspective + * transform. + * <p> + * This should not be used in conjunction with {@link android.graphics.Camera}. + * + * @see #getUsesGlobalCamera() + * @see #setTranslationZ(float) + * @see #setRotationX(float) + * @see #setRotationY(float) + * + * @hide + */ + public void setUsesGlobalCamera(boolean usesGlobalCamera) { + // TODO : Add a fast invalidation here. + if (getUsesGlobalCamera() != usesGlobalCamera) { + if (usesGlobalCamera) { + mPrivateFlags3 |= PFLAG3_USES_GLOBAL_CAMERA; + } else { + mPrivateFlags3 &= ~PFLAG3_USES_GLOBAL_CAMERA; + } + if (mDisplayList != null) { + mDisplayList.setUsesGlobalCamera(usesGlobalCamera); + } + } + } + + /** * Hit rectangle in parent's coordinates * * @param outRect The hit rectangle of the view. @@ -10797,94 +11458,54 @@ public class View implements Drawable.Callback, KeyEvent.Callback, (!(mParent instanceof ViewGroup) || !((ViewGroup) mParent).isViewTransitioning(this)); } + /** * Mark the area defined by dirty as needing to be drawn. If the view is - * visible, {@link #onDraw(android.graphics.Canvas)} will be called at some point - * in the future. This must be called from a UI thread. To call from a non-UI - * thread, call {@link #postInvalidate()}. + * visible, {@link #onDraw(android.graphics.Canvas)} will be called at some + * point in the future. + * <p> + * This must be called from a UI thread. To call from a non-UI thread, call + * {@link #postInvalidate()}. + * <p> + * <b>WARNING:</b> In API 19 and below, this method may be destructive to + * {@code dirty}. * - * WARNING: This method is destructive to dirty. * @param dirty the rectangle representing the bounds of the dirty region */ public void invalidate(Rect dirty) { - if (skipInvalidate()) { - return; - } - if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) || - (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID || - (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED) { - mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; - mPrivateFlags |= PFLAG_INVALIDATED; - mPrivateFlags |= PFLAG_DIRTY; - final ViewParent p = mParent; - final AttachInfo ai = mAttachInfo; - //noinspection PointlessBooleanExpression,ConstantConditions - if (!HardwareRenderer.RENDER_DIRTY_REGIONS) { - if (p != null && ai != null && ai.mHardwareAccelerated) { - // fast-track for GL-enabled applications; just invalidate the whole hierarchy - // with a null dirty rect, which tells the ViewAncestor to redraw everything - p.invalidateChild(this, null); - return; - } - } - if (p != null && ai != null) { - final int scrollX = mScrollX; - final int scrollY = mScrollY; - final Rect r = ai.mTmpInvalRect; - r.set(dirty.left - scrollX, dirty.top - scrollY, - dirty.right - scrollX, dirty.bottom - scrollY); - mParent.invalidateChild(this, r); - } - } + final int scrollX = mScrollX; + final int scrollY = mScrollY; + invalidateInternal(dirty.left - scrollX, dirty.top - scrollY, + dirty.right - scrollX, dirty.bottom - scrollY, true, false); } /** - * Mark the area defined by the rect (l,t,r,b) as needing to be drawn. - * The coordinates of the dirty rect are relative to the view. - * If the view is visible, {@link #onDraw(android.graphics.Canvas)} - * will be called at some point in the future. This must be called from - * a UI thread. To call from a non-UI thread, call {@link #postInvalidate()}. + * Mark the area defined by the rect (l,t,r,b) as needing to be drawn. The + * coordinates of the dirty rect are relative to the view. If the view is + * visible, {@link #onDraw(android.graphics.Canvas)} will be called at some + * point in the future. + * <p> + * This must be called from a UI thread. To call from a non-UI thread, call + * {@link #postInvalidate()}. + * * @param l the left position of the dirty region * @param t the top position of the dirty region * @param r the right position of the dirty region * @param b the bottom position of the dirty region */ public void invalidate(int l, int t, int r, int b) { - if (skipInvalidate()) { - return; - } - if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) || - (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID || - (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED) { - mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; - mPrivateFlags |= PFLAG_INVALIDATED; - mPrivateFlags |= PFLAG_DIRTY; - final ViewParent p = mParent; - final AttachInfo ai = mAttachInfo; - //noinspection PointlessBooleanExpression,ConstantConditions - if (!HardwareRenderer.RENDER_DIRTY_REGIONS) { - if (p != null && ai != null && ai.mHardwareAccelerated) { - // fast-track for GL-enabled applications; just invalidate the whole hierarchy - // with a null dirty rect, which tells the ViewAncestor to redraw everything - p.invalidateChild(this, null); - return; - } - } - if (p != null && ai != null && l < r && t < b) { - final int scrollX = mScrollX; - final int scrollY = mScrollY; - final Rect tmpr = ai.mTmpInvalRect; - tmpr.set(l - scrollX, t - scrollY, r - scrollX, b - scrollY); - p.invalidateChild(this, tmpr); - } - } + final int scrollX = mScrollX; + final int scrollY = mScrollY; + invalidateInternal(l - scrollX, t - scrollY, r - scrollX, b - scrollY, true, false); } /** * Invalidate the whole view. If the view is visible, * {@link #onDraw(android.graphics.Canvas)} will be called at some point in - * the future. This must be called from a UI thread. To call from a non-UI thread, - * call {@link #postInvalidate()}. + * the future. + * <p> + * This must be called from a UI thread. To call from a non-UI thread, call + * {@link #postInvalidate()}. */ public void invalidate() { invalidate(true); @@ -10892,47 +11513,108 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * This is where the invalidate() work actually happens. A full invalidate() - * causes the drawing cache to be invalidated, but this function can be called with - * invalidateCache set to false to skip that invalidation step for cases that do not - * need it (for example, a component that remains at the same dimensions with the same - * content). + * causes the drawing cache to be invalidated, but this function can be + * called with invalidateCache set to false to skip that invalidation step + * for cases that do not need it (for example, a component that remains at + * the same dimensions with the same content). * - * @param invalidateCache Whether the drawing cache for this view should be invalidated as - * well. This is usually true for a full invalidate, but may be set to false if the - * View's contents or dimensions have not changed. + * @param invalidateCache Whether the drawing cache for this view should be + * invalidated as well. This is usually true for a full + * invalidate, but may be set to false if the View's contents or + * dimensions have not changed. */ void invalidate(boolean invalidateCache) { + invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true); + } + + void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, + boolean fullInvalidate) { if (skipInvalidate()) { return; } - if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) || - (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) || - (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED || isOpaque() != mLastIsOpaque) { - mLastIsOpaque = isOpaque(); - mPrivateFlags &= ~PFLAG_DRAWN; + + if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) + || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) + || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED + || (fullInvalidate && isOpaque() != mLastIsOpaque)) { + if (fullInvalidate) { + mLastIsOpaque = isOpaque(); + mPrivateFlags &= ~PFLAG_DRAWN; + } + mPrivateFlags |= PFLAG_DIRTY; + if (invalidateCache) { mPrivateFlags |= PFLAG_INVALIDATED; mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; } + + // Propagate the damage rectangle to the parent view. final AttachInfo ai = mAttachInfo; final ViewParent p = mParent; - //noinspection PointlessBooleanExpression,ConstantConditions - if (!HardwareRenderer.RENDER_DIRTY_REGIONS) { - if (p != null && ai != null && ai.mHardwareAccelerated) { - // fast-track for GL-enabled applications; just invalidate the whole hierarchy - // with a null dirty rect, which tells the ViewAncestor to redraw everything - p.invalidateChild(this, null); - return; + if (p != null && ai != null && l < r && t < b) { + final Rect damage = ai.mTmpInvalRect; + damage.set(l, t, r, b); + p.invalidateChild(this, damage); + } + + // Damage the entire projection receiver, if necessary. + if (mBackground != null && mBackground.isProjected()) { + final View receiver = getProjectionReceiver(); + if (receiver != null) { + receiver.damageInParent(); } } - if (p != null && ai != null) { - final Rect r = ai.mTmpInvalRect; - r.set(0, 0, mRight - mLeft, mBottom - mTop); - // Don't call invalidate -- we don't want to internally scroll - // our own bounds - p.invalidateChild(this, r); + // Damage the entire IsolatedZVolume recieving this view's shadow. + if (getCastsShadow() && getTranslationZ() != 0) { + damageIsolatedZVolume(); + } + } + } + + /** + * @return this view's projection receiver, or {@code null} if none exists + */ + private View getProjectionReceiver() { + ViewParent p = getParent(); + while (p != null && p instanceof View) { + final View v = (View) p; + if (v.isProjectionReceiver()) { + return v; + } + p = p.getParent(); + } + + return null; + } + + /** + * @return whether the view is a projection receiver + */ + private boolean isProjectionReceiver() { + return mBackground != null; + } + + /** + * Damage area of the screen covered by the current isolated Z volume + * + * This method will guarantee that any changes to shadows cast by a View + * are damaged on the screen for future redraw. + */ + private void damageIsolatedZVolume() { + final AttachInfo ai = mAttachInfo; + if (ai != null) { + ViewParent p = getParent(); + while (p != null) { + if (p instanceof ViewGroup) { + final ViewGroup vg = (ViewGroup) p; + if (vg.hasIsolatedZVolume()) { + vg.damageInParent(); + return; + } + } + p = p.getParent(); } } } @@ -10963,16 +11645,28 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } invalidate(false); } else { - final AttachInfo ai = mAttachInfo; - final ViewParent p = mParent; - if (p != null && ai != null) { - final Rect r = ai.mTmpInvalRect; - r.set(0, 0, mRight - mLeft, mBottom - mTop); - if (mParent instanceof ViewGroup) { - ((ViewGroup) mParent).invalidateChildFast(this, r); - } else { - mParent.invalidateChild(this, r); - } + damageInParent(); + } + if (invalidateParent && getCastsShadow() && getTranslationZ() != 0) { + damageIsolatedZVolume(); + } + } + + /** + * Tells the parent view to damage this view's bounds. + * + * @hide + */ + protected void damageInParent() { + final AttachInfo ai = mAttachInfo; + final ViewParent p = mParent; + if (p != null && ai != null) { + final Rect r = ai.mTmpInvalRect; + r.set(0, 0, mRight - mLeft, mBottom - mTop); + if (mParent instanceof ViewGroup) { + ((ViewGroup) mParent).invalidateChildFast(this, r); + } else { + mParent.invalidateChild(this, r); } } } @@ -11096,6 +11790,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * @hide + */ + public HardwareRenderer getHardwareRenderer() { + return mAttachInfo != null ? mAttachInfo.mHardwareRenderer : null; + } + + /** * <p>Causes the Runnable to be added to the message queue. * The runnable will be run on the user interface thread.</p> * @@ -11212,10 +11913,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, attachInfo.mHandler.removeCallbacks(action); attachInfo.mViewRootImpl.mChoreographer.removeCallbacks( Choreographer.CALLBACK_ANIMATION, action, null); - } else { - // Assume that post will succeed later - ViewRootImpl.getRunQueue().removeCallbacks(action); } + // Assume that post will succeed later + ViewRootImpl.getRunQueue().removeCallbacks(action); } return true; } @@ -11706,7 +12406,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @attr ref android.R.styleable#View_scrollbarStyle */ - public void setScrollBarStyle(int style) { + public void setScrollBarStyle(@ScrollBarStyle int style) { if (style != (mViewFlags & SCROLLBARS_STYLE_MASK)) { mViewFlags = (mViewFlags & ~SCROLLBARS_STYLE_MASK) | (style & SCROLLBARS_STYLE_MASK); computeOpaqueFlags(); @@ -11730,6 +12430,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @ViewDebug.IntToString(from = SCROLLBARS_OUTSIDE_OVERLAY, to = "OUTSIDE_OVERLAY"), @ViewDebug.IntToString(from = SCROLLBARS_OUTSIDE_INSET, to = "OUTSIDE_INSET") }) + @ScrollBarStyle public int getScrollBarStyle() { return mViewFlags & SCROLLBARS_STYLE_MASK; } @@ -12121,10 +12822,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, InputMethodManager imm = InputMethodManager.peekInstance(); imm.focusIn(this); } - - if (mDisplayList != null) { - mDisplayList.clearDirty(); - } } /** @@ -12231,7 +12928,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #LAYOUT_DIRECTION_LTR * @see #LAYOUT_DIRECTION_RTL */ - public void onRtlPropertiesChanged(int layoutDirection) { + public void onRtlPropertiesChanged(@ResolvedLayoutDir int layoutDirection) { } /** @@ -12425,6 +13122,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #onAttachedToWindow() */ protected void onDetachedFromWindow() { + } + + /** + * This is a framework-internal mirror of onDetachedFromWindow() that's called + * after onDetachedFromWindow(). + * + * If you override this you *MUST* call super.onDetachedFromWindowInternal()! + * The super method should be called at the end of the overriden method to ensure + * subclasses are destroyed first + * + * @hide + */ + protected void onDetachedFromWindowInternal() { mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT; mPrivateFlags3 &= ~PFLAG3_IS_LAID_OUT; @@ -12442,15 +13152,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } private void cleanupDraw() { + resetDisplayList(); if (mAttachInfo != null) { - if (mDisplayList != null) { - mDisplayList.markDirty(); - mAttachInfo.mViewRootImpl.enqueueDisplayList(mDisplayList); - } mAttachInfo.mViewRootImpl.cancelInvalidate(this); - } else { - // Should never happen - resetDisplayList(); } } @@ -12618,6 +13322,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } onDetachedFromWindow(); + onDetachedFromWindowInternal(); ListenerInfo li = mListenerInfo; final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners = @@ -12919,11 +13624,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } if (layerType == mLayerType) { - if (layerType != LAYER_TYPE_NONE && paint != mLayerPaint) { - mLayerPaint = paint == null ? new Paint() : paint; - invalidateParentCaches(); - invalidate(true); - } + setLayerPaint(paint); return; } @@ -12980,7 +13681,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (layerType == LAYER_TYPE_HARDWARE) { HardwareLayer layer = getHardwareLayer(); if (layer != null) { - layer.setLayerPaint(paint); + layer.setLayerPaint(mLayerPaint); } invalidateViewProperty(false, false); } else { @@ -13040,19 +13741,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, switch (mLayerType) { case LAYER_TYPE_HARDWARE: - if (attachInfo.mHardwareRenderer != null && - attachInfo.mHardwareRenderer.isEnabled() && - attachInfo.mHardwareRenderer.validate()) { - getHardwareLayer(); - // TODO: We need a better way to handle this case - // If views have registered pre-draw listeners they need - // to be notified before we build the layer. Those listeners - // may however rely on other events to happen first so we - // cannot just invoke them here until they don't cancel the - // current frame - if (!attachInfo.mTreeObserver.hasOnPreDrawListeners()) { - attachInfo.mViewRootImpl.dispatchFlushHardwareLayerUpdates(); - } + getHardwareLayer(); + // TODO: We need a better way to handle this case + // If views have registered pre-draw listeners they need + // to be notified before we build the layer. Those listeners + // may however rely on other events to happen first so we + // cannot just invoke them here until they don't cancel the + // current frame + if (!attachInfo.mTreeObserver.hasOnPreDrawListeners()) { + attachInfo.mViewRootImpl.dispatchFlushHardwareLayerUpdates(); } break; case LAYER_TYPE_SOFTWARE: @@ -13073,8 +13770,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return null; } - if (!mAttachInfo.mHardwareRenderer.validate()) return null; - final int width = mRight - mLeft; final int height = mBottom - mTop; @@ -13084,16 +13779,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || mHardwareLayer == null) { if (mHardwareLayer == null) { - mHardwareLayer = mAttachInfo.mHardwareRenderer.createHardwareLayer( - width, height, isOpaque()); + mHardwareLayer = mAttachInfo.mHardwareRenderer.createDisplayListLayer( + width, height); mLocalDirtyRect.set(0, 0, width, height); - } else { - if (mHardwareLayer.getWidth() != width || mHardwareLayer.getHeight() != height) { - if (mHardwareLayer.resize(width, height)) { - mLocalDirtyRect.set(0, 0, width, height); - } - } - + } else if (mHardwareLayer.isValid()) { // This should not be necessary but applications that change // the parameters of their background drawable without calling // this.setBackground(Drawable) can leave the view in a bad state @@ -13101,23 +13790,24 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // not opaque.) computeOpaqueFlags(); - final boolean opaque = isOpaque(); - if (mHardwareLayer.isValid() && mHardwareLayer.isOpaque() != opaque) { - mHardwareLayer.setOpaque(opaque); + if (mHardwareLayer.prepare(width, height, isOpaque())) { mLocalDirtyRect.set(0, 0, width, height); } } // The layer is not valid if the underlying GPU resources cannot be allocated + mHardwareLayer.flushChanges(); if (!mHardwareLayer.isValid()) { return null; } mHardwareLayer.setLayerPaint(mLayerPaint); - mHardwareLayer.redrawLater(getHardwareLayerDisplayList(mHardwareLayer), mLocalDirtyRect); - ViewRootImpl viewRoot = getViewRootImpl(); - if (viewRoot != null) viewRoot.pushHardwareLayerUpdate(mHardwareLayer); - + DisplayList displayList = mHardwareLayer.startRecording(); + if (getDisplayList(displayList, true) != displayList) { + throw new IllegalStateException("getDisplayList() didn't return" + + " the input displaylist for a hardware layer!"); + } + mHardwareLayer.endRecording(mLocalDirtyRect); mLocalDirtyRect.setEmpty(); } @@ -13134,18 +13824,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ boolean destroyLayer(boolean valid) { if (mHardwareLayer != null) { - AttachInfo info = mAttachInfo; - if (info != null && info.mHardwareRenderer != null && - info.mHardwareRenderer.isEnabled() && - (valid || info.mHardwareRenderer.validate())) { - - info.mHardwareRenderer.cancelLayerUpdate(mHardwareLayer); - mHardwareLayer.destroy(); - mHardwareLayer = null; + mHardwareLayer.destroy(); + mHardwareLayer = null; - invalidate(true); - invalidateParentCaches(); - } + invalidate(true); + invalidateParentCaches(); return true; } return false; @@ -13260,20 +13943,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * @return The {@link HardwareRenderer} associated with that view or null if - * hardware rendering is not supported or this view is not attached - * to a window. - * - * @hide - */ - public HardwareRenderer getHardwareRenderer() { - if (mAttachInfo != null) { - return mAttachInfo.mHardwareRenderer; - } - return null; - } - - /** * Returns a DisplayList. If the incoming displayList is null, one will be created. * Otherwise, the same display list will be returned (after having been rendered into * along the way, depending on the invalidation state of the view). @@ -13284,7 +13953,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @return A new or reused DisplayList object. */ private DisplayList getDisplayList(DisplayList displayList, boolean isLayer) { - if (!canHaveDisplayList()) { + final HardwareRenderer renderer = getHardwareRenderer(); + if (renderer == null || !canHaveDisplayList()) { return null; } @@ -13308,7 +13978,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mRecreateDisplayList = true; } if (displayList == null) { - displayList = mAttachInfo.mHardwareRenderer.createDisplayList(getClass().getName()); + displayList = DisplayList.create(getClass().getName()); // If we're creating a new display list, make sure our parent gets invalidated // since they will need to recreate their display list to account for this // new child display list. @@ -13363,13 +14033,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } } finally { - displayList.end(); + displayList.end(renderer, canvas); displayList.setCaching(caching); if (isLayer) { displayList.setLeftTopRightBottom(0, 0, width, height); } else { setDisplayListProperties(displayList); } + + if (renderer != getHardwareRenderer()) { + Log.w(VIEW_LOG_TAG, "View was detached during a draw() call!"); + // TODO: Should this be elevated to a crash? + // For now have it behaves the same as it previously did, it + // will result in the DisplayListData being destroyed later + // than it could be but oh well... + } } } else if (!isLayer) { mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID; @@ -13380,19 +14058,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Get the DisplayList for the HardwareLayer - * - * @param layer The HardwareLayer whose DisplayList we want - * @return A DisplayList fopr the specified HardwareLayer - */ - private DisplayList getHardwareLayerDisplayList(HardwareLayer layer) { - DisplayList displayList = getDisplayList(layer.getDisplayList(), true); - layer.setDisplayList(displayList); - return displayList; - } - - - /** * <p>Returns a display list that can be used to draw this view again * without executing its draw method.</p> * @@ -13405,15 +14070,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return mDisplayList; } - private void clearDisplayList() { - if (mDisplayList != null) { - mDisplayList.clear(); + private void resetDisplayList() { + if (mDisplayList != null && mDisplayList.isValid()) { + mDisplayList.destroyDisplayListData(); } - } - private void resetDisplayList() { - if (mDisplayList != null) { - mDisplayList.reset(); + if (mBackgroundDisplayList != null && mBackgroundDisplayList.isValid()) { + mBackgroundDisplayList.destroyDisplayListData(); } } @@ -14016,6 +14679,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, displayList.setClipToBounds( (((ViewGroup) mParent).mGroupFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0); } + if (this instanceof ViewGroup) { + displayList.setIsolatedZVolume( + (((ViewGroup) this).mGroupFlags & ViewGroup.FLAG_ISOLATED_Z_VOLUME) != 0); + } + displayList.setOutline(mOutline); + displayList.setClipToOutline(getClipToOutline()); + displayList.setCastsShadow(getCastsShadow()); + displayList.setUsesGlobalCamera(getUsesGlobalCamera()); float alpha = 1; if (mParent instanceof ViewGroup && (((ViewGroup) mParent).mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) { @@ -14028,7 +14699,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, alpha = t.getAlpha(); } if ((transformType & Transformation.TYPE_MATRIX) != 0) { - displayList.setMatrix(t.getMatrix()); + displayList.setStaticMatrix(t.getMatrix()); } } } @@ -14043,6 +14714,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } displayList.setTransformationInfo(alpha, mTransformationInfo.mTranslationX, mTransformationInfo.mTranslationY, + mTransformationInfo.mTranslationZ, mTransformationInfo.mRotation, mTransformationInfo.mRotationX, mTransformationInfo.mRotationY, mTransformationInfo.mScaleX, mTransformationInfo.mScaleY); @@ -14436,24 +15108,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, int saveCount; if (!dirtyOpaque) { - final Drawable background = mBackground; - if (background != null) { - final int scrollX = mScrollX; - final int scrollY = mScrollY; - - if (mBackgroundSizeChanged) { - background.setBounds(0, 0, mRight - mLeft, mBottom - mTop); - mBackgroundSizeChanged = false; - } - - if ((scrollX | scrollY) == 0) { - background.draw(canvas); - } else { - canvas.translate(scrollX, scrollY); - background.draw(canvas); - canvas.translate(-scrollX, -scrollY); - } - } + drawBackground(canvas); } // skip step 2 & 5 if possible (common case) @@ -14620,6 +15275,84 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Draws the background onto the specified canvas. + * + * @param canvas Canvas on which to draw the background + */ + private void drawBackground(Canvas canvas) { + final Drawable background = mBackground; + if (background == null) { + return; + } + + if (mBackgroundSizeChanged) { + background.setBounds(0, 0, mRight - mLeft, mBottom - mTop); + mBackgroundSizeChanged = false; + } + + // Attempt to use a display list if requested. + if (canvas.isHardwareAccelerated() && mAttachInfo != null + && mAttachInfo.mHardwareRenderer != null) { + mBackgroundDisplayList = getDrawableDisplayList(background, mBackgroundDisplayList); + + final DisplayList displayList = mBackgroundDisplayList; + if (displayList != null && displayList.isValid()) { + setBackgroundDisplayListProperties(displayList); + ((HardwareCanvas) canvas).drawDisplayList(displayList); + return; + } + } + + final int scrollX = mScrollX; + final int scrollY = mScrollY; + if ((scrollX | scrollY) == 0) { + background.draw(canvas); + } else { + canvas.translate(scrollX, scrollY); + background.draw(canvas); + canvas.translate(-scrollX, -scrollY); + } + } + + /** + * Set up background drawable display list properties. + * + * @param displayList Valid display list for the background drawable + */ + private void setBackgroundDisplayListProperties(DisplayList displayList) { + displayList.setTranslationX(mScrollX); + displayList.setTranslationY(mScrollY); + } + + /** + * Creates a new display list or updates the existing display list for the + * specified Drawable. + * + * @param drawable Drawable for which to create a display list + * @param displayList Existing display list, or {@code null} + * @return A valid display list for the specified drawable + */ + private DisplayList getDrawableDisplayList(Drawable drawable, DisplayList displayList) { + if (displayList == null) { + displayList = DisplayList.create(drawable.getClass().getName()); + } + + final Rect bounds = drawable.getBounds(); + final int width = bounds.width(); + final int height = bounds.height(); + final HardwareCanvas canvas = displayList.start(width, height); + drawable.draw(canvas); + displayList.end(getHardwareRenderer(), canvas); + + // Set up drawable properties that are view-independent. + displayList.setLeftTopRightBottom(bounds.left, bounds.top, bounds.right, bounds.bottom); + displayList.setProjectBackwards(drawable.isProjected()); + displayList.setProjectionReceiver(true); + displayList.setClipToBounds(false); + return displayList; + } + + /** * Returns the overlay for this view, creating it if it does not yet exist. * Adding drawables to the overlay will cause them to be displayed whenever * the view itself is redrawn. Objects in the overlay should be actively @@ -14960,9 +15693,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @param drawable the drawable to invalidate */ + @Override public void invalidateDrawable(Drawable drawable) { if (verifyDrawable(drawable)) { - final Rect dirty = drawable.getBounds(); + final Rect dirty = drawable.getDirtyBounds(); final int scrollX = mScrollX; final int scrollY = mScrollY; @@ -14979,6 +15713,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param when the time at which the action must occur. Uses the * {@link SystemClock#uptimeMillis} timebase. */ + @Override public void scheduleDrawable(Drawable who, Runnable what, long when) { if (verifyDrawable(who) && what != null) { final long delay = when - SystemClock.uptimeMillis(); @@ -14998,14 +15733,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param who the recipient of the action * @param what the action to cancel */ + @Override public void unscheduleDrawable(Drawable who, Runnable what) { if (verifyDrawable(who) && what != null) { if (mAttachInfo != null) { mAttachInfo.mViewRootImpl.mChoreographer.removeCallbacks( Choreographer.CALLBACK_ANIMATION, what, who); - } else { - ViewRootImpl.getRunQueue().removeCallbacks(what); } + ViewRootImpl.getRunQueue().removeCallbacks(what); } } @@ -15068,7 +15803,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @hide */ - public void onResolveDrawables(int layoutDirection) { + public void onResolveDrawables(@ResolvedLayoutDir int layoutDirection) { } /** @@ -15115,7 +15850,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see Drawable#setState(int[]) */ protected void drawableStateChanged() { - Drawable d = mBackground; + final Drawable d = mBackground; if (d != null && d.isStateful()) { d.setState(getDrawableState()); } @@ -15300,7 +16035,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Drawable d= null; if (resid != 0) { - d = mResources.getDrawable(resid); + d = mContext.getDrawable(resid); } setBackground(d); @@ -15835,8 +16570,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return false; } - transformMotionEventToGlobal(ev); - ev.offsetLocation(info.mWindowLeft, info.mWindowTop); + final Matrix m = info.mTmpMatrix; + m.set(Matrix.IDENTITY_MATRIX); + transformMatrixToGlobal(m); + ev.transform(m); return true; } @@ -15854,54 +16591,60 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return false; } - ev.offsetLocation(-info.mWindowLeft, -info.mWindowTop); - transformMotionEventToLocal(ev); + final Matrix m = info.mTmpMatrix; + m.set(Matrix.IDENTITY_MATRIX); + transformMatrixToLocal(m); + ev.transform(m); return true; } /** - * Recursive helper method that applies transformations in post-order. + * Modifies the input matrix such that it maps view-local coordinates to + * on-screen coordinates. * - * @param ev the on-screen motion event + * @param m input matrix to modify */ - private void transformMotionEventToLocal(MotionEvent ev) { + void transformMatrixToGlobal(Matrix m) { final ViewParent parent = mParent; if (parent instanceof View) { final View vp = (View) parent; - vp.transformMotionEventToLocal(ev); - ev.offsetLocation(vp.mScrollX, vp.mScrollY); + vp.transformMatrixToGlobal(m); + m.postTranslate(-vp.mScrollX, -vp.mScrollY); } else if (parent instanceof ViewRootImpl) { final ViewRootImpl vr = (ViewRootImpl) parent; - ev.offsetLocation(0, vr.mCurScrollY); + vr.transformMatrixToGlobal(m); + m.postTranslate(0, -vr.mCurScrollY); } - ev.offsetLocation(-mLeft, -mTop); + m.postTranslate(mLeft, mTop); if (!hasIdentityMatrix()) { - ev.transform(getInverseMatrix()); + m.postConcat(getMatrix()); } } /** - * Recursive helper method that applies transformations in pre-order. + * Modifies the input matrix such that it maps on-screen coordinates to + * view-local coordinates. * - * @param ev the on-screen motion event + * @param m input matrix to modify */ - private void transformMotionEventToGlobal(MotionEvent ev) { - if (!hasIdentityMatrix()) { - ev.transform(getMatrix()); - } - - ev.offsetLocation(mLeft, mTop); - + void transformMatrixToLocal(Matrix m) { final ViewParent parent = mParent; if (parent instanceof View) { final View vp = (View) parent; - ev.offsetLocation(-vp.mScrollX, -vp.mScrollY); - vp.transformMotionEventToGlobal(ev); + vp.transformMatrixToLocal(m); + m.preTranslate(vp.mScrollX, vp.mScrollY); } else if (parent instanceof ViewRootImpl) { final ViewRootImpl vr = (ViewRootImpl) parent; - ev.offsetLocation(0, -vr.mCurScrollY); + vr.transformMatrixToLocal(m); + m.preTranslate(0, vr.mCurScrollY); + } + + m.preTranslate(-mLeft, -mTop); + + if (!hasIdentityMatrix()) { + m.preConcat(getInverseMatrix()); } } @@ -16184,7 +16927,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Returns this view's tag. * - * @return the Object stored in this view as a tag + * @return the Object stored in this view as a tag, or {@code null} if not + * set * * @see #setTag(Object) * @see #getTag(int) @@ -16214,7 +16958,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @param key The key identifying the tag * - * @return the Object stored in this view as a tag + * @return the Object stored in this view as a tag, or {@code null} if not + * set * * @see #setTag(int, Object) * @see #getTag() @@ -17886,6 +18631,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_START, to = "VIEW_START"), @ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_END, to = "VIEW_END") }) + @TextAlignment public int getRawTextAlignment() { return (mPrivateFlags2 & PFLAG2_TEXT_ALIGNMENT_MASK) >> PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT; } @@ -17909,7 +18655,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @attr ref android.R.styleable#View_textAlignment */ - public void setTextAlignment(int textAlignment) { + public void setTextAlignment(@TextAlignment int textAlignment) { if (textAlignment != getRawTextAlignment()) { // Reset the current and resolved text alignment mPrivateFlags2 &= ~PFLAG2_TEXT_ALIGNMENT_MASK; @@ -17950,6 +18696,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_START, to = "VIEW_START"), @ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_END, to = "VIEW_END") }) + @TextAlignment public int getTextAlignment() { return (mPrivateFlags2 & PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK) >> PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT; @@ -18112,6 +18859,33 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } + /** + * Gets the Views in the hierarchy affected by entering and exiting Activity Scene transitions. + * @param transitioningViews This View will be added to transitioningViews if it is VISIBLE and + * a normal View or a ViewGroup with + * {@link android.view.ViewGroup#isTransitionGroup()} true. + * @hide + */ + public void captureTransitioningViews(List<View> transitioningViews) { + if (getVisibility() == View.VISIBLE) { + transitioningViews.add(this); + } + } + + /** + * Adds all Views that have {@link #getSharedElementName()} non-null to sharedElements. + * @param sharedElements Will contain all Views in the hierarchy having a shared element name. + * @hide + */ + public void findSharedElements(Map<String, View> sharedElements) { + if (getVisibility() == VISIBLE) { + String sharedElementName = getSharedElementName(); + if (sharedElementName != null) { + sharedElements.put(sharedElementName, this); + } + } + } + // // Properties // @@ -18164,6 +18938,22 @@ public class View implements Drawable.Callback, KeyEvent.Callback, }; /** + * A Property wrapper around the <code>translationZ</code> functionality handled by the + * {@link View#setTranslationZ(float)} and {@link View#getTranslationZ()} methods. + */ + public static final Property<View, Float> TRANSLATION_Z = new FloatProperty<View>("translationZ") { + @Override + public void setValue(View object, float value) { + object.setTranslationZ(value); + } + + @Override + public Float get(View object) { + return object.getTranslationZ(); + } + }; + + /** * A Property wrapper around the <code>x</code> functionality handled by the * {@link View#setX(float)} and {@link View#getX()} methods. */ @@ -18469,6 +19259,35 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Specifies that the shared name of the View to be shared with another Activity. + * When transitioning between Activities, the name links a UI element in the starting + * Activity to UI element in the called Activity. Names should be unique in the + * View hierarchy. + * + * @param sharedElementName The cross-Activity View identifier. The called Activity will use + * the name to match the location with a View in its layout. + * @see android.app.ActivityOptions#makeSceneTransitionAnimation(android.os.Bundle) + */ + public void setSharedElementName(String sharedElementName) { + setTagInternal(com.android.internal.R.id.shared_element_name, sharedElementName); + } + + /** + * Returns the shared name of the View to be shared with another Activity. + * When transitioning between Activities, the name links a UI element in the starting + * Activity to UI element in the called Activity. Names should be unique in the + * View hierarchy. + * + * <p>This returns null if the View is not a shared element or the name if it is.</p> + * + * @return The name used for this View for cross-Activity transitions or null if + * this View has not been identified as shared. + */ + public String getSharedElementName() { + return (String) getTag(com.android.internal.R.id.shared_element_name); + } + + /** * Interface definition for a callback to be invoked when a hardware key event is * dispatched to this view. The callback will be invoked before the key event is * given to the view. This is only useful for hardware keyboards; a software input @@ -18668,6 +19487,31 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public void onViewDetachedFromWindow(View v); } + /** + * Listener for applying window insets on a view in a custom way. + * + * <p>Apps may choose to implement this interface if they want to apply custom policy + * to the way that window insets are treated for a view. If an OnApplyWindowInsetsListener + * is set, its + * {@link OnApplyWindowInsetsListener#onApplyWindowInsets(View, WindowInsets) onApplyWindowInsets} + * method will be called instead of the View's own + * {@link #onApplyWindowInsets(WindowInsets) onApplyWindowInsets} method. The listener + * may optionally call the parameter View's <code>onApplyWindowInsets</code> method to apply + * the View's normal behavior as part of its own.</p> + */ + public interface OnApplyWindowInsetsListener { + /** + * When {@link View#setOnApplyWindowInsetsListener(View.OnApplyWindowInsetsListener) set} + * on a View, this listener method will be called instead of the view's own + * {@link View#onApplyWindowInsets(WindowInsets) onApplyWindowInsets} method. + * + * @param v The view applying window insets + * @param insets The insets to apply + * @return The insets supplied, minus any insets that were consumed + */ + public WindowInsets onApplyWindowInsets(View v, WindowInsets insets); + } + private final class UnsetPressedState implements Runnable { public void run() { setPressed(false); @@ -18762,8 +19606,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, final Callbacks mRootCallbacks; - HardwareCanvas mHardwareCanvas; - IWindowId mIWindowId; WindowId mWindowId; @@ -18773,7 +19615,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, View mRootView; IBinder mPanelParentWindowToken; - Surface mSurface; boolean mHardwareAccelerated; boolean mHardwareAccelerationRequested; diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java index c3f064f..e67659c 100644 --- a/core/java/android/view/ViewConfiguration.java +++ b/core/java/android/view/ViewConfiguration.java @@ -212,6 +212,14 @@ public class ViewConfiguration { */ private static final int OVERFLING_DISTANCE = 6; + /** + * Configuration values for overriding {@link #hasPermanentMenuKey()} behavior. + * These constants must match the definition in res/values/config.xml. + */ + private static final int HAS_PERMANENT_MENU_KEY_AUTODETECT = 0; + private static final int HAS_PERMANENT_MENU_KEY_TRUE = 1; + private static final int HAS_PERMANENT_MENU_KEY_FALSE = 2; + private final int mEdgeSlop; private final int mFadingEdgeLength; private final int mMinimumFlingVelocity; @@ -296,12 +304,31 @@ public class ViewConfiguration { mOverflingDistance = (int) (sizeAndDensity * OVERFLING_DISTANCE + 0.5f); if (!sHasPermanentMenuKeySet) { - IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); - try { - sHasPermanentMenuKey = !wm.hasNavigationBar(); - sHasPermanentMenuKeySet = true; - } catch (RemoteException ex) { - sHasPermanentMenuKey = false; + final int configVal = res.getInteger( + com.android.internal.R.integer.config_overrideHasPermanentMenuKey); + + switch (configVal) { + default: + case HAS_PERMANENT_MENU_KEY_AUTODETECT: { + IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); + try { + sHasPermanentMenuKey = !wm.hasNavigationBar(); + sHasPermanentMenuKeySet = true; + } catch (RemoteException ex) { + sHasPermanentMenuKey = false; + } + } + break; + + case HAS_PERMANENT_MENU_KEY_TRUE: + sHasPermanentMenuKey = true; + sHasPermanentMenuKeySet = true; + break; + + case HAS_PERMANENT_MENU_KEY_FALSE: + sHasPermanentMenuKey = false; + sHasPermanentMenuKeySet = true; + break; } } diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 5763e72..f9b9401 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -31,6 +31,7 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; import android.os.Build; +import android.os.Bundle; import android.os.Parcelable; import android.os.SystemClock; import android.util.AttributeSet; @@ -38,7 +39,6 @@ import android.util.Log; import android.util.Pools.SynchronizedPool; import android.util.SparseArray; import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.Animation; import android.view.animation.AnimationUtils; @@ -51,6 +51,8 @@ import com.android.internal.util.Predicate; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; +import java.util.List; +import java.util.Map; import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; @@ -356,6 +358,16 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager private static final int FLAG_LAYOUT_MODE_WAS_EXPLICITLY_SET = 0x800000; /** + * When true, indicates that all 3d composited descendents are contained within this group, and + * will not be interleaved with other 3d composited content. + */ + static final int FLAG_ISOLATED_Z_VOLUME = 0x1000000; + + static final int FLAG_IS_TRANSITION_GROUP = 0x2000000; + + static final int FLAG_IS_TRANSITION_GROUP_SET = 0x4000000; + + /** * Indicates which types of drawing caches are to be kept in memory. * This field should be made private, so it is hidden from the SDK. * {@hide} @@ -456,20 +468,21 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager private int mChildCountWithTransientState = 0; public ViewGroup(Context context) { - super(context); - initViewGroup(); + this(context, null); } public ViewGroup(Context context, AttributeSet attrs) { - super(context, attrs); - initViewGroup(); - initFromAttributes(context, attrs, 0); + this(context, attrs, 0); } - public ViewGroup(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); initViewGroup(); - initFromAttributes(context, attrs, defStyle); + initFromAttributes(context, attrs, defStyleAttr, defStyleRes); } private boolean debugDraw() { @@ -486,6 +499,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager mGroupFlags |= FLAG_ANIMATION_DONE; mGroupFlags |= FLAG_ANIMATION_CACHE; mGroupFlags |= FLAG_ALWAYS_DRAWN_WITH_CACHE; + mGroupFlags |= FLAG_ISOLATED_Z_VOLUME; if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) { mGroupFlags |= FLAG_SPLIT_MOTION_EVENTS; @@ -499,8 +513,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager mPersistentDrawingCache = PERSISTENT_SCROLLING_CACHE; } - private void initFromAttributes(Context context, AttributeSet attrs, int defStyle) { - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewGroup, defStyle, 0); + private void initFromAttributes( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewGroup, defStyleAttr, + defStyleRes); final int N = a.getIndexCount(); for (int i = 0; i < N; i++) { @@ -512,6 +528,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager case R.styleable.ViewGroup_clipToPadding: setClipToPadding(a.getBoolean(attr, true)); break; + case R.styleable.ViewGroup_isolatedZVolume: + setIsolatedZVolume(a.getBoolean(attr, true)); + break; case R.styleable.ViewGroup_animationCache: setAnimationCacheEnabled(a.getBoolean(attr, true)); break; @@ -545,6 +564,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager case R.styleable.ViewGroup_layoutMode: setLayoutMode(a.getInt(attr, LAYOUT_MODE_UNDEFINED)); break; + case R.styleable.ViewGroup_transitionGroup: + setTransitionGroup(a.getBoolean(attr, false)); + break; } } @@ -597,7 +619,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager @Override void handleFocusGainInternal(int direction, Rect previouslyFocusedRect) { if (mFocused != null) { - mFocused.unFocus(); + mFocused.unFocus(this); mFocused = null; } super.handleFocusGainInternal(direction, previouslyFocusedRect); @@ -615,12 +637,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } // Unfocus us, if necessary - super.unFocus(); + super.unFocus(focused); // We had a previous notion of who had focus. Clear it. if (mFocused != child) { if (mFocused != null) { - mFocused.unFocus(); + mFocused.unFocus(focused); } mFocused = child; @@ -811,14 +833,14 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * {@inheritDoc} */ @Override - void unFocus() { + void unFocus(View focused) { if (DBG) { System.out.println(this + " unFocus()"); } if (mFocused == null) { - super.unFocus(); + super.unFocus(focused); } else { - mFocused.unFocus(); + mFocused.unFocus(focused); mFocused = null; } } @@ -2277,6 +2299,39 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** + * Returns true if this ViewGroup should be considered as a single entity for removal + * when executing an Activity transition. If this is false, child elements will move + * individually during the transition. + * @return True if the ViewGroup should be acted on together during an Activity transition. + * The default value is false when the background is null and true when the background + * is not null or if {@link #getSharedElementName()} is not null. + */ + public boolean isTransitionGroup() { + if ((mGroupFlags & FLAG_IS_TRANSITION_GROUP_SET) != 0) { + return ((mGroupFlags & FLAG_IS_TRANSITION_GROUP) != 0); + } else { + return getBackground() != null || getSharedElementName() != null; + } + } + + /** + * Changes whether or not this ViewGroup should be treated as a single entity during + * ActivityTransitions. + * @param isTransitionGroup Whether or not the ViewGroup should be treated as a unit + * in Activity transitions. If false, the ViewGroup won't transition, + * only its children. If true, the entire ViewGroup will transition + * together. + */ + public void setTransitionGroup(boolean isTransitionGroup) { + mGroupFlags |= FLAG_IS_TRANSITION_GROUP_SET; + if (isTransitionGroup) { + mGroupFlags |= FLAG_IS_TRANSITION_GROUP; + } else { + mGroupFlags &= ~FLAG_IS_TRANSITION_GROUP; + } + } + + /** * {@inheritDoc} */ public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { @@ -2509,13 +2564,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfoInternal(info); if (mAttachInfo != null) { - ArrayList<View> childrenForAccessibility = mAttachInfo.mTempArrayList; + final ArrayList<View> childrenForAccessibility = mAttachInfo.mTempArrayList; childrenForAccessibility.clear(); addChildrenForAccessibility(childrenForAccessibility); final int childrenForAccessibilityCount = childrenForAccessibility.size(); for (int i = 0; i < childrenForAccessibilityCount; i++) { - View child = childrenForAccessibility.get(i); - info.addChild(child); + final View child = childrenForAccessibility.get(i); + info.addChildUnchecked(child); } childrenForAccessibility.clear(); } @@ -2583,6 +2638,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager for (int i = 0; i < count; i++) { children[i].dispatchDetachedFromWindow(); } + clearDisappearingChildren(); super.dispatchDetachedFromWindow(); } @@ -3103,7 +3159,44 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** - * Returns whether ths group's children are clipped to their bounds before drawing. + * Returns whether this group's descendents are drawn in their own + * independent Z volume. Views drawn in one contained volume will not + * interleave with views in another, even if their Z values are interleaved. + * The default value is true. + * @see #setIsolatedZVolume(boolean) + * + * @return True if the ViewGroup has an isolated Z volume. + * + * @hide + */ + public boolean hasIsolatedZVolume() { + return ((mGroupFlags & FLAG_ISOLATED_Z_VOLUME) != 0); + } + + /** + * By default, only direct children of a group can interleave drawing order + * by interleaving Z values. Set to false on individual groups to enable Z + * interleaving of views that aren't direct siblings. + * + * @return True if the group should be an isolated Z volume with its own Z + * ordering space, false if its decendents should inhabit the + * inherited Z ordering volume. + * @attr ref android.R.styleable#ViewGroup_isolatedZVolume + * + * @hide + */ + public void setIsolatedZVolume(boolean isolateZVolume) { + boolean previousValue = (mGroupFlags & FLAG_ISOLATED_Z_VOLUME) != 0; + if (isolateZVolume != previousValue) { + setBooleanFlag(FLAG_ISOLATED_Z_VOLUME, isolateZVolume); + if (mDisplayList != null) { + mDisplayList.setIsolatedZVolume(isolateZVolume); + } + } + } + + /** + * Returns whether this group's children are clipped to their bounds before drawing. * The default value is true. * @see #setClipChildren(boolean) * @@ -3826,7 +3919,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager boolean clearChildFocus = false; if (view == mFocused) { - view.unFocus(); + view.unFocus(null); clearChildFocus = true; } @@ -3921,7 +4014,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } if (view == focused) { - view.unFocus(); + view.unFocus(null); clearChildFocus = true; } @@ -4008,7 +4101,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } if (view == focused) { - view.unFocus(); + view.unFocus(null); clearChildFocus = true; } @@ -5217,8 +5310,17 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * this if you don't want animations for exiting views to stack up. */ public void clearDisappearingChildren() { - if (mDisappearingChildren != null) { - mDisappearingChildren.clear(); + final ArrayList<View> disappearingChildren = mDisappearingChildren; + if (disappearingChildren != null) { + final int count = disappearingChildren.size(); + for (int i = 0; i < count; i++) { + final View view = disappearingChildren.get(i); + if (view.mAttachInfo != null) { + view.dispatchDetachedFromWindow(); + } + view.clearAnimation(); + } + disappearingChildren.clear(); invalidate(); } } @@ -5429,21 +5531,19 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } - @Override - protected boolean fitSystemWindows(Rect insets) { - boolean done = super.fitSystemWindows(insets); - if (!done) { - final int count = mChildrenCount; - final View[] children = mChildren; + public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) { + insets = super.dispatchApplyWindowInsets(insets); + if (insets.hasInsets()) { + final int count = getChildCount(); for (int i = 0; i < count; i++) { - done = children[i].fitSystemWindows(insets); - if (done) { + insets = getChildAt(i).dispatchApplyWindowInsets(insets); + if (!insets.hasInsets()) { break; } } } - return done; + return insets; } /** @@ -5796,6 +5896,37 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager protected void onSetLayoutParams(View child, LayoutParams layoutParams) { } + /** @hide */ + @Override + public void captureTransitioningViews(List<View> transitioningViews) { + if (getVisibility() != View.VISIBLE) { + return; + } + if (isTransitionGroup()) { + transitioningViews.add(this); + } else { + int count = getChildCount(); + for (int i = 0; i < count; i++) { + View child = getChildAt(i); + child.captureTransitioningViews(transitioningViews); + } + } + } + + /** @hide */ + @Override + public void findSharedElements(Map<String, View> sharedElements) { + if (getVisibility() != VISIBLE) { + return; + } + super.findSharedElements(sharedElements); + int count = getChildCount(); + for (int i = 0; i < count; i++) { + View child = getChildAt(i); + child.findSharedElements(sharedElements); + } + } + /** * LayoutParams are used by views to tell their parents how they want to be * laid out. See diff --git a/core/java/android/view/ViewOverlay.java b/core/java/android/view/ViewOverlay.java index 975931a..47de780 100644 --- a/core/java/android/view/ViewOverlay.java +++ b/core/java/android/view/ViewOverlay.java @@ -155,6 +155,11 @@ public class ViewOverlay { } } + @Override + protected boolean verifyDrawable(Drawable who) { + return super.verifyDrawable(who) || (mDrawables != null && mDrawables.contains(who)); + } + public void add(View child) { if (child.getParent() instanceof ViewGroup) { ViewGroup parent = (ViewGroup) child.getParent(); diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java index 67a94be..1892aa7 100644 --- a/core/java/android/view/ViewPropertyAnimator.java +++ b/core/java/android/view/ViewPropertyAnimator.java @@ -136,17 +136,18 @@ public class ViewPropertyAnimator { private static final int NONE = 0x0000; private static final int TRANSLATION_X = 0x0001; private static final int TRANSLATION_Y = 0x0002; - private static final int SCALE_X = 0x0004; - private static final int SCALE_Y = 0x0008; - private static final int ROTATION = 0x0010; - private static final int ROTATION_X = 0x0020; - private static final int ROTATION_Y = 0x0040; - private static final int X = 0x0080; - private static final int Y = 0x0100; - private static final int ALPHA = 0x0200; - - private static final int TRANSFORM_MASK = TRANSLATION_X | TRANSLATION_Y | SCALE_X | SCALE_Y | - ROTATION | ROTATION_X | ROTATION_Y | X | Y; + private static final int TRANSLATION_Z = 0x0004; + private static final int SCALE_X = 0x0008; + private static final int SCALE_Y = 0x0010; + private static final int ROTATION = 0x0020; + private static final int ROTATION_X = 0x0040; + private static final int ROTATION_Y = 0x0080; + private static final int X = 0x0100; + private static final int Y = 0x0200; + private static final int ALPHA = 0x0400; + + private static final int TRANSFORM_MASK = TRANSLATION_X | TRANSLATION_Y | TRANSLATION_Z | + SCALE_X | SCALE_Y | ROTATION | ROTATION_X | ROTATION_Y | X | Y; /** * The mechanism by which the user can request several properties that are then animated @@ -599,6 +600,31 @@ public class ViewPropertyAnimator { } /** + * This method will cause the View's <code>translationZ</code> property to be animated to the + * specified value. Animations already running on the property will be canceled. + * + * @param value The value to be animated to. + * @see View#setTranslationZ(float) + * @return This object, allowing calls to methods in this class to be chained. + */ + public ViewPropertyAnimator translationZ(float value) { + animateProperty(TRANSLATION_Z, value); + return this; + } + + /** + * This method will cause the View's <code>translationZ</code> property to be animated by the + * specified value. Animations already running on the property will be canceled. + * + * @param value The amount to be animated by, as an offset from the current value. + * @see View#setTranslationZ(float) + * @return This object, allowing calls to methods in this class to be chained. + */ + public ViewPropertyAnimator translationZBy(float value) { + animatePropertyBy(TRANSLATION_Z, value); + return this; + } + /** * This method will cause the View's <code>scaleX</code> property to be animated to the * specified value. Animations already running on the property will be canceled. * @@ -909,6 +935,10 @@ public class ViewPropertyAnimator { info.mTranslationY = value; if (displayList != null) displayList.setTranslationY(value); break; + case TRANSLATION_Z: + info.mTranslationZ = value; + if (displayList != null) displayList.setTranslationZ(value); + break; case ROTATION: info.mRotation = value; if (displayList != null) displayList.setRotation(value); @@ -957,6 +987,8 @@ public class ViewPropertyAnimator { return info.mTranslationX; case TRANSLATION_Y: return info.mTranslationY; + case TRANSLATION_Z: + return info.mTranslationZ; case ROTATION: return info.mRotation; case ROTATION_X: diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index d779628..18517c5 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -28,6 +28,7 @@ import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Canvas; +import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.Point; @@ -55,6 +56,7 @@ import android.util.DisplayMetrics; import android.util.Log; import android.util.Slog; import android.util.TypedValue; +import android.view.Surface.OutOfResourcesException; import android.view.View.AttachInfo; import android.view.View.MeasureSpec; import android.view.accessibility.AccessibilityEvent; @@ -68,7 +70,6 @@ import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Interpolator; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; -import android.view.Surface.OutOfResourcesException; import android.widget.Scroller; import com.android.internal.R; @@ -108,7 +109,6 @@ public final class ViewRootImpl implements ViewParent, private static final boolean DEBUG_IMF = false || LOCAL_LOGV; private static final boolean DEBUG_CONFIGURATION = false || LOCAL_LOGV; private static final boolean DEBUG_FPS = false; - private static final boolean DEBUG_INPUT_PROCESSING = false || LOCAL_LOGV; /** * Set this system property to true to force the view hierarchy to render @@ -267,6 +267,10 @@ public final class ViewRootImpl implements ViewParent, HardwareLayer mResizeBuffer; long mResizeBufferStartTime; int mResizeBufferDuration; + // Used to block the creation of the ResizeBuffer due to invalidations in + // the previous DisplayList tree that must prevent re-execution. + // Currently this means a functor was detached. + boolean mBlockResizeBuffer; static final Interpolator mResizeInterpolator = new AccelerateDecelerateInterpolator(); private ArrayList<LayoutTransition> mPendingTransitions; @@ -290,8 +294,6 @@ public final class ViewRootImpl implements ViewParent, private long mFpsPrevTime = -1; private int mFpsNumFrames; - private final ArrayList<DisplayList> mDisplayLists = new ArrayList<DisplayList>(); - /** * see {@link #playSoundEffect(int)} */ @@ -616,7 +618,6 @@ public final class ViewRootImpl implements ViewParent, } void destroyHardwareResources() { - invalidateDisplayLists(); if (mAttachInfo.mHardwareRenderer != null) { mAttachInfo.mHardwareRenderer.destroyHardwareResources(mView); mAttachInfo.mHardwareRenderer.destroy(false); @@ -630,23 +631,25 @@ public final class ViewRootImpl implements ViewParent, HardwareRenderer.trimMemory(ComponentCallbacks2.TRIM_MEMORY_MODERATE); } } else { - invalidateDisplayLists(); - if (mAttachInfo.mHardwareRenderer != null && - mAttachInfo.mHardwareRenderer.isEnabled()) { - mAttachInfo.mHardwareRenderer.destroyLayers(mView); - } + destroyHardwareLayer(mView); } } - void pushHardwareLayerUpdate(HardwareLayer layer) { - if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) { - mAttachInfo.mHardwareRenderer.pushLayerUpdate(layer); + private static void destroyHardwareLayer(View view) { + view.destroyLayer(true); + + if (view instanceof ViewGroup) { + ViewGroup group = (ViewGroup) view; + + int count = group.getChildCount(); + for (int i = 0; i < count; i++) { + destroyHardwareLayer(group.getChildAt(i)); + } } } void flushHardwareLayerUpdates() { - if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled() && - mAttachInfo.mHardwareRenderer.validate()) { + if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) { mAttachInfo.mHardwareRenderer.flushLayerUpdates(); } } @@ -656,15 +659,15 @@ public final class ViewRootImpl implements ViewParent, mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(MSG_FLUSH_LAYER_UPDATES)); } - public boolean attachFunctor(int functor) { + public void attachFunctor(int functor) { //noinspection SimplifiableIfStatement if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) { - return mAttachInfo.mHardwareRenderer.attachFunctor(mAttachInfo, functor); + mAttachInfo.mHardwareRenderer.attachFunctor(mAttachInfo, functor); } - return false; } public void detachFunctor(int functor) { + mBlockResizeBuffer = true; if (mAttachInfo.mHardwareRenderer != null) { mAttachInfo.mHardwareRenderer.detachFunctor(functor); } @@ -715,7 +718,7 @@ public final class ViewRootImpl implements ViewParent, } final boolean translucent = attrs.format != PixelFormat.OPAQUE; - mAttachInfo.mHardwareRenderer = HardwareRenderer.createGlRenderer(2, translucent); + mAttachInfo.mHardwareRenderer = HardwareRenderer.create(translucent); if (mAttachInfo.mHardwareRenderer != null) { mAttachInfo.mHardwareRenderer.setName(attrs.getTitle().toString()); mAttachInfo.mHardwareAccelerated = @@ -930,14 +933,9 @@ public final class ViewRootImpl implements ViewParent, } void disposeResizeBuffer() { - if (mResizeBuffer != null && mAttachInfo.mHardwareRenderer != null) { - mAttachInfo.mHardwareRenderer.safelyRun(new Runnable() { - @Override - public void run() { - mResizeBuffer.destroy(); - mResizeBuffer = null; - } - }); + if (mResizeBuffer != null) { + mResizeBuffer.destroy(); + mResizeBuffer = null; } } @@ -1121,6 +1119,28 @@ public final class ViewRootImpl implements ViewParent, return windowSizeMayChange; } + /** + * Modifies the input matrix such that it maps view-local coordinates to + * on-screen coordinates. + * + * @param m input matrix to modify + */ + void transformMatrixToGlobal(Matrix m) { + final View.AttachInfo attachInfo = mAttachInfo; + m.postTranslate(attachInfo.mWindowLeft, attachInfo.mWindowTop); + } + + /** + * Modifies the input matrix such that it maps on-screen coordinates to + * view-local coordinates. + * + * @param m input matrix to modify + */ + void transformMatrixToLocal(Matrix m) { + final View.AttachInfo attachInfo = mAttachInfo; + m.preTranslate(-attachInfo.mWindowLeft, -attachInfo.mWindowTop); + } + private void performTraversals() { // cache mView since it is used so much below... final View host = mView; @@ -1191,11 +1211,6 @@ public final class ViewRootImpl implements ViewParent, desiredWindowHeight = packageMetrics.heightPixels; } - // For the very first time, tell the view hierarchy that it - // is attached to the window. Note that at this point the surface - // object is not initialized to its backing store, but soon it - // will be (assuming the window is visible). - attachInfo.mSurface = mSurface; // We used to use the following condition to choose 32 bits drawing caches: // PixelFormat.hasAlpha(lp.format) || lp.format == PixelFormat.RGBX_8888 // However, windows are now always 32 bits by default, so choose 32 bits @@ -1440,67 +1455,56 @@ public final class ViewRootImpl implements ViewParent, !mAttachInfo.mTurnOffWindowResizeAnim && mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled() && - mAttachInfo.mHardwareRenderer.validate() && - lp != null && !PixelFormat.formatHasAlpha(lp.format)) { + lp != null && !PixelFormat.formatHasAlpha(lp.format) + && !mBlockResizeBuffer) { disposeResizeBuffer(); - boolean completed = false; - HardwareCanvas hwRendererCanvas = mAttachInfo.mHardwareRenderer.getCanvas(); - HardwareCanvas layerCanvas = null; - try { - if (mResizeBuffer == null) { - mResizeBuffer = mAttachInfo.mHardwareRenderer.createHardwareLayer( - mWidth, mHeight, false); - } else if (mResizeBuffer.getWidth() != mWidth || - mResizeBuffer.getHeight() != mHeight) { - mResizeBuffer.resize(mWidth, mHeight); - } - // TODO: should handle create/resize failure - layerCanvas = mResizeBuffer.start(hwRendererCanvas); - final int restoreCount = layerCanvas.save(); - - int yoff; - final boolean scrolling = mScroller != null - && mScroller.computeScrollOffset(); - if (scrolling) { - yoff = mScroller.getCurrY(); - mScroller.abortAnimation(); - } else { - yoff = mScrollY; - } - - layerCanvas.translate(0, -yoff); - if (mTranslator != null) { - mTranslator.translateCanvas(layerCanvas); - } + if (mResizeBuffer == null) { + mResizeBuffer = mAttachInfo.mHardwareRenderer.createDisplayListLayer( + mWidth, mHeight); + } + mResizeBuffer.prepare(mWidth, mHeight, false); + DisplayList layerDisplayList = mResizeBuffer.startRecording(); + HardwareCanvas layerCanvas = layerDisplayList.start(mWidth, mHeight); + final int restoreCount = layerCanvas.save(); + + int yoff; + final boolean scrolling = mScroller != null + && mScroller.computeScrollOffset(); + if (scrolling) { + yoff = mScroller.getCurrY(); + mScroller.abortAnimation(); + } else { + yoff = mScrollY; + } - DisplayList displayList = mView.mDisplayList; - if (displayList != null && displayList.isValid()) { - layerCanvas.drawDisplayList(displayList, null, - DisplayList.FLAG_CLIP_CHILDREN); - } else { - mView.draw(layerCanvas); - } + layerCanvas.translate(0, -yoff); + if (mTranslator != null) { + mTranslator.translateCanvas(layerCanvas); + } - drawAccessibilityFocusedDrawableIfNeeded(layerCanvas); - - mResizeBufferStartTime = SystemClock.uptimeMillis(); - mResizeBufferDuration = mView.getResources().getInteger( - com.android.internal.R.integer.config_mediumAnimTime); - completed = true; - - layerCanvas.restoreToCount(restoreCount); - } catch (OutOfMemoryError e) { - Log.w(TAG, "Not enough memory for content change anim buffer", e); - } finally { - if (mResizeBuffer != null) { - mResizeBuffer.end(hwRendererCanvas); - if (!completed) { - disposeResizeBuffer(); - } - } + DisplayList displayList = mView.mDisplayList; + if (displayList != null && displayList.isValid()) { + layerCanvas.drawDisplayList(displayList, null, + DisplayList.FLAG_CLIP_CHILDREN); + } else { + mView.draw(layerCanvas); } + + drawAccessibilityFocusedDrawableIfNeeded(layerCanvas); + + mResizeBufferStartTime = SystemClock.uptimeMillis(); + mResizeBufferDuration = mView.getResources().getInteger( + com.android.internal.R.integer.config_mediumAnimTime); + + layerCanvas.restoreToCount(restoreCount); + layerDisplayList.end(mAttachInfo.mHardwareRenderer, layerCanvas); + layerDisplayList.setCaching(true); + layerDisplayList.setLeftTopRightBottom(0, 0, mWidth, mHeight); + mTempRect.set(0, 0, mWidth, mHeight); + mResizeBuffer.endRecording(mTempRect); + mAttachInfo.mHardwareRenderer.flushLayerUpdates(); } mAttachInfo.mContentInsets.set(mPendingContentInsets); if (DEBUG_LAYOUT) Log.v(TAG, "Content insets changing to: " @@ -1544,7 +1548,7 @@ public final class ViewRootImpl implements ViewParent, if (mAttachInfo.mHardwareRenderer != null) { try { hwInitialized = mAttachInfo.mHardwareRenderer.initialize( - mHolder.getSurface()); + mSurface); } catch (OutOfResourcesException e) { handleOutOfResourcesException(e); return; @@ -1571,7 +1575,7 @@ public final class ViewRootImpl implements ViewParent, mSurfaceHolder == null && mAttachInfo.mHardwareRenderer != null) { mFullRedrawNeeded = true; try { - mAttachInfo.mHardwareRenderer.updateSurface(mHolder.getSurface()); + mAttachInfo.mHardwareRenderer.updateSurface(mSurface); } catch (OutOfResourcesException e) { handleOutOfResourcesException(e); return; @@ -1654,7 +1658,7 @@ public final class ViewRootImpl implements ViewParent, mHeight != mAttachInfo.mHardwareRenderer.getHeight()) { mAttachInfo.mHardwareRenderer.setup(mWidth, mHeight); if (!hwInitialized) { - mAttachInfo.mHardwareRenderer.invalidate(mHolder.getSurface()); + mAttachInfo.mHardwareRenderer.invalidate(mSurface); mFullRedrawNeeded = true; } } @@ -2164,18 +2168,19 @@ public final class ViewRootImpl implements ViewParent, mResizePaint.setAlpha(mResizeAlpha); canvas.drawHardwareLayer(mResizeBuffer, 0.0f, mHardwareYOffset, mResizePaint); } - drawAccessibilityFocusedDrawableIfNeeded(canvas); + // TODO: this + if (!HardwareRenderer.sUseRenderThread) { + drawAccessibilityFocusedDrawableIfNeeded(canvas); + } } /** * @hide */ void outputDisplayList(View view) { - if (mAttachInfo != null && mAttachInfo.mHardwareCanvas != null) { - DisplayList displayList = view.getDisplayList(); - if (displayList != null) { - mAttachInfo.mHardwareCanvas.outputDisplayList(displayList); - } + DisplayList displayList = view.getDisplayList(); + if (displayList != null) { + displayList.output(); } } @@ -2360,8 +2365,6 @@ public final class ViewRootImpl implements ViewParent, appScale + ", width=" + mWidth + ", height=" + mHeight); } - invalidateDisplayLists(); - attachInfo.mTreeObserver.dispatchOnDraw(); if (!dirty.isEmpty() || mIsAnimating) { @@ -2374,6 +2377,7 @@ public final class ViewRootImpl implements ViewParent, mCurrentDirty.set(dirty); dirty.setEmpty(); + mBlockResizeBuffer = false; attachInfo.mHardwareRenderer.draw(mView, attachInfo, this, animating ? null : mCurrentDirty); } else { @@ -2391,7 +2395,7 @@ public final class ViewRootImpl implements ViewParent, try { attachInfo.mHardwareRenderer.initializeIfNeeded(mWidth, mHeight, - mHolder.getSurface()); + mSurface); } catch (OutOfResourcesException e) { handleOutOfResourcesException(e); return; @@ -2526,28 +2530,35 @@ public final class ViewRootImpl implements ViewParent, * @param canvas The canvas on which to draw. */ private void drawAccessibilityFocusedDrawableIfNeeded(Canvas canvas) { - AccessibilityManager manager = AccessibilityManager.getInstance(mView.mContext); + if (!mAttachInfo.mHasWindowFocus) { + return; + } + + final AccessibilityManager manager = AccessibilityManager.getInstance(mView.mContext); if (!manager.isEnabled() || !manager.isTouchExplorationEnabled()) { return; } - if (mAccessibilityFocusedHost == null || mAccessibilityFocusedHost.mAttachInfo == null) { + + final View host = mAccessibilityFocusedHost; + if (host == null || host.mAttachInfo == null) { return; } - Drawable drawable = getAccessibilityFocusedDrawable(); + + final Drawable drawable = getAccessibilityFocusedDrawable(); if (drawable == null) { return; } - AccessibilityNodeProvider provider = - mAccessibilityFocusedHost.getAccessibilityNodeProvider(); - Rect bounds = mView.mAttachInfo.mTmpInvalRect; + + final AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider(); + final Rect bounds = mView.mAttachInfo.mTmpInvalRect; if (provider == null) { - mAccessibilityFocusedHost.getBoundsOnScreen(bounds); - } else { - if (mAccessibilityFocusedVirtualView == null) { - return; - } + host.getBoundsOnScreen(bounds); + } else if (mAccessibilityFocusedVirtualView != null) { mAccessibilityFocusedVirtualView.getBoundsInScreen(bounds); + } else { + return; } + bounds.offset(-mAttachInfo.mWindowLeft, -mAttachInfo.mWindowTop); bounds.intersect(0, 0, mAttachInfo.mViewRootImpl.mWidth, mAttachInfo.mViewRootImpl.mHeight); drawable.setBounds(bounds); @@ -2563,7 +2574,7 @@ public final class ViewRootImpl implements ViewParent, R.attr.accessibilityFocusedDrawable, value, true); if (resolved) { mAttachInfo.mAccessibilityFocusDrawable = - mView.mContext.getResources().getDrawable(value.resourceId); + mView.mContext.getDrawable(value.resourceId); } } return mAttachInfo.mAccessibilityFocusDrawable; @@ -2571,20 +2582,6 @@ public final class ViewRootImpl implements ViewParent, return null; } - void invalidateDisplayLists() { - final ArrayList<DisplayList> displayLists = mDisplayLists; - final int count = displayLists.size(); - - for (int i = 0; i < count; i++) { - final DisplayList displayList = displayLists.get(i); - if (displayList.isDirty()) { - displayList.reset(); - } - } - - displayLists.clear(); - } - /** * @hide */ @@ -2826,10 +2823,6 @@ public final class ViewRootImpl implements ViewParent, void dispatchDetachedFromWindow() { if (mView != null && mView.mAttachInfo != null) { - if (mAttachInfo.mHardwareRenderer != null && - mAttachInfo.mHardwareRenderer.isEnabled()) { - mAttachInfo.mHardwareRenderer.validate(); - } mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false); mView.dispatchDetachedFromWindow(); } @@ -2846,7 +2839,6 @@ public final class ViewRootImpl implements ViewParent, mView.assignParent(null); mView = null; mAttachInfo.mRootView = null; - mAttachInfo.mSurface = null; mSurface.release(); @@ -3103,7 +3095,7 @@ public final class ViewRootImpl implements ViewParent, mFullRedrawNeeded = true; try { mAttachInfo.mHardwareRenderer.initializeIfNeeded( - mWidth, mHeight, mHolder.getSurface()); + mWidth, mHeight, mSurface); } catch (OutOfResourcesException e) { Log.e(TAG, "OutOfResourcesException locking surface", e); try { @@ -3152,8 +3144,6 @@ public final class ViewRootImpl implements ViewParent, mHasHadWindowFocus = true; } - setAccessibilityFocus(null, null); - if (mView != null && mAccessibilityManager.isEnabled()) { if (hasWindowFocus) { mView.sendAccessibilityEvent( @@ -3301,7 +3291,7 @@ public final class ViewRootImpl implements ViewParent, } else { // There's nothing to focus. Clear and propagate through the // hierarchy, but don't attempt to place new focus. - focused.clearFocusInternal(true, false); + focused.clearFocusInternal(null, true, false); return true; } } @@ -4493,8 +4483,7 @@ public final class ViewRootImpl implements ViewParent, // The active pointer id, or -1 if none. private int mActivePointerId = -1; - // Time and location where tracking started. - private long mStartTime; + // Location where tracking started. private float mStartX; private float mStartY; @@ -4522,9 +4511,6 @@ public final class ViewRootImpl implements ViewParent, private boolean mFlinging; private float mFlingVelocity; - // The last time a confirm key was pressed on the touch nav device - private long mLastConfirmKeyTime = Long.MAX_VALUE; - public SyntheticTouchNavigationHandler() { super(true); } @@ -4591,7 +4577,6 @@ public final class ViewRootImpl implements ViewParent, mActivePointerId = event.getPointerId(0); mVelocityTracker = VelocityTracker.obtain(); mVelocityTracker.addMovement(event); - mStartTime = time; mStartX = event.getX(); mStartY = event.getY(); mLastX = mStartX; @@ -5224,7 +5209,7 @@ public final class ViewRootImpl implements ViewParent, DisplayList displayList = view.mDisplayList; info[0]++; if (displayList != null) { - info[1] += displayList.getSize(); + info[1] += 0; /* TODO: Memory used by display lists */ } if (view instanceof ViewGroup) { @@ -5272,7 +5257,6 @@ public final class ViewRootImpl implements ViewParent, } if (mAdded && !mFirst) { - invalidateDisplayLists(); destroyHardwareRenderer(); if (mView != null) { @@ -5318,7 +5302,7 @@ public final class ViewRootImpl implements ViewParent, // Hardware rendering if (mAttachInfo.mHardwareRenderer != null) { - if (mAttachInfo.mHardwareRenderer.loadSystemProperties(mHolder.getSurface())) { + if (mAttachInfo.mHardwareRenderer.loadSystemProperties()) { invalidate(); } } @@ -5521,24 +5505,23 @@ public final class ViewRootImpl implements ViewParent, } private void deliverInputEvent(QueuedInputEvent q) { - Trace.traceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent"); - try { - if (mInputEventConsistencyVerifier != null) { - mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0); - } + Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent", + q.mEvent.getSequenceNumber()); + if (mInputEventConsistencyVerifier != null) { + mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0); + } - InputStage stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage; - if (stage != null) { - stage.deliver(q); - } else { - finishInputEvent(q); - } - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIEW); + InputStage stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage; + if (stage != null) { + stage.deliver(q); + } else { + finishInputEvent(q); } } private void finishInputEvent(QueuedInputEvent q) { + Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, "deliverInputEvent", + q.mEvent.getSequenceNumber()); if (q.mReceiver != null) { boolean handled = (q.mFlags & QueuedInputEvent.FLAG_FINISHED_HANDLED) != 0; q.mReceiver.finishInputEvent(q.mEvent, handled); @@ -5739,10 +5722,6 @@ public final class ViewRootImpl implements ViewParent, mInvalidateOnAnimationRunnable.addViewRect(info); } - public void enqueueDisplayList(DisplayList displayList) { - mDisplayLists.add(displayList); - } - public void cancelInvalidate(View view) { mHandler.removeMessages(MSG_INVALIDATE, view); // fixme: might leak the AttachInfo.InvalidateInfo objects instead of returning @@ -5765,6 +5744,9 @@ public final class ViewRootImpl implements ViewParent, public void dispatchUnhandledKey(KeyEvent event) { if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) { + // Some fallback keys are decided by the ViewRoot as they might have special + // properties (e.g. are locale aware). These take precedence over fallbacks defined by + // the kcm. final KeyCharacterMap kcm = event.getKeyCharacterMap(); final int keyCode = event.getKeyCode(); final int metaState = event.getMetaState(); @@ -5781,7 +5763,6 @@ public final class ViewRootImpl implements ViewParent, event.getDeviceId(), event.getScanCode(), flags, event.getSource(), null); fallbackAction.recycle(); - dispatchInputEvent(fallbackEvent); } } @@ -6265,68 +6246,6 @@ public final class ViewRootImpl implements ViewParent, } } - private final SurfaceHolder mHolder = new SurfaceHolder() { - // we only need a SurfaceHolder for opengl. it would be nice - // to implement everything else though, especially the callback - // support (opengl doesn't make use of it right now, but eventually - // will). - @Override - public Surface getSurface() { - return mSurface; - } - - @Override - public boolean isCreating() { - return false; - } - - @Override - public void addCallback(Callback callback) { - } - - @Override - public void removeCallback(Callback callback) { - } - - @Override - public void setFixedSize(int width, int height) { - } - - @Override - public void setSizeFromLayout() { - } - - @Override - public void setFormat(int format) { - } - - @Override - public void setType(int type) { - } - - @Override - public void setKeepScreenOn(boolean screenOn) { - } - - @Override - public Canvas lockCanvas() { - return null; - } - - @Override - public Canvas lockCanvas(Rect dirty) { - return null; - } - - @Override - public void unlockCanvasAndPost(Canvas canvas) { - } - @Override - public Rect getSurfaceFrame() { - return null; - } - }; - static RunQueue getRunQueue() { RunQueue rq = sRunQueues.get(); if (rq != null) { diff --git a/core/java/android/view/ViewStub.java b/core/java/android/view/ViewStub.java index a5dc3ae..d68a860 100644 --- a/core/java/android/view/ViewStub.java +++ b/core/java/android/view/ViewStub.java @@ -97,16 +97,21 @@ public final class ViewStub extends View { } @SuppressWarnings({"UnusedDeclaration"}) - public ViewStub(Context context, AttributeSet attrs, int defStyle) { - TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ViewStub, - defStyle, 0); + public ViewStub(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + TypedArray a = context.obtainStyledAttributes( + attrs, com.android.internal.R.styleable.ViewStub, defStyleAttr, defStyleRes); mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID); mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0); a.recycle(); - a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View, defStyle, 0); + a = context.obtainStyledAttributes( + attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes); mID = a.getResourceId(R.styleable.View_id, NO_ID); a.recycle(); diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index c450f3c..0cd6325 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -16,6 +16,9 @@ package android.view; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityOptions; import android.content.Context; import android.content.res.Configuration; import android.content.res.TypedArray; @@ -25,8 +28,13 @@ import android.net.Uri; import android.os.Bundle; import android.os.IBinder; import android.os.SystemProperties; +import android.transition.Scene; +import android.transition.Transition; +import android.transition.TransitionManager; import android.view.accessibility.AccessibilityEvent; +import java.util.Map; + /** * Abstract base class for a top-level window look and behavior policy. An * instance of this class should be used as the top-level view added to the @@ -89,17 +97,25 @@ public abstract class Window { * If overlay is enabled, the action mode UI will be allowed to cover existing window content. */ public static final int FEATURE_ACTION_MODE_OVERLAY = 10; - /** * Flag for requesting a decoration-free window that is dismissed by swiping from the left. */ public static final int FEATURE_SWIPE_TO_DISMISS = 11; + /** + * Flag for requesting that window content changes should be represented + * with scenes and transitions. + * + * TODO Add docs + * + * @see #setContentView + */ + public static final int FEATURE_CONTENT_TRANSITIONS = 12; /** * Max value used as a feature ID * @hide */ - public static final int FEATURE_MAX = FEATURE_SWIPE_TO_DISMISS; + public static final int FEATURE_MAX = FEATURE_CONTENT_TRANSITIONS; /** Flag for setting the progress bar's visibility to VISIBLE */ public static final int PROGRESS_VISIBILITY_ON = -1; @@ -245,6 +261,7 @@ public abstract class Window { * * @see #onPreparePanel */ + @Nullable public View onCreatePanelView(int featureId); /** @@ -373,6 +390,7 @@ public abstract class Window { * @param callback Callback to control the lifecycle of this action mode * @return The ActionMode that was started, or null if the system should present it */ + @Nullable public ActionMode onWindowStartingActionMode(ActionMode.Callback callback); /** @@ -980,6 +998,7 @@ public abstract class Window { * * @return View The current View with focus or null. */ + @Nullable public abstract View getCurrentFocus(); /** @@ -988,10 +1007,12 @@ public abstract class Window { * * @return LayoutInflater The shared LayoutInflater. */ + @NonNull public abstract LayoutInflater getLayoutInflater(); public abstract void setTitle(CharSequence title); + @Deprecated public abstract void setTitleColor(int textColor); public abstract void openPanel(int featureId, KeyEvent event); @@ -1032,7 +1053,7 @@ public abstract class Window { */ public void setBackgroundDrawableResource(int resid) { - setBackgroundDrawable(mContext.getResources().getDrawable(resid)); + setBackgroundDrawable(mContext.getDrawable(resid)); } /** @@ -1328,4 +1349,93 @@ public abstract class Window { * @param event A key or touch event to inject to this window. */ public void injectInputEvent(InputEvent event) { } + + /** + * Retrieve the {@link TransitionManager} responsible for for default transitions + * in this window. Requires {@link #FEATURE_CONTENT_TRANSITIONS}. + * + * <p>This method will return non-null after content has been initialized (e.g. by using + * {@link #setContentView}) if {@link #FEATURE_CONTENT_TRANSITIONS} has been granted.</p> + * + * @return This window's content TransitionManager or null if none is set. + */ + public TransitionManager getTransitionManager() { + return null; + } + + /** + * Set the {@link TransitionManager} to use for default transitions in this window. + * Requires {@link #FEATURE_CONTENT_TRANSITIONS}. + * + * @param tm The TransitionManager to use for scene changes. + */ + public void setTransitionManager(TransitionManager tm) { + throw new UnsupportedOperationException(); + } + + /** + * Retrieve the {@link Scene} representing this window's current content. + * Requires {@link #FEATURE_CONTENT_TRANSITIONS}. + * + * <p>This method will return null if the current content is not represented by a Scene.</p> + * + * @return Current Scene being shown or null + */ + public Scene getContentScene() { + return null; + } + + /** + * Set options that can affect the transition behavior within this window. + * @param options Options to set or null for none + * @hide + */ + public void setTransitionOptions(ActivityOptions options, SceneTransitionListener listener) { + } + + /** + * A callback for Activity transitions to be told when the shared element is ready to be shown + * and start the transition to its target location. + * @hide + */ + public interface SceneTransitionListener { + void nullPendingTransition(); + void convertFromTranslucent(); + void convertToTranslucent(); + void sharedElementStart(Transition transition); + void sharedElementEnd(); + } + + /** + * Controls when the Activity enter scene is triggered and the background is faded in. If + * triggerEarly is true, the enter scene will begin as soon as possible and the background + * will fade in when all shared elements are ready to begin transitioning. If triggerEarly is + * false, the Activity enter scene and background fade will be triggered when the calling + * Activity's exit transition completes. + * + * @param triggerEarly Set to true to have the Activity enter scene transition in as early as + * possible or set to false to wait for the calling Activity to exit first. + */ + public void setTriggerEarlyEnterTransition(boolean triggerEarly) { + } + + /** + * Start the exit transition. + * @hide + */ + public Bundle startExitTransition(ActivityOptions options) { + return null; + } + + /** + * On entering Activity Scene transitions, shared element names may be mapped from a + * source Activity's specified name to a unique shared element name in the View hierarchy. + * Under most circumstances, mapping is not necessary - a single View will have the + * shared element name given by the calling Activity. However, if there are several similar + * Views (e.g. in a ListView), the correct shared element must be mapped. + * @param sharedElementNames A mapping from the calling Activity's assigned shared element + * name to a unique shared element name in the View hierarchy. + */ + public void mapTransitionTargets(Map<String, String> sharedElementNames) { + } } diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java new file mode 100644 index 0000000..cdfcb43 --- /dev/null +++ b/core/java/android/view/WindowInsets.java @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.view; + +import android.graphics.Rect; + +/** + * Describes a set of insets for window content. + * + * <p>WindowInsets are immutable and may be expanded to include more inset types in the future. + * To adjust insets, use one of the supplied clone methods to obtain a new WindowInsets instance + * with the adjusted properties.</p> + * + * @see View.OnApplyWindowInsetsListener + * @see View#onApplyWindowInsets(WindowInsets) + */ +public class WindowInsets { + private Rect mSystemWindowInsets; + private Rect mWindowDecorInsets; + private Rect mTempRect; + + private static final Rect EMPTY_RECT = new Rect(0, 0, 0, 0); + + /** + * Since new insets may be added in the future that existing apps couldn't + * know about, this fully empty constant shouldn't be made available to apps + * since it would allow them to inadvertently consume unknown insets by returning it. + * @hide + */ + public static final WindowInsets EMPTY = new WindowInsets(EMPTY_RECT, EMPTY_RECT); + + /** @hide */ + public WindowInsets(Rect systemWindowInsets, Rect windowDecorInsets) { + mSystemWindowInsets = systemWindowInsets; + mWindowDecorInsets = windowDecorInsets; + } + + /** + * Construct a new WindowInsets, copying all values from a source WindowInsets. + * + * @param src Source to copy insets from + */ + public WindowInsets(WindowInsets src) { + mSystemWindowInsets = src.mSystemWindowInsets; + mWindowDecorInsets = src.mWindowDecorInsets; + } + + /** @hide */ + public WindowInsets(Rect systemWindowInsets) { + mSystemWindowInsets = systemWindowInsets; + mWindowDecorInsets = EMPTY_RECT; + } + + /** + * Used to provide a safe copy of the system window insets to pass through + * to the existing fitSystemWindows method and other similar internals. + * @hide + */ + public Rect getSystemWindowInsets() { + if (mTempRect == null) { + mTempRect = new Rect(); + } + mTempRect.set(mSystemWindowInsets); + return mTempRect; + } + + /** + * Returns the left system window inset in pixels. + * + * <p>The system window inset represents the area of a full-screen window that is + * partially or fully obscured by the status bar, navigation bar, IME or other system windows. + * </p> + * + * @return The left system window inset + */ + public int getSystemWindowInsetLeft() { + return mSystemWindowInsets.left; + } + + /** + * Returns the top system window inset in pixels. + * + * <p>The system window inset represents the area of a full-screen window that is + * partially or fully obscured by the status bar, navigation bar, IME or other system windows. + * </p> + * + * @return The top system window inset + */ + public int getSystemWindowInsetTop() { + return mSystemWindowInsets.top; + } + + /** + * Returns the right system window inset in pixels. + * + * <p>The system window inset represents the area of a full-screen window that is + * partially or fully obscured by the status bar, navigation bar, IME or other system windows. + * </p> + * + * @return The right system window inset + */ + public int getSystemWindowInsetRight() { + return mSystemWindowInsets.right; + } + + /** + * Returns the bottom system window inset in pixels. + * + * <p>The system window inset represents the area of a full-screen window that is + * partially or fully obscured by the status bar, navigation bar, IME or other system windows. + * </p> + * + * @return The bottom system window inset + */ + public int getSystemWindowInsetBottom() { + return mSystemWindowInsets.bottom; + } + + /** + * Returns the left window decor inset in pixels. + * + * <p>The window decor inset represents the area of the window content area that is + * partially or fully obscured by decorations within the window provided by the framework. + * This can include action bars, title bars, toolbars, etc.</p> + * + * @return The left window decor inset + */ + public int getWindowDecorInsetLeft() { + return mWindowDecorInsets.left; + } + + /** + * Returns the top window decor inset in pixels. + * + * <p>The window decor inset represents the area of the window content area that is + * partially or fully obscured by decorations within the window provided by the framework. + * This can include action bars, title bars, toolbars, etc.</p> + * + * @return The top window decor inset + */ + public int getWindowDecorInsetTop() { + return mWindowDecorInsets.top; + } + + /** + * Returns the right window decor inset in pixels. + * + * <p>The window decor inset represents the area of the window content area that is + * partially or fully obscured by decorations within the window provided by the framework. + * This can include action bars, title bars, toolbars, etc.</p> + * + * @return The right window decor inset + */ + public int getWindowDecorInsetRight() { + return mWindowDecorInsets.right; + } + + /** + * Returns the bottom window decor inset in pixels. + * + * <p>The window decor inset represents the area of the window content area that is + * partially or fully obscured by decorations within the window provided by the framework. + * This can include action bars, title bars, toolbars, etc.</p> + * + * @return The bottom window decor inset + */ + public int getWindowDecorInsetBottom() { + return mWindowDecorInsets.bottom; + } + + /** + * Returns true if this WindowInsets has nonzero system window insets. + * + * <p>The system window inset represents the area of a full-screen window that is + * partially or fully obscured by the status bar, navigation bar, IME or other system windows. + * </p> + * + * @return true if any of the system window inset values are nonzero + */ + public boolean hasSystemWindowInsets() { + return mSystemWindowInsets.left != 0 || mSystemWindowInsets.top != 0 || + mSystemWindowInsets.right != 0 || mSystemWindowInsets.bottom != 0; + } + + /** + * Returns true if this WindowInsets has nonzero window decor insets. + * + * <p>The window decor inset represents the area of the window content area that is + * partially or fully obscured by decorations within the window provided by the framework. + * This can include action bars, title bars, toolbars, etc.</p> + * + * @return true if any of the window decor inset values are nonzero + */ + public boolean hasWindowDecorInsets() { + return mWindowDecorInsets.left != 0 || mWindowDecorInsets.top != 0 || + mWindowDecorInsets.right != 0 || mWindowDecorInsets.bottom != 0; + } + + /** + * Returns true if this WindowInsets has any nonzero insets. + * + * @return true if any inset values are nonzero + */ + public boolean hasInsets() { + return hasSystemWindowInsets() || hasWindowDecorInsets(); + } + + public WindowInsets cloneWithSystemWindowInsetsConsumed() { + final WindowInsets result = new WindowInsets(this); + result.mSystemWindowInsets = new Rect(0, 0, 0, 0); + return result; + } + + public WindowInsets cloneWithSystemWindowInsetsConsumed(boolean left, boolean top, + boolean right, boolean bottom) { + if (left || top || right || bottom) { + final WindowInsets result = new WindowInsets(this); + result.mSystemWindowInsets = new Rect(left ? 0 : mSystemWindowInsets.left, + top ? 0 : mSystemWindowInsets.top, + right ? 0 : mSystemWindowInsets.right, + bottom ? 0 : mSystemWindowInsets.bottom); + return result; + } + return this; + } + + public WindowInsets cloneWithSystemWindowInsets(int left, int top, int right, int bottom) { + final WindowInsets result = new WindowInsets(this); + result.mSystemWindowInsets = new Rect(left, top, right, bottom); + return result; + } + + public WindowInsets cloneWithWindowDecorInsetsConsumed() { + final WindowInsets result = new WindowInsets(this); + result.mWindowDecorInsets.set(0, 0, 0, 0); + return result; + } + + public WindowInsets cloneWithWindowDecorInsetsConsumed(boolean left, boolean top, + boolean right, boolean bottom) { + if (left || top || right || bottom) { + final WindowInsets result = new WindowInsets(this); + result.mWindowDecorInsets = new Rect(left ? 0 : mWindowDecorInsets.left, + top ? 0 : mWindowDecorInsets.top, + right ? 0 : mWindowDecorInsets.right, + bottom ? 0 : mWindowDecorInsets.bottom); + return result; + } + return this; + } + + public WindowInsets cloneWithWindowDecorInsets(int left, int top, int right, int bottom) { + final WindowInsets result = new WindowInsets(this); + result.mWindowDecorInsets = new Rect(left, top, right, bottom); + return result; + } + + @Override + public String toString() { + return "WindowInsets{systemWindowInsets=" + mSystemWindowInsets + " windowDecorInsets=" + + mWindowDecorInsets + "}"; + } +} diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 53a4c0d0..55956bf 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -98,7 +98,7 @@ public interface WindowManager extends ViewManager { * the given view hierarchy's {@link View#onDetachedFromWindow() * View.onDetachedFromWindow()} methods before returning. This is not * for normal applications; using it correctly requires great care. - * + * * @param view The view to be removed. */ public void removeViewImmediate(View view); @@ -112,7 +112,7 @@ public interface WindowManager extends ViewManager { */ @ViewDebug.ExportedProperty public int x; - + /** * Y position for this window. With the default gravity it is ignored. * When using {@link Gravity#TOP} or {@link Gravity#BOTTOM} it provides @@ -161,7 +161,7 @@ public interface WindowManager extends ViewManager { * be used by applications, and a special permission is required * to use them. * </ul> - * + * * @see #TYPE_BASE_APPLICATION * @see #TYPE_APPLICATION * @see #TYPE_APPLICATION_STARTING @@ -223,12 +223,12 @@ public interface WindowManager extends ViewManager { @ViewDebug.IntToString(from = TYPE_PRIVATE_PRESENTATION, to = "TYPE_PRIVATE_PRESENTATION") }) public int type; - + /** * Start of window types that represent normal application windows. */ public static final int FIRST_APPLICATION_WINDOW = 1; - + /** * Window type: an application window that serves as the "base" window * of the overall application; all other application windows will @@ -236,14 +236,14 @@ public interface WindowManager extends ViewManager { * In multiuser systems shows only on the owning user's window. */ public static final int TYPE_BASE_APPLICATION = 1; - + /** * Window type: a normal application window. The {@link #token} must be * an Activity token identifying who the window belongs to. * In multiuser systems shows only on the owning user's window. */ public static final int TYPE_APPLICATION = 2; - + /** * Window type: special application window that is displayed while the * application is starting. Not for use by applications themselves; @@ -252,12 +252,12 @@ public interface WindowManager extends ViewManager { * In multiuser systems shows on all users' windows. */ public static final int TYPE_APPLICATION_STARTING = 3; - + /** * End of types of application windows. */ public static final int LAST_APPLICATION_WINDOW = 99; - + /** * Start of types of sub-windows. The {@link #token} of these windows * must be set to the window they are attached to. These types of @@ -265,19 +265,19 @@ public interface WindowManager extends ViewManager { * coordinate space is relative to their attached window. */ public static final int FIRST_SUB_WINDOW = 1000; - + /** * Window type: a panel on top of an application window. These windows * appear on top of their attached window. */ public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW; - + /** * Window type: window for showing media (such as video). These windows * are displayed behind their attached window. */ public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW+1; - + /** * Window type: a sub-panel on top of an application window. These * windows are displayed on top their attached window and any @@ -290,7 +290,7 @@ public interface WindowManager extends ViewManager { * as a child of its container. */ public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW+3; - + /** * Window type: window for showing overlays on top of media windows. * These windows are displayed between TYPE_APPLICATION_MEDIA and the @@ -299,18 +299,18 @@ public interface WindowManager extends ViewManager { * @hide */ public static final int TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW+4; - + /** * End of types of sub-windows. */ public static final int LAST_SUB_WINDOW = 1999; - + /** * Start of system-specific window types. These are not normally * created by applications. */ public static final int FIRST_SYSTEM_WINDOW = 2000; - + /** * Window type: the status bar. There can be only one status bar * window; it is placed at the top of the screen, and all other @@ -318,14 +318,14 @@ public interface WindowManager extends ViewManager { * In multiuser systems shows on all users' windows. */ public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW; - + /** * Window type: the search bar. There can be only one search bar * window; it is placed at the top of the screen. * In multiuser systems shows on all users' windows. */ public static final int TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1; - + /** * Window type: phone. These are non-application windows providing * user interaction with the phone (in particular incoming calls). @@ -334,26 +334,26 @@ public interface WindowManager extends ViewManager { * In multiuser systems shows on all users' windows. */ public static final int TYPE_PHONE = FIRST_SYSTEM_WINDOW+2; - + /** * Window type: system window, such as low power alert. These windows * are always on top of application windows. * In multiuser systems shows only on the owning user's window. */ public static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3; - + /** * Window type: keyguard window. * In multiuser systems shows on all users' windows. */ public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4; - + /** * Window type: transient notifications. * In multiuser systems shows only on the owning user's window. */ public static final int TYPE_TOAST = FIRST_SYSTEM_WINDOW+5; - + /** * Window type: system overlay windows, which need to be displayed * on top of everything else. These windows must not take input @@ -361,7 +361,7 @@ public interface WindowManager extends ViewManager { * In multiuser systems shows only on the owning user's window. */ public static final int TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+6; - + /** * Window type: priority phone UI, which needs to be displayed even if * the keyguard is active. These windows must not take input @@ -369,26 +369,26 @@ public interface WindowManager extends ViewManager { * In multiuser systems shows on all users' windows. */ public static final int TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW+7; - + /** * Window type: panel that slides out from the status bar * In multiuser systems shows on all users' windows. */ public static final int TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW+8; - + /** * Window type: dialogs that the keyguard shows * In multiuser systems shows on all users' windows. */ public static final int TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW+9; - + /** * Window type: internal system error windows, appear on top of * everything they can. * In multiuser systems shows only on the owning user's window. */ public static final int TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW+10; - + /** * Window type: internal input methods windows, which appear above * the normal UI. Application windows may be resized or panned to keep @@ -559,16 +559,16 @@ public interface WindowManager extends ViewManager { /** @deprecated this is ignored, this value is set automatically when needed. */ @Deprecated public static final int MEMORY_TYPE_PUSH_BUFFERS = 3; - + /** * @deprecated this is ignored */ @Deprecated public int memoryType; - + /** Window flag: as long as this window is visible to the user, allow - * the lock screen to activate while the screen is on. - * This can be used independently, or in combination with + * the lock screen to activate while the screen is on. + * This can be used independently, or in combination with * {@link #FLAG_KEEP_SCREEN_ON} and/or {@link #FLAG_SHOW_WHEN_LOCKED} */ public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001; @@ -586,47 +586,47 @@ public interface WindowManager extends ViewManager { * instead go to whatever focusable window is behind it. This flag * will also enable {@link #FLAG_NOT_TOUCH_MODAL} whether or not that * is explicitly set. - * + * * <p>Setting this flag also implies that the window will not need to * interact with - * a soft input method, so it will be Z-ordered and positioned + * a soft input method, so it will be Z-ordered and positioned * independently of any active input method (typically this means it * gets Z-ordered on top of the input method, so it can use the full * screen for its content and cover the input method if needed. You * can use {@link #FLAG_ALT_FOCUSABLE_IM} to modify this behavior. */ public static final int FLAG_NOT_FOCUSABLE = 0x00000008; - + /** Window flag: this window can never receive touch events. */ public static final int FLAG_NOT_TOUCHABLE = 0x00000010; - + /** Window flag: even when this window is focusable (its * {@link #FLAG_NOT_FOCUSABLE} is not set), allow any pointer events * outside of the window to be sent to the windows behind it. Otherwise * it will consume all pointer events itself, regardless of whether they * are inside of the window. */ public static final int FLAG_NOT_TOUCH_MODAL = 0x00000020; - + /** Window flag: when set, if the device is asleep when the touch * screen is pressed, you will receive this first touch event. Usually * the first touch event is consumed by the system since the user can * not see what they are pressing on. */ public static final int FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040; - + /** Window flag: as long as this window is visible to the user, keep * the device's screen turned on and bright. */ public static final int FLAG_KEEP_SCREEN_ON = 0x00000080; - + /** Window flag: place the window within the entire screen, ignoring * decorations around the border (such as the status bar). The * window must correctly position its contents to take the screen * decoration into account. This flag is normally set for you * by Window as described in {@link Window#setFlags}. */ public static final int FLAG_LAYOUT_IN_SCREEN = 0x00000100; - + /** Window flag: allow window to extend outside of the screen. */ public static final int FLAG_LAYOUT_NO_LIMITS = 0x00000200; - + /** * Window flag: hide all screen decorations (such as the status bar) while * this window is displayed. This allows the window to use the entire @@ -648,17 +648,17 @@ public interface WindowManager extends ViewManager { * {@link android.R.style#Theme_DeviceDefault_Light_NoActionBar_Fullscreen}.</p> */ public static final int FLAG_FULLSCREEN = 0x00000400; - + /** Window flag: override {@link #FLAG_FULLSCREEN} and force the * screen decorations (such as the status bar) to be shown. */ public static final int FLAG_FORCE_NOT_FULLSCREEN = 0x00000800; - + /** Window flag: turn on dithering when compositing this window to * the screen. * @deprecated This flag is no longer used. */ @Deprecated public static final int FLAG_DITHER = 0x00001000; - + /** Window flag: treat the content of the window as secure, preventing * it from appearing in screenshots or from being viewed on non-secure * displays. @@ -667,21 +667,21 @@ public interface WindowManager extends ViewManager { * secure surfaces and secure displays. */ public static final int FLAG_SECURE = 0x00002000; - + /** Window flag: a special mode where the layout parameters are used * to perform scaling of the surface when it is composited to the * screen. */ public static final int FLAG_SCALED = 0x00004000; - + /** Window flag: intended for windows that will often be used when the user is * holding the screen against their face, it will aggressively filter the event * stream to prevent unintended presses in this situation that may not be - * desired for a particular window, when such an event stream is detected, the + * desired for a particular window, when such an event stream is detected, the * application will receive a CANCEL motion event to indicate this so applications - * can handle this accordingly by taking no action on the event + * can handle this accordingly by taking no action on the event * until the finger is released. */ public static final int FLAG_IGNORE_CHEEK_PRESSES = 0x00008000; - + /** Window flag: a special option only for use in combination with * {@link #FLAG_LAYOUT_IN_SCREEN}. When requesting layout in the * screen your window may appear on top of or behind screen decorations @@ -690,7 +690,7 @@ public interface WindowManager extends ViewManager { * content is not covered by screen decorations. This flag is normally * set for you by Window as described in {@link Window#setFlags}.*/ public static final int FLAG_LAYOUT_INSET_DECOR = 0x00010000; - + /** Window flag: invert the state of {@link #FLAG_NOT_FOCUSABLE} with * respect to how this window interacts with the current method. That * is, if FLAG_NOT_FOCUSABLE is set and this flag is set, then the @@ -701,7 +701,7 @@ public interface WindowManager extends ViewManager { * to use more space and cover the input method. */ public static final int FLAG_ALT_FOCUSABLE_IM = 0x00020000; - + /** Window flag: if you have set {@link #FLAG_NOT_TOUCH_MODAL}, you * can set this flag to receive a single special MotionEvent with * the action @@ -711,7 +711,7 @@ public interface WindowManager extends ViewManager { * first down as an ACTION_OUTSIDE. */ public static final int FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000; - + /** Window flag: special flag to let windows be shown when the screen * is locked. This will let application windows take precedence over * key guard or any other lock screens. Can be used with @@ -741,13 +741,13 @@ public interface WindowManager extends ViewManager { * {@link android.R.style#Theme_DeviceDefault_Wallpaper_NoTitleBar}.</p> */ public static final int FLAG_SHOW_WALLPAPER = 0x00100000; - + /** Window flag: when set as a window is being added or made * visible, once the window has been shown then the system will * poke the power manager's user activity (as if the user had woken * up the device) to turn the screen on. */ public static final int FLAG_TURN_SCREEN_ON = 0x00200000; - + /** Window flag: when set the window will cause the keyguard to * be dismissed, only if it is not a secure lock keyguard. Because such * a keyguard is not needed for security, it will never re-appear if @@ -761,7 +761,7 @@ public interface WindowManager extends ViewManager { * also been set. */ public static final int FLAG_DISMISS_KEYGUARD = 0x00400000; - + /** Window flag: when set the window will accept for touch events * outside of its bounds to be sent to other windows that also * support split touch. When this flag is not set, the first pointer @@ -773,7 +773,7 @@ public interface WindowManager extends ViewManager { * to be split across multiple windows. */ public static final int FLAG_SPLIT_TOUCH = 0x00800000; - + /** * <p>Indicates whether this window should be hardware accelerated. * Requesting hardware acceleration does not guarantee it will happen.</p> @@ -916,7 +916,7 @@ public interface WindowManager extends ViewManager { /** * Various behavioral options/flags. Default is none. - * + * * @see #FLAG_ALLOW_LOCK_WHILE_SCREEN_ON * @see #FLAG_DIM_BEHIND * @see #FLAG_NOT_FOCUSABLE @@ -1014,10 +1014,10 @@ public interface WindowManager extends ViewManager { * as if it was. * Like {@link #FLAG_HARDWARE_ACCELERATED} except for trusted system windows * that need hardware acceleration (e.g. LockScreen), where hardware acceleration - * is generally disabled. This flag must be specified in addition to + * is generally disabled. This flag must be specified in addition to * {@link #FLAG_HARDWARE_ACCELERATED} to enable hardware acceleration for system * windows. - * + * * @hide */ public static final int PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED = 0x00000001; @@ -1028,7 +1028,7 @@ public interface WindowManager extends ViewManager { * If certain parts of the UI that really do want to use hardware * acceleration, this flag can be set to force it. This is basically * for the lock screen. Anyone else using it, you are probably wrong. - * + * * @hide */ public static final int PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED = 0x00000002; @@ -1086,6 +1086,11 @@ public interface WindowManager extends ViewManager { * {@hide} */ public static final int PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR = 0x00000200; + /** Window flag: the window is backed by a video plane, instead of a + * regular surface. + * {@hide} */ + public static final int PRIVATE_FLAG_VIDEO_PLANE = 0x00000400; + /** * Control flags that are private to the platform. * @hide @@ -1100,9 +1105,9 @@ public interface WindowManager extends ViewManager { * flags and returns true if the combination of the two corresponds * to a window that needs to be behind the input method so that the * user can type into it. - * + * * @param flags The current window manager flags. - * + * * @return Returns true if such a window should be behind/interact * with an input method, false if not. */ @@ -1114,63 +1119,63 @@ public interface WindowManager extends ViewManager { } return false; } - + /** * Mask for {@link #softInputMode} of the bits that determine the * desired visibility state of the soft input area for this window. */ public static final int SOFT_INPUT_MASK_STATE = 0x0f; - + /** * Visibility state for {@link #softInputMode}: no state has been specified. */ public static final int SOFT_INPUT_STATE_UNSPECIFIED = 0; - + /** * Visibility state for {@link #softInputMode}: please don't change the state of * the soft input area. */ public static final int SOFT_INPUT_STATE_UNCHANGED = 1; - + /** * Visibility state for {@link #softInputMode}: please hide any soft input * area when normally appropriate (when the user is navigating * forward to your window). */ public static final int SOFT_INPUT_STATE_HIDDEN = 2; - + /** * Visibility state for {@link #softInputMode}: please always hide any * soft input area when this window receives focus. */ public static final int SOFT_INPUT_STATE_ALWAYS_HIDDEN = 3; - + /** * Visibility state for {@link #softInputMode}: please show the soft * input area when normally appropriate (when the user is navigating * forward to your window). */ public static final int SOFT_INPUT_STATE_VISIBLE = 4; - + /** * Visibility state for {@link #softInputMode}: please always make the * soft input area visible when this window receives input focus. */ public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5; - + /** * Mask for {@link #softInputMode} of the bits that determine the * way that the window should be adjusted to accommodate the soft * input window. */ public static final int SOFT_INPUT_MASK_ADJUST = 0xf0; - + /** Adjustment option for {@link #softInputMode}: nothing specified. * The system will try to pick one or * the other depending on the contents of the window. */ public static final int SOFT_INPUT_ADJUST_UNSPECIFIED = 0x00; - + /** Adjustment option for {@link #softInputMode}: set to allow the * window to be resized when an input * method is shown, so that its contents are not covered by the input @@ -1183,7 +1188,7 @@ public interface WindowManager extends ViewManager { * not resize, but will stay fullscreen. */ public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10; - + /** Adjustment option for {@link #softInputMode}: set to have a window * pan when an input method is * shown, so it doesn't need to deal with resizing but just panned @@ -1193,7 +1198,7 @@ public interface WindowManager extends ViewManager { * the other depending on the contents of the window. */ public static final int SOFT_INPUT_ADJUST_PAN = 0x20; - + /** Adjustment option for {@link #softInputMode}: set to have a window * not adjust for a shown input method. The window will not be resized, * and it will not be panned to make its focus visible. @@ -1212,7 +1217,7 @@ public interface WindowManager extends ViewManager { /** * Desired operating mode for any soft input area. May be any combination * of: - * + * * <ul> * <li> One of the visibility states * {@link #SOFT_INPUT_STATE_UNSPECIFIED}, {@link #SOFT_INPUT_STATE_UNCHANGED}, @@ -1229,7 +1234,7 @@ public interface WindowManager extends ViewManager { * {@link android.R.attr#windowSoftInputMode} attribute.</p> */ public int softInputMode; - + /** * Placement of window within the screen as per {@link Gravity}. Both * {@link Gravity#apply(int, int, int, android.graphics.Rect, int, int, @@ -1246,7 +1251,7 @@ public interface WindowManager extends ViewManager { * @see Gravity */ public int gravity; - + /** * The horizontal margin, as a percentage of the container's width, * between the container and the widget. See @@ -1255,7 +1260,7 @@ public interface WindowManager extends ViewManager { * field is added with {@link #x} to supply the <var>xAdj</var> parameter. */ public float horizontalMargin; - + /** * The vertical margin, as a percentage of the container's height, * between the container and the widget. See @@ -1264,26 +1269,26 @@ public interface WindowManager extends ViewManager { * field is added with {@link #y} to supply the <var>yAdj</var> parameter. */ public float verticalMargin; - + /** * The desired bitmap format. May be one of the constants in * {@link android.graphics.PixelFormat}. Default is OPAQUE. */ public int format; - + /** * A style resource defining the animations to use for this window. * This must be a system resource; it can not be an application resource * because the window manager does not have access to applications. */ public int windowAnimations; - + /** * An alpha value to apply to this entire window. * An alpha of 1.0 means fully opaque and 0.0 means fully transparent */ public float alpha = 1.0f; - + /** * When {@link #FLAG_DIM_BEHIND} is set, this is the amount of dimming * to apply. Range is from 1.0 for completely opaque to 0.0 for no @@ -1311,7 +1316,7 @@ public interface WindowManager extends ViewManager { * to the hightest value when this window is in front. */ public static final float BRIGHTNESS_OVERRIDE_FULL = 1.0f; - + /** * This can be used to override the user's preferred brightness of * the screen. A value of less than 0, the default, means to use the @@ -1319,7 +1324,7 @@ public interface WindowManager extends ViewManager { * dark to full bright. */ public float screenBrightness = BRIGHTNESS_OVERRIDE_NONE; - + /** * This can be used to override the standard behavior of the button and * keyboard backlights. A value of less than 0, the default, means to @@ -1353,7 +1358,7 @@ public interface WindowManager extends ViewManager { * opaque windows have the #FLAG_FULLSCREEN bit set and are not covered * by other windows. All other situations default to the * {@link #ROTATION_ANIMATION_ROTATE} behavior. - * + * * @see #ROTATION_ANIMATION_ROTATE * @see #ROTATION_ANIMATION_CROSSFADE * @see #ROTATION_ANIMATION_JUMPCUT @@ -1365,18 +1370,18 @@ public interface WindowManager extends ViewManager { * you. */ public IBinder token = null; - + /** * Name of the package owning this window. */ public String packageName = null; - + /** * Specific orientation value for a window. * May be any of the same values allowed - * for {@link android.content.pm.ActivityInfo#screenOrientation}. - * If not set, a default value of - * {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED} + * for {@link android.content.pm.ActivityInfo#screenOrientation}. + * If not set, a default value of + * {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED} * will be used. */ public int screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; @@ -1398,7 +1403,7 @@ public interface WindowManager extends ViewManager { /** * Get callbacks about the system ui visibility changing. - * + * * TODO: Maybe there should be a bitfield of optional callbacks that we need. * * @hide @@ -1464,34 +1469,34 @@ public interface WindowManager extends ViewManager { type = TYPE_APPLICATION; format = PixelFormat.OPAQUE; } - + public LayoutParams(int _type) { super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); type = _type; format = PixelFormat.OPAQUE; } - + public LayoutParams(int _type, int _flags) { super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); type = _type; flags = _flags; format = PixelFormat.OPAQUE; } - + public LayoutParams(int _type, int _flags, int _format) { super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); type = _type; flags = _flags; format = _format; } - + public LayoutParams(int w, int h, int _type, int _flags, int _format) { super(w, h); type = _type; flags = _flags; format = _format; } - + public LayoutParams(int w, int h, int xpos, int ypos, int _type, int _flags, int _format) { super(w, h); @@ -1501,18 +1506,18 @@ public interface WindowManager extends ViewManager { flags = _flags; format = _format; } - + public final void setTitle(CharSequence title) { if (null == title) title = ""; - + mTitle = TextUtils.stringOrSpannedString(title); } - + public final CharSequence getTitle() { return mTitle; } - + public int describeContents() { return 0; } @@ -1546,19 +1551,19 @@ public interface WindowManager extends ViewManager { out.writeInt(inputFeatures); out.writeLong(userActivityTimeout); } - + public static final Parcelable.Creator<LayoutParams> CREATOR = new Parcelable.Creator<LayoutParams>() { public LayoutParams createFromParcel(Parcel in) { return new LayoutParams(in); } - + public LayoutParams[] newArray(int size) { return new LayoutParams[size]; } }; - - + + public LayoutParams(Parcel in) { width = in.readInt(); height = in.readInt(); @@ -1588,7 +1593,7 @@ public interface WindowManager extends ViewManager { inputFeatures = in.readInt(); userActivityTimeout = in.readLong(); } - + @SuppressWarnings({"PointlessBitwiseExpression"}) public static final int LAYOUT_CHANGED = 1<<0; public static final int TYPE_CHANGED = 1<<1; @@ -1622,10 +1627,10 @@ public interface WindowManager extends ViewManager { // internal buffer to backup/restore parameters under compatibility mode. private int[] mCompatibilityParamsBackup = null; - + public final int copyFrom(LayoutParams o) { int changes = 0; - + if (width != o.width) { width = o.width; changes |= LAYOUT_CHANGED; @@ -1724,7 +1729,7 @@ public interface WindowManager extends ViewManager { rotationAnimation = o.rotationAnimation; changes |= ROTATION_ANIMATION_CHANGED; } - + if (screenOrientation != o.screenOrientation) { screenOrientation = o.screenOrientation; changes |= SCREEN_ORIENTATION_CHANGED; @@ -1754,7 +1759,7 @@ public interface WindowManager extends ViewManager { return changes; } - + @Override public String debug(String output) { output += "Contents of " + this + ":"; @@ -1765,7 +1770,7 @@ public interface WindowManager extends ViewManager { Log.d("Debug", "WindowManager.LayoutParams={title=" + mTitle + "}"); return ""; } - + @Override public String toString() { StringBuilder sb = new StringBuilder(256); diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index 74dda77..75656545 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -16,7 +16,9 @@ package android.view; +import android.annotation.IntDef; import android.content.Context; +import android.content.pm.ActivityInfo; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.graphics.Rect; @@ -27,6 +29,8 @@ import android.os.Looper; import android.view.animation.Animation; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * This interface supplies all UI-specific behavior of the window manager. An @@ -450,6 +454,11 @@ public interface WindowManagerPolicy { /** Screen turned off because of proximity sensor */ public final int OFF_BECAUSE_OF_PROX_SENSOR = 4; + /** @hide */ + @IntDef({USER_ROTATION_FREE, USER_ROTATION_LOCKED}) + @Retention(RetentionPolicy.SOURCE) + public @interface UserRotationMode {} + /** When not otherwise specified by the activity's screenOrientation, rotation should be * determined by the system (that is, using sensors). */ public final int USER_ROTATION_FREE = 0; @@ -1006,7 +1015,8 @@ public interface WindowManagerPolicy { * @param lastRotation The most recently used rotation. * @return The surface rotation to use. */ - public int rotationForOrientationLw(int orientation, int lastRotation); + public int rotationForOrientationLw(@ActivityInfo.ScreenOrientation int orientation, + int lastRotation); /** * Given an orientation constant and a rotation, returns true if the rotation @@ -1021,7 +1031,8 @@ public interface WindowManagerPolicy { * @param rotation The rotation to check. * @return True if the rotation is compatible with the requested orientation. */ - public boolean rotationHasCompatibleMetricsLw(int orientation, int rotation); + public boolean rotationHasCompatibleMetricsLw(@ActivityInfo.ScreenOrientation int orientation, + int rotation); /** * Called by the window manager when the rotation changes. @@ -1070,7 +1081,7 @@ public interface WindowManagerPolicy { */ public void enableScreenAfterBoot(); - public void setCurrentOrientationLw(int newOrientation); + public void setCurrentOrientationLw(@ActivityInfo.ScreenOrientation int newOrientation); /** * Call from application to perform haptic feedback on its window. @@ -1097,6 +1108,7 @@ public interface WindowManagerPolicy { * @see WindowManagerPolicy#USER_ROTATION_LOCKED * @see WindowManagerPolicy#USER_ROTATION_FREE */ + @UserRotationMode public int getUserRotationMode(); /** @@ -1107,12 +1119,12 @@ public interface WindowManagerPolicy { * @param rotation One of {@link Surface#ROTATION_0}, {@link Surface#ROTATION_90}, * {@link Surface#ROTATION_180}, {@link Surface#ROTATION_270}. */ - public void setUserRotationMode(int mode, int rotation); + public void setUserRotationMode(@UserRotationMode int mode, @Surface.Rotation int rotation); /** * Called when a new system UI visibility is being reported, allowing * the policy to adjust what is actually reported. - * @param visibility The raw visiblity reported by the status bar. + * @param visibility The raw visibility reported by the status bar. * @return The new desired visibility. */ public int adjustSystemUiVisibilityLw(int visibility); @@ -1135,6 +1147,11 @@ public interface WindowManagerPolicy { public void setLastInputMethodWindowLw(WindowState ime, WindowState target); /** + * @return The current height of the input method window. + */ + public int getInputMethodWindowVisibleHeightLw(); + + /** * Called when the current user changes. Guaranteed to be called before the broadcast * of the new user id is made to all listeners. * diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java index f635eee..8b91155 100644 --- a/core/java/android/view/accessibility/AccessibilityEvent.java +++ b/core/java/android/view/accessibility/AccessibilityEvent.java @@ -722,7 +722,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par int mAction; int mContentChangeTypes; - private final ArrayList<AccessibilityRecord> mRecords = new ArrayList<AccessibilityRecord>(); + private ArrayList<AccessibilityRecord> mRecords; /* * Hide constructor from clients. @@ -755,11 +755,13 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par @Override public void setSealed(boolean sealed) { super.setSealed(sealed); - List<AccessibilityRecord> records = mRecords; - final int recordCount = records.size(); - for (int i = 0; i < recordCount; i++) { - AccessibilityRecord record = records.get(i); - record.setSealed(sealed); + final List<AccessibilityRecord> records = mRecords; + if (records != null) { + final int recordCount = records.size(); + for (int i = 0; i < recordCount; i++) { + AccessibilityRecord record = records.get(i); + record.setSealed(sealed); + } } } @@ -769,7 +771,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par * @return The number of records. */ public int getRecordCount() { - return mRecords.size(); + return mRecords == null ? 0 : mRecords.size(); } /** @@ -781,6 +783,9 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par */ public void appendRecord(AccessibilityRecord record) { enforceNotSealed(); + if (mRecords == null) { + mRecords = new ArrayList<AccessibilityRecord>(); + } mRecords.add(record); } @@ -791,6 +796,9 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par * @return The record at the specified index. */ public AccessibilityRecord getRecord(int index) { + if (mRecords == null) { + throw new IndexOutOfBoundsException("Invalid index " + index + ", size is 0"); + } return mRecords.get(index); } @@ -964,11 +972,14 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par AccessibilityEvent eventClone = AccessibilityEvent.obtain(); eventClone.init(event); - final int recordCount = event.mRecords.size(); - for (int i = 0; i < recordCount; i++) { - AccessibilityRecord record = event.mRecords.get(i); - AccessibilityRecord recordClone = AccessibilityRecord.obtain(record); - eventClone.mRecords.add(recordClone); + if (event.mRecords != null) { + final int recordCount = event.mRecords.size(); + eventClone.mRecords = new ArrayList<AccessibilityRecord>(recordCount); + for (int i = 0; i < recordCount; i++) { + final AccessibilityRecord record = event.mRecords.get(i); + final AccessibilityRecord recordClone = AccessibilityRecord.obtain(record); + eventClone.mRecords.add(recordClone); + } } return eventClone; @@ -1013,9 +1024,11 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par mContentChangeTypes = 0; mPackageName = null; mEventTime = 0; - while (!mRecords.isEmpty()) { - AccessibilityRecord record = mRecords.remove(0); - record.recycle(); + if (mRecords != null) { + while (!mRecords.isEmpty()) { + AccessibilityRecord record = mRecords.remove(0); + record.recycle(); + } } } @@ -1037,11 +1050,14 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par // Read the records. final int recordCount = parcel.readInt(); - for (int i = 0; i < recordCount; i++) { - AccessibilityRecord record = AccessibilityRecord.obtain(); - readAccessibilityRecordFromParcel(record, parcel); - record.mConnectionId = mConnectionId; - mRecords.add(record); + if (recordCount > 0) { + mRecords = new ArrayList<AccessibilityRecord>(recordCount); + for (int i = 0; i < recordCount; i++) { + AccessibilityRecord record = AccessibilityRecord.obtain(); + readAccessibilityRecordFromParcel(record, parcel); + record.mConnectionId = mConnectionId; + mRecords.add(record); + } } } @@ -1147,8 +1163,8 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par builder.append("; ContentChangeTypes: ").append(mContentChangeTypes); builder.append("; sourceWindowId: ").append(mSourceWindowId); builder.append("; mSourceNodeId: ").append(mSourceNodeId); - for (int i = 0; i < mRecords.size(); i++) { - AccessibilityRecord record = mRecords.get(i); + for (int i = 0; i < getRecordCount(); i++) { + final AccessibilityRecord record = getRecord(i); builder.append(" Record "); builder.append(i); builder.append(":"); diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index 139df3e..5a55e34 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -27,7 +27,6 @@ import android.os.SystemClock; import android.util.Log; import android.util.LongSparseArray; import android.util.SparseArray; -import android.util.SparseLongArray; import java.util.ArrayList; import java.util.Collections; @@ -718,10 +717,9 @@ public final class AccessibilityInteractionClient Log.e(LOG_TAG, "Duplicate node."); return; } - SparseLongArray childIds = current.getChildNodeIds(); - final int childCount = childIds.size(); + final int childCount = current.getChildCount(); for (int i = 0; i < childCount; i++) { - final long childId = childIds.valueAt(i); + final long childId = current.getChildId(i); for (int j = 0; j < infoCount; j++) { AccessibilityNodeInfo child = infos.get(j); if (child.getSourceNodeId() == childId) { diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index 879e58f..a711f48 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -75,6 +75,42 @@ public final class AccessibilityManager { /** @hide */ public static final int STATE_FLAG_TOUCH_EXPLORATION_ENABLED = 0x00000002; + /** @hide */ + public static final int INVERSION_DISABLED = -1; + + /** @hide */ + public static final int INVERSION_STANDARD = 0; + + /** @hide */ + public static final int INVERSION_HUE_ONLY = 1; + + /** @hide */ + public static final int INVERSION_VALUE_ONLY = 2; + + /** @hide */ + public static final int DALTONIZER_DISABLED = -1; + + /** @hide */ + public static final int DALTONIZER_SIMULATE_MONOCHROMACY = 0; + + /** @hide */ + public static final int DALTONIZER_SIMULATE_PROTANOMALY = 1; + + /** @hide */ + public static final int DALTONIZER_SIMULATE_DEUTERANOMALY = 2; + + /** @hide */ + public static final int DALTONIZER_SIMULATE_TRITANOMALY = 3; + + /** @hide */ + public static final int DALTONIZER_CORRECT_PROTANOMALY = 11; + + /** @hide */ + public static final int DALTONIZER_CORRECT_DEUTERANOMALY = 12; + + /** @hide */ + public static final int DALTONIZER_CORRECT_TRITANOMALY = 13; + static final Object sInstanceSync = new Object(); private static AccessibilityManager sInstance; diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 4f53c1e..560d0c9 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -22,8 +22,8 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.text.InputType; +import android.util.LongArray; import android.util.Pools.SynchronizedPool; -import android.util.SparseLongArray; import android.view.View; import java.util.Collections; @@ -282,6 +282,22 @@ public class AccessibilityNodeInfo implements Parcelable { */ public static final int ACTION_DISMISS = 0x00100000; + /** + * Action that sets the text of the node. Performing the action without argument, using <code> + * null</code> or empty {@link CharSequence} will clear the text. This action will also put the + * cursor at the end of text. + * <p> + * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE}<br> + * <strong>Example:</strong> + * <code><pre><p> + * Bundle arguments = new Bundle(); + * arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, + * "android"); + * info.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments); + * </code></pre></p> + */ + public static final int ACTION_SET_TEXT = 0x00200000; + // Action arguments /** @@ -351,6 +367,18 @@ public class AccessibilityNodeInfo implements Parcelable { public static final String ACTION_ARGUMENT_SELECTION_END_INT = "ACTION_ARGUMENT_SELECTION_END_INT"; + /** + * Argument for specifying the text content to set + * <p> + * <strong>Type:</strong> CharSequence<br> + * <strong>Actions:</strong> {@link #ACTION_SET_TEXT} + * </p> + * + * @see #ACTION_SET_TEXT + */ + public static final String ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE = + "ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE"; + // Focus types /** @@ -503,7 +531,7 @@ public class AccessibilityNodeInfo implements Parcelable { private CharSequence mContentDescription; private String mViewIdResourceName; - private final SparseLongArray mChildNodeIds = new SparseLongArray(); + private LongArray mChildNodeIds; private int mActions; private int mMovementGranularities; @@ -666,21 +694,35 @@ public class AccessibilityNodeInfo implements Parcelable { } /** - * @return The ids of the children. + * Returns the array containing the IDs of this node's children. * * @hide */ - public SparseLongArray getChildNodeIds() { + public LongArray getChildNodeIds() { return mChildNodeIds; } /** + * Returns the id of the child at the specified index. + * + * @throws IndexOutOfBoundsException when index < 0 || index >= + * getChildCount() + * @hide + */ + public long getChildId(int index) { + if (mChildNodeIds == null) { + throw new IndexOutOfBoundsException(); + } + return mChildNodeIds.get(index); + } + + /** * Gets the number of children. * * @return The child count. */ public int getChildCount() { - return mChildNodeIds.size(); + return mChildNodeIds == null ? 0 : mChildNodeIds.size(); } /** @@ -699,6 +741,9 @@ public class AccessibilityNodeInfo implements Parcelable { */ public AccessibilityNodeInfo getChild(int index) { enforceSealed(); + if (mChildNodeIds == null) { + return null; + } if (!canPerformRequestOverConnection(mSourceNodeId)) { return null; } @@ -721,7 +766,35 @@ public class AccessibilityNodeInfo implements Parcelable { * @throws IllegalStateException If called from an AccessibilityService. */ public void addChild(View child) { - addChild(child, UNDEFINED); + addChildInternal(child, UNDEFINED, true); + } + + /** + * Unchecked version of {@link #addChild(View)} that does not verify + * uniqueness. For framework use only. + * + * @hide + */ + public void addChildUnchecked(View child) { + addChildInternal(child, UNDEFINED, false); + } + + /** + * Removes a child. If the child was not previously added to the node, + * calling this method has no effect. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param child The child. + * @return true if the child was present + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public boolean removeChild(View child) { + return removeChild(child, UNDEFINED); } /** @@ -739,12 +812,49 @@ public class AccessibilityNodeInfo implements Parcelable { * @param virtualDescendantId The id of the virtual child. */ public void addChild(View root, int virtualDescendantId) { + addChildInternal(root, virtualDescendantId, true); + } + + private void addChildInternal(View root, int virtualDescendantId, boolean checked) { enforceNotSealed(); - final int index = mChildNodeIds.size(); + if (mChildNodeIds == null) { + mChildNodeIds = new LongArray(); + } final int rootAccessibilityViewId = (root != null) ? root.getAccessibilityViewId() : UNDEFINED; final long childNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId); - mChildNodeIds.put(index, childNodeId); + // If we're checking uniqueness and the ID already exists, abort. + if (checked && mChildNodeIds.indexOf(childNodeId) >= 0) { + return; + } + mChildNodeIds.add(childNodeId); + } + + /** + * Removes a virtual child which is a descendant of the given + * <code>root</code>. If the child was not previously added to the node, + * calling this method has no effect. + * + * @param root The root of the virtual subtree. + * @param virtualDescendantId The id of the virtual child. + * @return true if the child was present + * @see #addChild(View, int) + */ + public boolean removeChild(View root, int virtualDescendantId) { + enforceNotSealed(); + final LongArray childIds = mChildNodeIds; + if (childIds == null) { + return false; + } + final int rootAccessibilityViewId = + (root != null) ? root.getAccessibilityViewId() : UNDEFINED; + final long childNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId); + final int index = childIds.indexOf(childNodeId); + if (index < 0) { + return false; + } + childIds.remove(index); + return true; } /** @@ -789,6 +899,24 @@ public class AccessibilityNodeInfo implements Parcelable { } /** + * Removes an action that can be performed on the node. If the action was + * not already added to the node, calling this method has no effect. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param action The action. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void removeAction(int action) { + enforceNotSealed(); + mActions &= ~action; + } + + /** * Sets the movement granularities for traversing the text of this node. * <p> * <strong>Note:</strong> Cannot be called from an @@ -1408,8 +1536,6 @@ public class AccessibilityNodeInfo implements Parcelable { * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. * </p> - * - * @return collectionItem True if the node is an item. */ public void setCollectionItemInfo(CollectionItemInfo collectionItemInfo) { enforceNotSealed(); @@ -1951,6 +2077,7 @@ public class AccessibilityNodeInfo implements Parcelable { /** * {@inheritDoc} */ + @Override public int describeContents() { return 0; } @@ -2114,6 +2241,7 @@ public class AccessibilityNodeInfo implements Parcelable { * is recycled. You must not touch the object after calling this function. * </p> */ + @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeInt(isSealed() ? 1 : 0); parcel.writeLong(mSourceNodeId); @@ -2123,11 +2251,15 @@ public class AccessibilityNodeInfo implements Parcelable { parcel.writeLong(mLabeledById); parcel.writeInt(mConnectionId); - SparseLongArray childIds = mChildNodeIds; - final int childIdsSize = childIds.size(); - parcel.writeInt(childIdsSize); - for (int i = 0; i < childIdsSize; i++) { - parcel.writeLong(childIds.valueAt(i)); + final LongArray childIds = mChildNodeIds; + if (childIds == null) { + parcel.writeInt(0); + } else { + final int childIdsSize = childIds.size(); + parcel.writeInt(childIdsSize); + for (int i = 0; i < childIdsSize; i++) { + parcel.writeLong(childIds.get(i)); + } } parcel.writeInt(mBoundsInParent.top); @@ -2179,6 +2311,7 @@ public class AccessibilityNodeInfo implements Parcelable { parcel.writeInt(mCollectionInfo.getRowCount()); parcel.writeInt(mCollectionInfo.getColumnCount()); parcel.writeInt(mCollectionInfo.isHierarchical() ? 1 : 0); + parcel.writeInt(mCollectionInfo.getSelectionMode()); } else { parcel.writeInt(0); } @@ -2190,6 +2323,7 @@ public class AccessibilityNodeInfo implements Parcelable { parcel.writeInt(mCollectionItemInfo.getRowIndex()); parcel.writeInt(mCollectionItemInfo.getRowSpan()); parcel.writeInt(mCollectionItemInfo.isHeading() ? 1 : 0); + parcel.writeInt(mCollectionItemInfo.isSelected() ? 1 : 0); } else { parcel.writeInt(0); } @@ -2222,10 +2356,16 @@ public class AccessibilityNodeInfo implements Parcelable { mActions= other.mActions; mBooleanProperties = other.mBooleanProperties; mMovementGranularities = other.mMovementGranularities; - final int otherChildIdCount = other.mChildNodeIds.size(); - for (int i = 0; i < otherChildIdCount; i++) { - mChildNodeIds.put(i, other.mChildNodeIds.valueAt(i)); + + final LongArray otherChildNodeIds = other.mChildNodeIds; + if (otherChildNodeIds != null && otherChildNodeIds.size() > 0) { + if (mChildNodeIds == null) { + mChildNodeIds = otherChildNodeIds.clone(); + } else { + mChildNodeIds.addAll(otherChildNodeIds); + } } + mTextSelectionStart = other.mTextSelectionStart; mTextSelectionEnd = other.mTextSelectionEnd; mInputType = other.mInputType; @@ -2255,11 +2395,15 @@ public class AccessibilityNodeInfo implements Parcelable { mLabeledById = parcel.readLong(); mConnectionId = parcel.readInt(); - SparseLongArray childIds = mChildNodeIds; final int childrenSize = parcel.readInt(); - for (int i = 0; i < childrenSize; i++) { - final long childId = parcel.readLong(); - childIds.put(i, childId); + if (childrenSize <= 0) { + mChildNodeIds = null; + } else { + mChildNodeIds = new LongArray(childrenSize); + for (int i = 0; i < childrenSize; i++) { + final long childId = parcel.readLong(); + mChildNodeIds.add(childId); + } } mBoundsInParent.top = parcel.readInt(); @@ -2306,7 +2450,8 @@ public class AccessibilityNodeInfo implements Parcelable { mCollectionInfo = CollectionInfo.obtain( parcel.readInt(), parcel.readInt(), - parcel.readInt() == 1); + parcel.readInt() == 1, + parcel.readInt()); } if (parcel.readInt() == 1) { @@ -2315,6 +2460,7 @@ public class AccessibilityNodeInfo implements Parcelable { parcel.readInt(), parcel.readInt(), parcel.readInt(), + parcel.readInt() == 1, parcel.readInt() == 1); } } @@ -2331,7 +2477,9 @@ public class AccessibilityNodeInfo implements Parcelable { mWindowId = UNDEFINED; mConnectionId = UNDEFINED; mMovementGranularities = 0; - mChildNodeIds.clear(); + if (mChildNodeIds != null) { + mChildNodeIds.clear(); + } mBoundsInParent.set(0, 0, 0, 0); mBoundsInScreen.set(0, 0, 0, 0); mBooleanProperties = 0; @@ -2493,12 +2641,14 @@ public class AccessibilityNodeInfo implements Parcelable { } builder.append("]"); - SparseLongArray childIds = mChildNodeIds; builder.append("; childAccessibilityIds: ["); - for (int i = 0, count = childIds.size(); i < count; i++) { - builder.append(childIds.valueAt(i)); - if (i < count - 1) { - builder.append(", "); + final LongArray childIds = mChildNodeIds; + if (childIds != null) { + for (int i = 0, count = childIds.size(); i < count; i++) { + builder.append(childIds.get(i)); + if (i < count - 1) { + builder.append(", "); + } } } builder.append("]"); @@ -2668,6 +2818,15 @@ public class AccessibilityNodeInfo implements Parcelable { * </p> */ public static final class CollectionInfo { + /** Selection mode where items are not selectable. */ + public static final int SELECTION_MODE_NONE = 0; + + /** Selection mode where a single item may be selected. */ + public static final int SELECTION_MODE_SINGLE = 1; + + /** Selection mode where multiple items may be selected. */ + public static final int SELECTION_MODE_MULTIPLE = 2; + private static final int MAX_POOL_SIZE = 20; private static final SynchronizedPool<CollectionInfo> sPool = @@ -2676,17 +2835,17 @@ public class AccessibilityNodeInfo implements Parcelable { private int mRowCount; private int mColumnCount; private boolean mHierarchical; + private int mSelectionMode; /** * Obtains a pooled instance that is a clone of another one. * * @param other The instance to clone. - * * @hide */ public static CollectionInfo obtain(CollectionInfo other) { - return CollectionInfo.obtain(other.mRowCount, other.mColumnCount, - other.mHierarchical); + return CollectionInfo.obtain(other.mRowCount, other.mColumnCount, other.mHierarchical, + other.mSelectionMode); } /** @@ -2698,9 +2857,34 @@ public class AccessibilityNodeInfo implements Parcelable { */ public static CollectionInfo obtain(int rowCount, int columnCount, boolean hierarchical) { - CollectionInfo info = sPool.acquire(); - return (info != null) ? info : new CollectionInfo(rowCount, - columnCount, hierarchical); + return obtain(rowCount, columnCount, hierarchical, SELECTION_MODE_NONE); + } + + /** + * Obtains a pooled instance. + * + * @param rowCount The number of rows. + * @param columnCount The number of columns. + * @param hierarchical Whether the collection is hierarchical. + * @param selectionMode The collection's selection mode, one of: + * <ul> + * <li>{@link #SELECTION_MODE_NONE} + * <li>{@link #SELECTION_MODE_SINGLE} + * <li>{@link #SELECTION_MODE_MULTIPLE} + * </ul> + */ + public static CollectionInfo obtain(int rowCount, int columnCount, + boolean hierarchical, int selectionMode) { + final CollectionInfo info = sPool.acquire(); + if (info == null) { + return new CollectionInfo(rowCount, columnCount, hierarchical, selectionMode); + } + + info.mRowCount = rowCount; + info.mColumnCount = columnCount; + info.mHierarchical = hierarchical; + info.mSelectionMode = selectionMode; + return info; } /** @@ -2709,12 +2893,14 @@ public class AccessibilityNodeInfo implements Parcelable { * @param rowCount The number of rows. * @param columnCount The number of columns. * @param hierarchical Whether the collection is hierarchical. + * @param selectionMode The collection's selection mode. */ - private CollectionInfo(int rowCount, int columnCount, - boolean hierarchical) { + private CollectionInfo(int rowCount, int columnCount, boolean hierarchical, + int selectionMode) { mRowCount = rowCount; mColumnCount = columnCount; mHierarchical = hierarchical; + mSelectionMode = selectionMode; } /** @@ -2745,6 +2931,20 @@ public class AccessibilityNodeInfo implements Parcelable { } /** + * Gets the collection's selection mode. + * + * @return The collection's selection mode, one of: + * <ul> + * <li>{@link #SELECTION_MODE_NONE} + * <li>{@link #SELECTION_MODE_SINGLE} + * <li>{@link #SELECTION_MODE_MULTIPLE} + * </ul> + */ + public int getSelectionMode() { + return mSelectionMode; + } + + /** * Recycles this instance. */ void recycle() { @@ -2756,6 +2956,7 @@ public class AccessibilityNodeInfo implements Parcelable { mRowCount = 0; mColumnCount = 0; mHierarchical = false; + mSelectionMode = SELECTION_MODE_NONE; } } @@ -2781,12 +2982,11 @@ public class AccessibilityNodeInfo implements Parcelable { * Obtains a pooled instance that is a clone of another one. * * @param other The instance to clone. - * * @hide */ public static CollectionItemInfo obtain(CollectionItemInfo other) { - return CollectionItemInfo.obtain(other.mRowIndex, other.mRowSpan, - other.mColumnIndex, other.mColumnSpan, other.mHeading); + return CollectionItemInfo.obtain(other.mRowIndex, other.mRowSpan, other.mColumnIndex, + other.mColumnSpan, other.mHeading, other.mSelected); } /** @@ -2800,9 +3000,34 @@ public class AccessibilityNodeInfo implements Parcelable { */ public static CollectionItemInfo obtain(int rowIndex, int rowSpan, int columnIndex, int columnSpan, boolean heading) { - CollectionItemInfo info = sPool.acquire(); - return (info != null) ? info : new CollectionItemInfo(rowIndex, - rowSpan, columnIndex, columnSpan, heading); + return obtain(rowIndex, rowSpan, columnIndex, columnSpan, heading, false); + } + + /** + * Obtains a pooled instance. + * + * @param rowIndex The row index at which the item is located. + * @param rowSpan The number of rows the item spans. + * @param columnIndex The column index at which the item is located. + * @param columnSpan The number of columns the item spans. + * @param heading Whether the item is a heading. + * @param selected Whether the item is selected. + */ + public static CollectionItemInfo obtain(int rowIndex, int rowSpan, + int columnIndex, int columnSpan, boolean heading, boolean selected) { + final CollectionItemInfo info = sPool.acquire(); + if (info == null) { + return new CollectionItemInfo( + rowIndex, rowSpan, columnIndex, columnSpan, heading, selected); + } + + info.mRowIndex = rowIndex; + info.mRowSpan = rowSpan; + info.mColumnIndex = columnIndex; + info.mColumnSpan = columnSpan; + info.mHeading = heading; + info.mSelected = selected; + return info; } private boolean mHeading; @@ -2810,6 +3035,7 @@ public class AccessibilityNodeInfo implements Parcelable { private int mRowIndex; private int mColumnSpan; private int mRowSpan; + private boolean mSelected; /** * Creates a new instance. @@ -2820,13 +3046,14 @@ public class AccessibilityNodeInfo implements Parcelable { * @param columnSpan The number of columns the item spans. * @param heading Whether the item is a heading. */ - private CollectionItemInfo(int rowIndex, int rowSpan, - int columnIndex, int columnSpan, boolean heading) { + private CollectionItemInfo(int rowIndex, int rowSpan, int columnIndex, int columnSpan, + boolean heading, boolean selected) { mRowIndex = rowIndex; mRowSpan = rowSpan; mColumnIndex = columnIndex; mColumnSpan = columnSpan; mHeading = heading; + mSelected = selected; } /** @@ -2876,6 +3103,15 @@ public class AccessibilityNodeInfo implements Parcelable { } /** + * Gets if the collection item is selected. + * + * @return If the item is selected. + */ + public boolean isSelected() { + return mSelected; + } + + /** * Recycles this instance. */ void recycle() { @@ -2889,20 +3125,23 @@ public class AccessibilityNodeInfo implements Parcelable { mRowIndex = 0; mRowSpan = 0; mHeading = false; + mSelected = false; } } /** - * @see Parcelable.Creator + * @see android.os.Parcelable.Creator */ public static final Parcelable.Creator<AccessibilityNodeInfo> CREATOR = new Parcelable.Creator<AccessibilityNodeInfo>() { + @Override public AccessibilityNodeInfo createFromParcel(Parcel parcel) { AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(); info.initFromParcel(parcel); return info; } + @Override public AccessibilityNodeInfo[] newArray(int size) { return new AccessibilityNodeInfo[size]; } diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java b/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java index a9473a8..b4944be 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java @@ -18,8 +18,8 @@ package android.view.accessibility; import android.os.Build; import android.util.Log; +import android.util.LongArray; import android.util.LongSparseArray; -import android.util.SparseLongArray; import java.util.HashSet; import java.util.LinkedList; @@ -172,13 +172,15 @@ public class AccessibilityNodeInfoCache { // the new one represents a source state where some of the // children have been removed to avoid having disconnected // subtrees in the cache. - SparseLongArray oldChildrenIds = oldInfo.getChildNodeIds(); - SparseLongArray newChildrenIds = info.getChildNodeIds(); - final int oldChildCount = oldChildrenIds.size(); - for (int i = 0; i < oldChildCount; i++) { - final long oldChildId = oldChildrenIds.valueAt(i); - if (newChildrenIds.indexOfValue(oldChildId) < 0) { - clearSubTreeLocked(oldChildId); + // TODO: Runs in O(n^2), could optimize to O(n + n log n) + final LongArray newChildrenIds = info.getChildNodeIds(); + if (newChildrenIds != null) { + final int oldChildCount = oldInfo.getChildCount(); + for (int i = 0; i < oldChildCount; i++) { + final long oldChildId = oldInfo.getChildId(i); + if (newChildrenIds.indexOf(oldChildId) < 0) { + clearSubTreeLocked(oldChildId); + } } } @@ -237,10 +239,9 @@ public class AccessibilityNodeInfoCache { return; } mCacheImpl.remove(rootNodeId); - SparseLongArray childNodeIds = current.getChildNodeIds(); - final int childCount = childNodeIds.size(); + final int childCount = current.getChildCount(); for (int i = 0; i < childCount; i++) { - final long childNodeId = childNodeIds.valueAt(i); + final long childNodeId = current.getChildId(i); clearSubTreeRecursiveLocked(childNodeId); } } @@ -301,29 +302,35 @@ public class AccessibilityNodeInfoCache { } } - SparseLongArray childIds = current.getChildNodeIds(); - final int childCount = childIds.size(); + final int childCount = current.getChildCount(); for (int i = 0; i < childCount; i++) { - final long childId = childIds.valueAt(i); - AccessibilityNodeInfo child = mCacheImpl.get(childId); + final long childId = current.getChildId(i); + final AccessibilityNodeInfo child = mCacheImpl.get(childId); if (child != null) { fringe.add(child); } } } + int disconnectedNodeCount = 0; // Check for disconnected nodes or ones from another window. for (int i = 0; i < mCacheImpl.size(); i++) { AccessibilityNodeInfo info = mCacheImpl.valueAt(i); if (!seen.contains(info)) { if (info.getWindowId() == windowId) { - Log.e(LOG_TAG, "Disconneced node: " + info); + if (DEBUG) { + Log.e(LOG_TAG, "Disconnected node: " + info); + } + disconnectedNodeCount++; } else { Log.e(LOG_TAG, "Node from: " + info.getWindowId() + " not from:" + windowId + " " + info); } } } + if (disconnectedNodeCount > 0) { + Log.e(LOG_TAG, String.format("Found %d disconnected nodes", disconnectedNodeCount)); + } } } } diff --git a/core/java/android/view/accessibility/AccessibilityRecord.aidl b/core/java/android/view/accessibility/AccessibilityRecord.aidl new file mode 100644 index 0000000..d542af0 --- /dev/null +++ b/core/java/android/view/accessibility/AccessibilityRecord.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2014, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.accessibility; + +parcelable AccessibilityRecord; diff --git a/core/java/android/view/accessibility/CaptioningManager.java b/core/java/android/view/accessibility/CaptioningManager.java index 557239f..a0134d6 100644 --- a/core/java/android/view/accessibility/CaptioningManager.java +++ b/core/java/android/view/accessibility/CaptioningManager.java @@ -250,6 +250,9 @@ public class CaptioningManager { * background colors, edge properties, and typeface. */ public static final class CaptionStyle { + /** Packed value for a color of 'none' and a cached opacity of 100%. */ + private static final int COLOR_NONE_OPAQUE = 0x000000FF; + private static final CaptionStyle WHITE_ON_BLACK; private static final CaptionStyle BLACK_ON_WHITE; private static final CaptionStyle YELLOW_ON_BLACK; @@ -271,6 +274,12 @@ public class CaptioningManager { /** Edge type value specifying drop-shadowed character edges. */ public static final int EDGE_TYPE_DROP_SHADOW = 2; + /** Edge type value specifying raised bevel character edges. */ + public static final int EDGE_TYPE_RAISED = 3; + + /** Edge type value specifying depressed bevel character edges. */ + public static final int EDGE_TYPE_DEPRESSED = 4; + /** The preferred foreground color for video captions. */ public final int foregroundColor; @@ -283,6 +292,8 @@ public class CaptioningManager { * <li>{@link #EDGE_TYPE_NONE} * <li>{@link #EDGE_TYPE_OUTLINE} * <li>{@link #EDGE_TYPE_DROP_SHADOW} + * <li>{@link #EDGE_TYPE_RAISED} + * <li>{@link #EDGE_TYPE_DEPRESSED} * </ul> */ public final int edgeType; @@ -293,6 +304,9 @@ public class CaptioningManager { */ public final int edgeColor; + /** The preferred window color for video captions. */ + public final int windowColor; + /** * @hide */ @@ -301,11 +315,12 @@ public class CaptioningManager { private Typeface mParsedTypeface; private CaptionStyle(int foregroundColor, int backgroundColor, int edgeType, int edgeColor, - String rawTypeface) { + int windowColor, String rawTypeface) { this.foregroundColor = foregroundColor; this.backgroundColor = backgroundColor; this.edgeType = edgeType; this.edgeColor = edgeColor; + this.windowColor = windowColor; mRawTypeface = rawTypeface; } @@ -334,25 +349,27 @@ public class CaptioningManager { cr, Secure.ACCESSIBILITY_CAPTIONING_EDGE_TYPE, defStyle.edgeType); final int edgeColor = Secure.getInt( cr, Secure.ACCESSIBILITY_CAPTIONING_EDGE_COLOR, defStyle.edgeColor); + final int windowColor = Secure.getInt( + cr, Secure.ACCESSIBILITY_CAPTIONING_WINDOW_COLOR, defStyle.windowColor); String rawTypeface = Secure.getString(cr, Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE); if (rawTypeface == null) { rawTypeface = defStyle.mRawTypeface; } - return new CaptionStyle( - foregroundColor, backgroundColor, edgeType, edgeColor, rawTypeface); + return new CaptionStyle(foregroundColor, backgroundColor, edgeType, edgeColor, + windowColor, rawTypeface); } static { - WHITE_ON_BLACK = new CaptionStyle( - Color.WHITE, Color.BLACK, EDGE_TYPE_NONE, Color.BLACK, null); - BLACK_ON_WHITE = new CaptionStyle( - Color.BLACK, Color.WHITE, EDGE_TYPE_NONE, Color.BLACK, null); - YELLOW_ON_BLACK = new CaptionStyle( - Color.YELLOW, Color.BLACK, EDGE_TYPE_NONE, Color.BLACK, null); - YELLOW_ON_BLUE = new CaptionStyle( - Color.YELLOW, Color.BLUE, EDGE_TYPE_NONE, Color.BLACK, null); + WHITE_ON_BLACK = new CaptionStyle(Color.WHITE, Color.BLACK, EDGE_TYPE_NONE, + Color.BLACK, COLOR_NONE_OPAQUE, null); + BLACK_ON_WHITE = new CaptionStyle(Color.BLACK, Color.WHITE, EDGE_TYPE_NONE, + Color.BLACK, COLOR_NONE_OPAQUE, null); + YELLOW_ON_BLACK = new CaptionStyle(Color.YELLOW, Color.BLACK, EDGE_TYPE_NONE, + Color.BLACK, COLOR_NONE_OPAQUE, null); + YELLOW_ON_BLUE = new CaptionStyle(Color.YELLOW, Color.BLUE, EDGE_TYPE_NONE, + Color.BLACK, COLOR_NONE_OPAQUE, null); PRESETS = new CaptionStyle[] { WHITE_ON_BLACK, BLACK_ON_WHITE, YELLOW_ON_BLACK, YELLOW_ON_BLUE diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java index 38043b2..1d1fa1e 100644 --- a/core/java/android/view/animation/AnimationUtils.java +++ b/core/java/android/view/animation/AnimationUtils.java @@ -324,6 +324,8 @@ public class AnimationUtils { interpolator = new AnticipateOvershootInterpolator(c, attrs); } else if (name.equals("bounceInterpolator")) { interpolator = new BounceInterpolator(c, attrs); + } else if (name.equals("pathInterpolator")) { + interpolator = new PathInterpolator(c, attrs); } else { throw new RuntimeException("Unknown interpolator name: " + parser.getName()); } diff --git a/core/java/android/view/animation/BounceInterpolator.java b/core/java/android/view/animation/BounceInterpolator.java index f79e730..ecf99a7 100644 --- a/core/java/android/view/animation/BounceInterpolator.java +++ b/core/java/android/view/animation/BounceInterpolator.java @@ -17,7 +17,6 @@ package android.view.animation; import android.content.Context; -import android.content.res.TypedArray; import android.util.AttributeSet; /** diff --git a/core/java/android/view/animation/PathInterpolator.java b/core/java/android/view/animation/PathInterpolator.java new file mode 100644 index 0000000..a369509 --- /dev/null +++ b/core/java/android/view/animation/PathInterpolator.java @@ -0,0 +1,203 @@ +/* + * 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.view.animation; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Path; +import android.util.AttributeSet; +import android.view.InflateException; + +/** + * An interpolator that can traverse a Path that extends from <code>Point</code> + * <code>(0, 0)</code> to <code>(1, 1)</code>. The x coordinate along the <code>Path</code> + * is the input value and the output is the y coordinate of the line at that point. + * This means that the Path must conform to a function <code>y = f(x)</code>. + * + * <p>The <code>Path</code> must not have gaps in the x direction and must not + * loop back on itself such that there can be two points sharing the same x coordinate. + * It is alright to have a disjoint line in the vertical direction:</p> + * <p><blockquote><pre> + * Path path = new Path(); + * path.lineTo(0.25f, 0.25f); + * path.moveTo(0.25f, 0.5f); + * path.lineTo(1f, 1f); + * </pre></blockquote></p> + */ +public class PathInterpolator implements Interpolator { + + // This governs how accurate the approximation of the Path is. + private static final float PRECISION = 0.002f; + + private float[] mX; // x coordinates in the line + + private float[] mY; // y coordinates in the line + + /** + * Create an interpolator for an arbitrary <code>Path</code>. The <code>Path</code> + * must begin at <code>(0, 0)</code> and end at <code>(1, 1)</code>. + * + * @param path The <code>Path</code> to use to make the line representing the interpolator. + */ + public PathInterpolator(Path path) { + initPath(path); + } + + /** + * Create an interpolator for a quadratic Bezier curve. The end points + * <code>(0, 0)</code> and <code>(1, 1)</code> are assumed. + * + * @param controlX The x coordinate of the quadratic Bezier control point. + * @param controlY The y coordinate of the quadratic Bezier control point. + */ + public PathInterpolator(float controlX, float controlY) { + initQuad(controlX, controlY); + } + + /** + * Create an interpolator for a cubic Bezier curve. The end points + * <code>(0, 0)</code> and <code>(1, 1)</code> are assumed. + * + * @param controlX1 The x coordinate of the first control point of the cubic Bezier. + * @param controlY1 The y coordinate of the first control point of the cubic Bezier. + * @param controlX2 The x coordinate of the second control point of the cubic Bezier. + * @param controlY2 The y coordinate of the second control point of the cubic Bezier. + */ + public PathInterpolator(float controlX1, float controlY1, float controlX2, float controlY2) { + initCubic(controlX1, controlY1, controlX2, controlY2); + } + + public PathInterpolator(Context context, AttributeSet attrs) { + TypedArray a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.PathInterpolator); + if (!a.hasValue(com.android.internal.R.styleable.PathInterpolator_controlX1)) { + throw new InflateException("pathInterpolator requires the controlX1 attribute"); + } else if (!a.hasValue(com.android.internal.R.styleable.PathInterpolator_controlY1)) { + throw new InflateException("pathInterpolator requires the controlY1 attribute"); + } + float x1 = a.getFloat(com.android.internal.R.styleable.PathInterpolator_controlX1, 0); + float y1 = a.getFloat(com.android.internal.R.styleable.PathInterpolator_controlY1, 0); + + boolean hasX2 = a.hasValue(com.android.internal.R.styleable.PathInterpolator_controlX2); + boolean hasY2 = a.hasValue(com.android.internal.R.styleable.PathInterpolator_controlY2); + + if (hasX2 != hasY2) { + throw new InflateException( + "pathInterpolator requires both controlX2 and controlY2 for cubic Beziers."); + } + + if (!hasX2) { + initQuad(x1, y1); + } else { + float x2 = a.getFloat(com.android.internal.R.styleable.PathInterpolator_controlX2, 0); + float y2 = a.getFloat(com.android.internal.R.styleable.PathInterpolator_controlY2, 0); + initCubic(x1, y1, x2, y2); + } + + a.recycle(); + } + + private void initQuad(float controlX, float controlY) { + Path path = new Path(); + path.moveTo(0, 0); + path.quadTo(controlX, controlY, 1f, 1f); + initPath(path); + } + + private void initCubic(float x1, float y1, float x2, float y2) { + Path path = new Path(); + path.moveTo(0, 0); + path.cubicTo(x1, y1, x2, y2, 1f, 1f); + initPath(path); + } + + private void initPath(Path path) { + float[] pointComponents = path.approximate(PRECISION); + + int numPoints = pointComponents.length / 3; + if (pointComponents[1] != 0 || pointComponents[2] != 0 + || pointComponents[pointComponents.length - 2] != 1 + || pointComponents[pointComponents.length - 1] != 1) { + throw new IllegalArgumentException("The Path must start at (0,0) and end at (1,1)"); + } + + mX = new float[numPoints]; + mY = new float[numPoints]; + float prevX = 0; + float prevFraction = 0; + int componentIndex = 0; + for (int i = 0; i < numPoints; i++) { + float fraction = pointComponents[componentIndex++]; + float x = pointComponents[componentIndex++]; + float y = pointComponents[componentIndex++]; + if (fraction == prevFraction && x != prevX) { + throw new IllegalArgumentException( + "The Path cannot have discontinuity in the X axis."); + } + if (x < prevX) { + throw new IllegalArgumentException("The Path cannot loop back on itself."); + } + mX[i] = x; + mY[i] = y; + prevX = x; + prevFraction = fraction; + } + } + + /** + * Using the line in the Path in this interpolator that can be described as + * <code>y = f(x)</code>, finds the y coordinate of the line given <code>t</code> + * as the x coordinate. Values less than 0 will always return 0 and values greater + * than 1 will always return 1. + * + * @param t Treated as the x coordinate along the line. + * @return The y coordinate of the Path along the line where x = <code>t</code>. + * @see Interpolator#getInterpolation(float) + */ + @Override + public float getInterpolation(float t) { + if (t <= 0) { + return 0; + } else if (t >= 1) { + return 1; + } + // Do a binary search for the correct x to interpolate between. + int startIndex = 0; + int endIndex = mX.length - 1; + + while (endIndex - startIndex > 1) { + int midIndex = (startIndex + endIndex) / 2; + if (t < mX[midIndex]) { + endIndex = midIndex; + } else { + startIndex = midIndex; + } + } + + float xRange = mX[endIndex] - mX[startIndex]; + if (xRange == 0) { + return mY[startIndex]; + } + + float tInRange = t - mX[startIndex]; + float fraction = tInRange / xRange; + + float startY = mY[startIndex]; + float endY = mY[endIndex]; + return startY + (fraction * (endY - startY)); + } + +} diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java index f730cf7..cccfa78 100644 --- a/core/java/android/view/inputmethod/BaseInputConnection.java +++ b/core/java/android/view/inputmethod/BaseInputConnection.java @@ -484,10 +484,10 @@ public class BaseInputConnection implements InputConnection { final Editable content = getEditable(); if (content == null) return false; int len = content.length(); - if (start > len || end > len) { + if (start > len || end > len || start < 0 || end < 0) { // If the given selection is out of bounds, just ignore it. // Most likely the text was changed out from under the IME, - // the the IME is going to have to update all of its state + // and the IME is going to have to update all of its state // anyway. return true; } @@ -601,7 +601,11 @@ public class BaseInputConnection implements InputConnection { } beginBatchEdit(); - + if (!composing && !TextUtils.isEmpty(text)) { + // Notify the text is committed by the user to InputMethodManagerService + mIMM.notifyTextCommitted(); + } + // delete composing text set previously. int a = getComposingSpanStart(content); int b = getComposingSpanEnd(content); diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java index d4e005b..c0395cf 100644 --- a/core/java/android/view/inputmethod/EditorInfo.java +++ b/core/java/android/view/inputmethod/EditorInfo.java @@ -26,13 +26,13 @@ import android.util.Printer; /** * An EditorInfo describes several attributes of a text editing object * that an input method is communicating with (typically an EditText), most - * importantly the type of text content it contains. + * importantly the type of text content it contains and the current cursor position. */ public class EditorInfo implements InputType, Parcelable { /** * The content type of the text box, whose bits are defined by * {@link InputType}. - * + * * @see InputType * @see #TYPE_MASK_CLASS * @see #TYPE_MASK_VARIATION @@ -47,55 +47,55 @@ public class EditorInfo implements InputType, Parcelable { * to provide alternative mechanisms for providing that command. */ public static final int IME_MASK_ACTION = 0x000000ff; - + /** * Bits of {@link #IME_MASK_ACTION}: no specific action has been * associated with this editor, let the editor come up with its own if * it can. */ public static final int IME_ACTION_UNSPECIFIED = 0x00000000; - + /** * Bits of {@link #IME_MASK_ACTION}: there is no available action. */ public static final int IME_ACTION_NONE = 0x00000001; - + /** * Bits of {@link #IME_MASK_ACTION}: the action key performs a "go" * operation to take the user to the target of the text they typed. * Typically used, for example, when entering a URL. */ public static final int IME_ACTION_GO = 0x00000002; - + /** * Bits of {@link #IME_MASK_ACTION}: the action key performs a "search" * operation, taking the user to the results of searching for the text * they have typed (in whatever context is appropriate). */ public static final int IME_ACTION_SEARCH = 0x00000003; - + /** * Bits of {@link #IME_MASK_ACTION}: the action key performs a "send" * operation, delivering the text to its target. This is typically used * when composing a message in IM or SMS where sending is immediate. */ public static final int IME_ACTION_SEND = 0x00000004; - + /** * Bits of {@link #IME_MASK_ACTION}: the action key performs a "next" * operation, taking the user to the next field that will accept text. */ public static final int IME_ACTION_NEXT = 0x00000005; - + /** * Bits of {@link #IME_MASK_ACTION}: the action key performs a "done" * operation, typically meaning there is nothing more to input and the * IME will be closed. */ public static final int IME_ACTION_DONE = 0x00000006; - + /** - * Bits of {@link #IME_MASK_ACTION}: Like {@link #IME_ACTION_NEXT}, but + * Bits of {@link #IME_MASK_ACTION}: like {@link #IME_ACTION_NEXT}, but * for moving to the previous field. This will normally not be used to * specify an action (since it precludes {@link #IME_ACTION_NEXT}), but * can be returned to the app if it sets {@link #IME_FLAG_NAVIGATE_PREVIOUS}. @@ -154,7 +154,7 @@ public class EditorInfo implements InputType, Parcelable { * on older versions of the platform. */ public static final int IME_FLAG_NO_EXTRACT_UI = 0x10000000; - + /** * Flag of {@link #imeOptions}: used in conjunction with one of the actions * masked by {@link #IME_MASK_ACTION}, this indicates that the action @@ -167,7 +167,7 @@ public class EditorInfo implements InputType, Parcelable { * to show more text. */ public static final int IME_FLAG_NO_ACCESSORY_ACTION = 0x20000000; - + /** * Flag of {@link #imeOptions}: used in conjunction with one of the actions * masked by {@link #IME_MASK_ACTION}. If this flag is not set, IMEs will @@ -202,13 +202,13 @@ public class EditorInfo implements InputType, Parcelable { * Generic unspecified type for {@link #imeOptions}. */ public static final int IME_NULL = 0x00000000; - + /** * Extended type information for the editor, to help the IME better * integrate with it. */ public int imeOptions = IME_NULL; - + /** * A string supplying additional information options that are * private to a particular IME implementation. The string must be @@ -221,7 +221,7 @@ public class EditorInfo implements InputType, Parcelable { * attribute of a TextView. */ public String privateImeOptions = null; - + /** * In some cases an IME may be able to display an arbitrary label for * a command the user can perform, which you can specify here. This is @@ -233,7 +233,7 @@ public class EditorInfo implements InputType, Parcelable { * ignore this. */ public CharSequence actionLabel = null; - + /** * If {@link #actionLabel} has been given, this is the id for that command * when the user presses its button that is delivered back with @@ -241,50 +241,66 @@ public class EditorInfo implements InputType, Parcelable { * InputConnection.performEditorAction()}. */ public int actionId = 0; - + /** * The text offset of the start of the selection at the time editing - * began; -1 if not known. Keep in mind some IMEs may not be able - * to give their full feature set without knowing the cursor position; - * avoid passing -1 here if you can. + * begins; -1 if not known. Keep in mind that, without knowing the cursor + * position, many IMEs will not be able to offer their full feature set and + * may even behave in unpredictable ways: pass the actual cursor position + * here if possible at all. + * + * <p>Also, this needs to be the cursor position <strong>right now</strong>, + * not at some point in the past, even if input is starting in the same text field + * as before. When the app is filling this object, input is about to start by + * definition, and this value will override any value the app may have passed to + * {@link InputMethodManager#updateSelection(android.view.View, int, int, int, int)} + * before.</p> */ public int initialSelStart = -1; - + /** - * The text offset of the end of the selection at the time editing - * began; -1 if not known. Keep in mind some IMEs may not be able - * to give their full feature set without knowing the cursor position; - * avoid passing -1 here if you can. + * <p>The text offset of the end of the selection at the time editing + * begins; -1 if not known. Keep in mind that, without knowing the cursor + * position, many IMEs will not be able to offer their full feature set and + * may behave in unpredictable ways: pass the actual cursor position + * here if possible at all.</p> + * + * <p>Also, this needs to be the cursor position <strong>right now</strong>, + * not at some point in the past, even if input is starting in the same text field + * as before. When the app is filling this object, input is about to start by + * definition, and this value will override any value the app may have passed to + * {@link InputMethodManager#updateSelection(android.view.View, int, int, int, int)} + * before.</p> */ public int initialSelEnd = -1; - + /** * The capitalization mode of the first character being edited in the * text. Values may be any combination of * {@link TextUtils#CAP_MODE_CHARACTERS TextUtils.CAP_MODE_CHARACTERS}, * {@link TextUtils#CAP_MODE_WORDS TextUtils.CAP_MODE_WORDS}, and * {@link TextUtils#CAP_MODE_SENTENCES TextUtils.CAP_MODE_SENTENCES}, though - * you should generally just take a non-zero value to mean start out in - * caps mode. + * you should generally just take a non-zero value to mean "start out in + * caps mode". */ public int initialCapsMode = 0; - + /** * The "hint" text of the text view, typically shown in-line when the * text is empty to tell the user what to enter. */ public CharSequence hintText; - + /** * A label to show to the user describing the text they are writing. */ public CharSequence label; - + /** * Name of the package that owns this editor. */ public String packageName; - + /** * Identifier for the editor's field. This is optional, and may be * 0. By default it is filled in with the result of @@ -292,14 +308,14 @@ public class EditorInfo implements InputType, Parcelable { * is being edited. */ public int fieldId; - + /** * Additional name for the editor's field. This can supply additional * name information for the field. By default it is null. The actual * contents have no meaning. */ public String fieldName; - + /** * Any extra data to supply to the input method. This is for extended * communication with specific input methods; the name fields in the @@ -309,7 +325,7 @@ public class EditorInfo implements InputType, Parcelable { * attribute of a TextView. */ public Bundle extras; - + /** * Ensure that the data in this EditorInfo is compatible with an application * that was developed against the given target API version. This can @@ -365,10 +381,10 @@ public class EditorInfo implements InputType, Parcelable { + " fieldName=" + fieldName); pw.println(prefix + "extras=" + extras); } - + /** * Used to package this object into a {@link Parcel}. - * + * * @param dest The {@link Parcel} to be written. * @param flags The flags used for parceling. */ @@ -392,30 +408,31 @@ public class EditorInfo implements InputType, Parcelable { /** * Used to make this class parcelable. */ - public static final Parcelable.Creator<EditorInfo> CREATOR = new Parcelable.Creator<EditorInfo>() { - public EditorInfo createFromParcel(Parcel source) { - EditorInfo res = new EditorInfo(); - res.inputType = source.readInt(); - res.imeOptions = source.readInt(); - res.privateImeOptions = source.readString(); - res.actionLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); - res.actionId = source.readInt(); - res.initialSelStart = source.readInt(); - res.initialSelEnd = source.readInt(); - res.initialCapsMode = source.readInt(); - res.hintText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); - res.label = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); - res.packageName = source.readString(); - res.fieldId = source.readInt(); - res.fieldName = source.readString(); - res.extras = source.readBundle(); - return res; - } + public static final Parcelable.Creator<EditorInfo> CREATOR = + new Parcelable.Creator<EditorInfo>() { + public EditorInfo createFromParcel(Parcel source) { + EditorInfo res = new EditorInfo(); + res.inputType = source.readInt(); + res.imeOptions = source.readInt(); + res.privateImeOptions = source.readString(); + res.actionLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); + res.actionId = source.readInt(); + res.initialSelStart = source.readInt(); + res.initialSelEnd = source.readInt(); + res.initialCapsMode = source.readInt(); + res.hintText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); + res.label = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); + res.packageName = source.readString(); + res.fieldId = source.readInt(); + res.fieldName = source.readString(); + res.extras = source.readBundle(); + return res; + } - public EditorInfo[] newArray(int size) { - return new EditorInfo[size]; - } - }; + public EditorInfo[] newArray(int size) { + return new EditorInfo[size]; + } + }; public int describeContents() { return 0; diff --git a/core/java/android/view/inputmethod/ExtractedTextRequest.java b/core/java/android/view/inputmethod/ExtractedTextRequest.java index f658b87..bf0bef3 100644 --- a/core/java/android/view/inputmethod/ExtractedTextRequest.java +++ b/core/java/android/view/inputmethod/ExtractedTextRequest.java @@ -18,7 +18,6 @@ package android.view.inputmethod; import android.os.Parcel; import android.os.Parcelable; -import android.text.TextUtils; /** * Description of what an input method would like from an application when diff --git a/core/java/android/view/inputmethod/InputBinding.java b/core/java/android/view/inputmethod/InputBinding.java index f4209ef..bcd459e 100644 --- a/core/java/android/view/inputmethod/InputBinding.java +++ b/core/java/android/view/inputmethod/InputBinding.java @@ -19,7 +19,6 @@ package android.view.inputmethod; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; -import android.text.TextUtils; /** * Information given to an {@link InputMethod} about a client connecting diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java index 5df5811..f8160c8 100644 --- a/core/java/android/view/inputmethod/InputMethodInfo.java +++ b/core/java/android/view/inputmethod/InputMethodInfo.java @@ -1,12 +1,12 @@ /* * Copyright (C) 2007-2008 The Android Open Source Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -37,6 +37,7 @@ import android.util.Printer; import android.util.Slog; import android.util.Xml; import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder; +import android.view.inputmethod.InputMethodSubtypeArray; import java.io.IOException; import java.util.ArrayList; @@ -64,7 +65,7 @@ public final class InputMethodInfo implements Parcelable { * The Service that implements this input method component. */ final ResolveInfo mService; - + /** * The unique string Id to identify the input method. This is generated * from the input method component. @@ -86,9 +87,9 @@ public final class InputMethodInfo implements Parcelable { final int mIsDefaultResId; /** - * The array of the subtypes. + * An array-like container of the subtypes. */ - private final ArrayList<InputMethodSubtype> mSubtypes = new ArrayList<InputMethodSubtype>(); + private final InputMethodSubtypeArray mSubtypes; private final boolean mIsAuxIme; @@ -138,28 +139,29 @@ public final class InputMethodInfo implements Parcelable { int isDefaultResId = 0; XmlResourceParser parser = null; + final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); try { parser = si.loadXmlMetaData(pm, InputMethod.SERVICE_META_DATA); if (parser == null) { throw new XmlPullParserException("No " + InputMethod.SERVICE_META_DATA + " meta-data"); } - + Resources res = pm.getResourcesForApplication(si.applicationInfo); - + AttributeSet attrs = Xml.asAttributeSet(parser); - + int type; while ((type=parser.next()) != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) { } - + String nodeName = parser.getName(); if (!"input-method".equals(nodeName)) { throw new XmlPullParserException( "Meta-data does not start with input-method tag"); } - + TypedArray sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.InputMethod); settingsActivityComponent = sa.getString( @@ -206,7 +208,7 @@ public final class InputMethodInfo implements Parcelable { if (!subtype.isAuxiliary()) { isAuxIme = false; } - mSubtypes.add(subtype); + subtypes.add(subtype); } } } catch (NameNotFoundException e) { @@ -216,7 +218,7 @@ public final class InputMethodInfo implements Parcelable { if (parser != null) parser.close(); } - if (mSubtypes.size() == 0) { + if (subtypes.size() == 0) { isAuxIme = false; } @@ -225,14 +227,15 @@ public final class InputMethodInfo implements Parcelable { final int N = additionalSubtypes.size(); for (int i = 0; i < N; ++i) { final InputMethodSubtype subtype = additionalSubtypes.get(i); - if (!mSubtypes.contains(subtype)) { - mSubtypes.add(subtype); + if (!subtypes.contains(subtype)) { + subtypes.add(subtype); } else { Slog.w(TAG, "Duplicated subtype definition found: " + subtype.getLocale() + ", " + subtype.getMode()); } } } + mSubtypes = new InputMethodSubtypeArray(subtypes); mSettingsActivityName = settingsActivityComponent; mIsDefaultResId = isDefaultResId; mIsAuxIme = isAuxIme; @@ -246,7 +249,7 @@ public final class InputMethodInfo implements Parcelable { mIsAuxIme = source.readInt() == 1; mSupportsSwitchingToNextInputMethod = source.readInt() == 1; mService = ResolveInfo.CREATOR.createFromParcel(source); - source.readTypedList(mSubtypes, InputMethodSubtype.CREATOR); + mSubtypes = new InputMethodSubtypeArray(source); mForceDefault = false; } @@ -272,9 +275,7 @@ public final class InputMethodInfo implements Parcelable { mSettingsActivityName = settingsActivity; mIsDefaultResId = isDefaultResId; mIsAuxIme = isAuxIme; - if (subtypes != null) { - mSubtypes.addAll(subtypes); - } + mSubtypes = new InputMethodSubtypeArray(subtypes); mForceDefault = forceDefault; mSupportsSwitchingToNextInputMethod = true; } @@ -338,7 +339,7 @@ public final class InputMethodInfo implements Parcelable { /** * Load the user-displayed label for this input method. - * + * * @param pm Supply a PackageManager used to load the input method's * resources. */ @@ -348,7 +349,7 @@ public final class InputMethodInfo implements Parcelable { /** * Load the user-displayed icon for this input method. - * + * * @param pm Supply a PackageManager used to load the input method's * resources. */ @@ -362,9 +363,9 @@ public final class InputMethodInfo implements Parcelable { * an {@link android.content.Intent} whose action is MAIN and with an * explicit {@link android.content.ComponentName} * composed of {@link #getPackageName} and the class name returned here. - * + * * <p>A null will be returned if there is no settings activity associated - * with the input method. + * with the input method.</p> */ public String getSettingsActivity() { return mSettingsActivityName; @@ -374,7 +375,7 @@ public final class InputMethodInfo implements Parcelable { * Return the count of the subtypes of Input Method. */ public int getSubtypeCount() { - return mSubtypes.size(); + return mSubtypes.getCount(); } /** @@ -419,7 +420,7 @@ public final class InputMethodInfo implements Parcelable { pw.println(prefix + "Service:"); mService.dump(pw, prefix + " "); } - + @Override public String toString() { return "InputMethodInfo{" + mId @@ -430,7 +431,7 @@ public final class InputMethodInfo implements Parcelable { /** * Used to test whether the given parameter object is an * {@link InputMethodInfo} and its Id is the same to this one. - * + * * @return true if the given parameter object is an * {@link InputMethodInfo} and its Id is the same to this one. */ @@ -467,7 +468,7 @@ public final class InputMethodInfo implements Parcelable { /** * Used to package this object into a {@link Parcel}. - * + * * @param dest The {@link Parcel} to be written. * @param flags The flags used for parceling. */ @@ -479,7 +480,7 @@ public final class InputMethodInfo implements Parcelable { dest.writeInt(mIsAuxIme ? 1 : 0); dest.writeInt(mSupportsSwitchingToNextInputMethod ? 1 : 0); mService.writeToParcel(dest, flags); - dest.writeTypedList(mSubtypes); + mSubtypes.writeToParcel(dest); } /** diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 53f7c79..e812edd 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -1394,6 +1394,14 @@ public final class InputMethodManager { /** * Report the current selection range. + * + * <p><strong>Editor authors</strong>, you need to call this method whenever + * the cursor moves in your editor. Remember that in addition to doing this, your + * editor needs to always supply current cursor values in + * {@link EditorInfo#initialSelStart} and {@link EditorInfo#initialSelEnd} every + * time {@link android.view.View#onCreateInputConnection(EditorInfo)} is + * called, which happens whenever the keyboard shows up or the focus changes + * to a text field, among other cases.</p> */ public void updateSelection(View view, int selStart, int selEnd, int candidatesStart, int candidatesEnd) { @@ -1805,6 +1813,20 @@ public final class InputMethodManager { } /** + * Notify the current IME commits text + * @hide + */ + public void notifyTextCommitted() { + synchronized (mH) { + try { + mService.notifyTextCommitted(); + } catch (RemoteException e) { + Log.w(TAG, "IME died: " + mCurId, e); + } + } + } + + /** * Returns a map of all shortcut input method info and their subtypes. */ public Map<InputMethodInfo, List<InputMethodSubtype>> getShortcutInputMethodsAndSubtypes() { @@ -1840,6 +1862,21 @@ public final class InputMethodManager { } /** + * @return The current height of the input method window. + * @hide + */ + public int getInputMethodWindowVisibleHeight() { + synchronized (mH) { + try { + return mService.getInputMethodWindowVisibleHeight(); + } catch (RemoteException e) { + Log.w(TAG, "IME died: " + mCurId, e); + return 0; + } + } + } + + /** * Force switch to the last used input method and subtype. If the last input method didn't have * any subtypes, the framework will simply switch to the last input method with no subtype * specified. diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java index 2ab3024..e7ada27 100644 --- a/core/java/android/view/inputmethod/InputMethodSubtype.java +++ b/core/java/android/view/inputmethod/InputMethodSubtype.java @@ -472,12 +472,12 @@ public final class InputMethodSubtype implements Parcelable { return (subtype.hashCode() == hashCode()); } return (subtype.hashCode() == hashCode()) - && (subtype.getNameResId() == getNameResId()) - && (subtype.getMode().equals(getMode())) - && (subtype.getIconResId() == getIconResId()) && (subtype.getLocale().equals(getLocale())) + && (subtype.getMode().equals(getMode())) && (subtype.getExtraValue().equals(getExtraValue())) && (subtype.isAuxiliary() == isAuxiliary()) + && (subtype.overridesImplicitlyEnabledSubtype() + == overridesImplicitlyEnabledSubtype()) && (subtype.isAsciiCapable() == isAsciiCapable()); } return false; diff --git a/core/java/android/view/inputmethod/InputMethodSubtypeArray.java b/core/java/android/view/inputmethod/InputMethodSubtypeArray.java new file mode 100644 index 0000000..5bef71f --- /dev/null +++ b/core/java/android/view/inputmethod/InputMethodSubtypeArray.java @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2007-2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package android.view.inputmethod; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.AndroidRuntimeException; +import android.util.Slog; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +/** + * An array-like container that stores multiple instances of {@link InputMethodSubtype}. + * + * <p>This container is designed to reduce the risk of {@link TransactionTooLargeException} + * when one or more instancess of {@link InputMethodInfo} are transferred through IPC. + * Basically this class does following three tasks.</p> + * <ul> + * <li>Applying compression for the marshalled data</li> + * <li>Lazily unmarshalling objects</li> + * <li>Caching the marshalled data when appropriate</li> + * </ul> + * + * @hide + */ +public class InputMethodSubtypeArray { + private final static String TAG = "InputMethodSubtypeArray"; + + /** + * Create a new instance of {@link InputMethodSubtypeArray} from an existing list of + * {@link InputMethodSubtype}. + * + * @param subtypes A list of {@link InputMethodSubtype} from which + * {@link InputMethodSubtypeArray} will be created. + */ + public InputMethodSubtypeArray(final List<InputMethodSubtype> subtypes) { + if (subtypes == null) { + mCount = 0; + return; + } + mCount = subtypes.size(); + mInstance = subtypes.toArray(new InputMethodSubtype[mCount]); + } + + /** + * Unmarshall an instance of {@link InputMethodSubtypeArray} from a given {@link Parcel} + * object. + * + * @param source A {@link Parcel} object from which {@link InputMethodSubtypeArray} will be + * unmarshalled. + */ + public InputMethodSubtypeArray(final Parcel source) { + mCount = source.readInt(); + if (mCount > 0) { + mDecompressedSize = source.readInt(); + mCompressedData = source.createByteArray(); + } + } + + /** + * Marshall the instance into a given {@link Parcel} object. + * + * <p>This methods may take a bit additional time to compress data lazily when called + * first time.</p> + * + * @param source A {@link Parcel} object to which {@link InputMethodSubtypeArray} will be + * marshalled. + */ + public void writeToParcel(final Parcel dest) { + if (mCount == 0) { + dest.writeInt(mCount); + return; + } + + byte[] compressedData = mCompressedData; + int decompressedSize = mDecompressedSize; + if (compressedData == null && decompressedSize == 0) { + synchronized (mLockObject) { + compressedData = mCompressedData; + decompressedSize = mDecompressedSize; + if (compressedData == null && decompressedSize == 0) { + final byte[] decompressedData = marshall(mInstance); + compressedData = compress(decompressedData); + if (compressedData == null) { + decompressedSize = -1; + Slog.i(TAG, "Failed to compress data."); + } else { + decompressedSize = decompressedData.length; + } + mDecompressedSize = decompressedSize; + mCompressedData = compressedData; + } + } + } + + if (compressedData != null && decompressedSize > 0) { + dest.writeInt(mCount); + dest.writeInt(decompressedSize); + dest.writeByteArray(compressedData); + } else { + Slog.i(TAG, "Unexpected state. Behaving as an empty array."); + dest.writeInt(0); + } + } + + /** + * Return {@link InputMethodSubtype} specified with the given index. + * + * <p>This methods may take a bit additional time to decompress data lazily when called + * first time.</p> + * + * @param index The index of {@link InputMethodSubtype}. + */ + public InputMethodSubtype get(final int index) { + if (index < 0 || mCount <= index) { + throw new ArrayIndexOutOfBoundsException(); + } + InputMethodSubtype[] instance = mInstance; + if (instance == null) { + synchronized (mLockObject) { + instance = mInstance; + if (instance == null) { + final byte[] decompressedData = + decompress(mCompressedData, mDecompressedSize); + // Clear the compressed data until {@link #getMarshalled()} is called. + mCompressedData = null; + mDecompressedSize = 0; + if (decompressedData != null) { + instance = unmarshall(decompressedData); + } else { + Slog.e(TAG, "Failed to decompress data. Returns null as fallback."); + instance = new InputMethodSubtype[mCount]; + } + mInstance = instance; + } + } + } + return instance[index]; + } + + /** + * Return the number of {@link InputMethodSubtype} objects. + */ + public int getCount() { + return mCount; + } + + private final Object mLockObject = new Object(); + private final int mCount; + + private volatile InputMethodSubtype[] mInstance; + private volatile byte[] mCompressedData; + private volatile int mDecompressedSize; + + private static byte[] marshall(final InputMethodSubtype[] array) { + Parcel parcel = null; + try { + parcel = Parcel.obtain(); + parcel.writeTypedArray(array, 0); + return parcel.marshall(); + } finally { + if (parcel != null) { + parcel.recycle(); + parcel = null; + } + } + } + + private static InputMethodSubtype[] unmarshall(final byte[] data) { + Parcel parcel = null; + try { + parcel = Parcel.obtain(); + parcel.unmarshall(data, 0, data.length); + parcel.setDataPosition(0); + return parcel.createTypedArray(InputMethodSubtype.CREATOR); + } finally { + if (parcel != null) { + parcel.recycle(); + parcel = null; + } + } + } + + private static byte[] compress(final byte[] data) { + ByteArrayOutputStream resultStream = null; + GZIPOutputStream zipper = null; + try { + resultStream = new ByteArrayOutputStream(); + zipper = new GZIPOutputStream(resultStream); + zipper.write(data); + } catch(IOException e) { + return null; + } finally { + try { + if (zipper != null) { + zipper.close(); + } + } catch (IOException e) { + zipper = null; + Slog.e(TAG, "Failed to close the stream.", e); + // swallowed, not propagated back to the caller + } + try { + if (resultStream != null) { + resultStream.close(); + } + } catch (IOException e) { + resultStream = null; + Slog.e(TAG, "Failed to close the stream.", e); + // swallowed, not propagated back to the caller + } + } + return resultStream != null ? resultStream.toByteArray() : null; + } + + private static byte[] decompress(final byte[] data, final int expectedSize) { + ByteArrayInputStream inputStream = null; + GZIPInputStream unzipper = null; + try { + inputStream = new ByteArrayInputStream(data); + unzipper = new GZIPInputStream(inputStream); + final byte [] result = new byte[expectedSize]; + int totalReadBytes = 0; + while (totalReadBytes < result.length) { + final int restBytes = result.length - totalReadBytes; + final int readBytes = unzipper.read(result, totalReadBytes, restBytes); + if (readBytes < 0) { + break; + } + totalReadBytes += readBytes; + } + if (expectedSize != totalReadBytes) { + return null; + } + return result; + } catch(IOException e) { + return null; + } finally { + try { + if (unzipper != null) { + unzipper.close(); + } + } catch (IOException e) { + Slog.e(TAG, "Failed to close the stream.", e); + // swallowed, not propagated back to the caller + } + try { + if (inputStream != null) { + inputStream.close(); + } + } catch (IOException e) { + Slog.e(TAG, "Failed to close the stream.", e); + // swallowed, not propagated back to the caller + } + } + } +} diff --git a/core/java/android/webkit/CacheManager.java b/core/java/android/webkit/CacheManager.java index bbd3f2b..45e6eb3 100644 --- a/core/java/android/webkit/CacheManager.java +++ b/core/java/android/webkit/CacheManager.java @@ -16,13 +16,7 @@ package android.webkit; -import android.content.Context; -import android.net.http.Headers; -import android.util.Log; - import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; diff --git a/core/java/android/webkit/DateSorter.java b/core/java/android/webkit/DateSorter.java index 82c13ae..fede244 100644 --- a/core/java/android/webkit/DateSorter.java +++ b/core/java/android/webkit/DateSorter.java @@ -20,7 +20,6 @@ import android.content.Context; import android.content.res.Resources; import java.util.Calendar; -import java.util.Date; import java.util.Locale; import libcore.icu.LocaleData; diff --git a/core/java/android/webkit/DebugFlags.java b/core/java/android/webkit/DebugFlags.java index b5ca8c1..7b3cb1b 100644 --- a/core/java/android/webkit/DebugFlags.java +++ b/core/java/android/webkit/DebugFlags.java @@ -31,7 +31,6 @@ public class DebugFlags { public static final boolean COOKIE_SYNC_MANAGER = false; public static final boolean TRACE_API = false; public static final boolean TRACE_CALLBACK = false; - public static final boolean TRACE_JAVASCRIPT_BRIDGE = false; public static final boolean URL_UTIL = false; public static final boolean WEB_SYNC_MANAGER = false; diff --git a/core/java/android/webkit/Plugin.java b/core/java/android/webkit/Plugin.java index 529820b..072e02a 100644 --- a/core/java/android/webkit/Plugin.java +++ b/core/java/android/webkit/Plugin.java @@ -21,7 +21,6 @@ import com.android.internal.R; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; -import android.webkit.WebView; /** * Represents a plugin (Java equivalent of the PluginPackageAndroid diff --git a/core/java/android/webkit/WebResourceResponse.java b/core/java/android/webkit/WebResourceResponse.java index b7171ee..f21e2b4 100644 --- a/core/java/android/webkit/WebResourceResponse.java +++ b/core/java/android/webkit/WebResourceResponse.java @@ -16,8 +16,6 @@ package android.webkit; -import android.net.http.Headers; - import java.io.InputStream; /** diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index d53bb74..81d36a4 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -28,7 +28,6 @@ import android.graphics.drawable.Drawable; import android.net.http.SslCertificate; import android.os.Build; import android.os.Bundle; -import android.os.CancellationSignal; import android.os.Looper; import android.os.Message; import android.os.StrictMode; @@ -255,7 +254,7 @@ public class WebView extends AbsoluteLayout // Throwing an exception for incorrect thread usage if the // build target is JB MR2 or newer. Defaults to false, and is // set in the WebView constructor. - private static Boolean sEnforceThreadChecking = false; + private static volatile boolean sEnforceThreadChecking = false; /** * Transportation object for returning WebView across thread boundaries. @@ -449,10 +448,12 @@ public class WebView extends AbsoluteLayout * * @param context a Context object used to access application assets * @param attrs an AttributeSet passed to our parent - * @param defStyle the default style resource ID + * @param defStyleAttr an attribute in the current theme that contains a + * reference to a style resource that supplies default values for + * the view. Can be 0 to not look for defaults. */ - public WebView(Context context, AttributeSet attrs, int defStyle) { - this(context, attrs, defStyle, false); + public WebView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); } /** @@ -460,7 +461,26 @@ public class WebView extends AbsoluteLayout * * @param context a Context object used to access application assets * @param attrs an AttributeSet passed to our parent - * @param defStyle the default style resource ID + * @param defStyleAttr an attribute in the current theme that contains a + * reference to a style resource that supplies default values for + * the view. Can be 0 to not look for defaults. + * @param defStyleRes a resource identifier of a style resource that + * supplies default values for the view, used only if + * defStyleAttr is 0 or can not be found in the theme. Can be 0 + * to not look for defaults. + */ + public WebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + this(context, attrs, defStyleAttr, defStyleRes, null, false); + } + + /** + * Constructs a new WebView with layout parameters and a default style. + * + * @param context a Context object used to access application assets + * @param attrs an AttributeSet passed to our parent + * @param defStyleAttr an attribute in the current theme that contains a + * reference to a style resource that supplies default values for + * the view. Can be 0 to not look for defaults. * @param privateBrowsing whether this WebView will be initialized in * private mode * @@ -470,9 +490,9 @@ public class WebView extends AbsoluteLayout * and {@link WebStorage} for fine-grained control of privacy data. */ @Deprecated - public WebView(Context context, AttributeSet attrs, int defStyle, + public WebView(Context context, AttributeSet attrs, int defStyleAttr, boolean privateBrowsing) { - this(context, attrs, defStyle, null, privateBrowsing); + this(context, attrs, defStyleAttr, 0, null, privateBrowsing); } /** @@ -483,7 +503,9 @@ public class WebView extends AbsoluteLayout * * @param context a Context object used to access application assets * @param attrs an AttributeSet passed to our parent - * @param defStyle the default style resource ID + * @param defStyleAttr an attribute in the current theme that contains a + * reference to a style resource that supplies default values for + * the view. Can be 0 to not look for defaults. * @param javaScriptInterfaces a Map of interface names, as keys, and * object implementing those interfaces, as * values @@ -492,10 +514,18 @@ public class WebView extends AbsoluteLayout * @hide This is used internally by dumprendertree, as it requires the javaScript interfaces to * be added synchronously, before a subsequent loadUrl call takes effect. */ + protected WebView(Context context, AttributeSet attrs, int defStyleAttr, + Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) { + this(context, attrs, defStyleAttr, 0, javaScriptInterfaces, privateBrowsing); + } + + /** + * @hide + */ @SuppressWarnings("deprecation") // for super() call into deprecated base class constructor. - protected WebView(Context context, AttributeSet attrs, int defStyle, + protected WebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes, Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) { - super(context, attrs, defStyle); + super(context, attrs, defStyleAttr, defStyleRes); if (context == null) { throw new IllegalArgumentException("Invalid context argument"); } @@ -790,7 +820,15 @@ public class WebView extends AbsoluteLayout */ public void loadUrl(String url, Map<String, String> additionalHttpHeaders) { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "loadUrl(extra headers)=" + url); + if (DebugFlags.TRACE_API) { + StringBuilder headers = new StringBuilder(); + if (additionalHttpHeaders != null) { + for (Map.Entry<String, String> entry : additionalHttpHeaders.entrySet()) { + headers.append(entry.getKey() + ":" + entry.getValue() + "\n"); + } + } + Log.d(LOGTAG, "loadUrl(extra headers)=" + url + "\n" + headers); + } mProvider.loadUrl(url, additionalHttpHeaders); } @@ -807,8 +845,8 @@ public class WebView extends AbsoluteLayout /** * Loads the URL with postData using "POST" method into this WebView. If url - * is not a network URL, it will be loaded with {link - * {@link #loadUrl(String)} instead. + * is not a network URL, it will be loaded with {@link #loadUrl(String)} + * instead, ignoring the postData param. * * @param url the URL of the resource to load * @param postData the data will be passed to "POST" request, which must be @@ -817,7 +855,11 @@ public class WebView extends AbsoluteLayout public void postUrl(String url, byte[] postData) { checkThread(); if (DebugFlags.TRACE_API) Log.d(LOGTAG, "postUrl=" + url); - mProvider.postUrl(url, postData); + if (URLUtil.isNetworkUrl(url)) { + mProvider.postUrl(url, postData); + } else { + mProvider.loadUrl(url); + } } /** @@ -2097,10 +2139,11 @@ public class WebView extends AbsoluteLayout mProvider.getViewDelegate().onAttachedToWindow(); } + /** @hide */ @Override - protected void onDetachedFromWindow() { + protected void onDetachedFromWindowInternal() { mProvider.getViewDelegate().onDetachedFromWindow(); - super.onDetachedFromWindow(); + super.onDetachedFromWindowInternal(); } @Override diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java index b9131bf..25bcd44 100644 --- a/core/java/android/webkit/WebViewFactory.java +++ b/core/java/android/webkit/WebViewFactory.java @@ -16,9 +16,7 @@ package android.webkit; -import android.os.Build; import android.os.StrictMode; -import android.os.SystemProperties; import android.util.AndroidRuntimeException; import android.util.Log; diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 25a43a6..96a2ab5 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -36,6 +36,7 @@ import android.text.TextWatcher; import android.util.AttributeSet; import android.util.Log; import android.util.LongSparseArray; +import android.util.MathUtils; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.StateSet; @@ -59,6 +60,9 @@ import android.view.ViewTreeObserver; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; import android.view.inputmethod.BaseInputConnection; @@ -417,7 +421,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te /** * Handles scrolling between positions within the list. */ - PositionScroller mPositionScroller; + AbsPositionScroller mPositionScroller; /** * The offset in pixels form the top of the AdapterView to the top @@ -581,7 +585,13 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te /** * Helper object that renders and controls the fast scroll thumb. */ - private FastScroller mFastScroller; + private FastScroller mFastScroll; + + /** + * Temporary holder for fast scroller style until a FastScroller object + * is created. + */ + private int mFastScrollStyle; private boolean mGlobalLayoutListenerAddedFilter; @@ -693,6 +703,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te private SavedState mPendingSync; /** + * Whether the view is in the process of detaching from its window. + */ + private boolean mIsDetaching; + + /** * Interface definition for a callback to be invoked when the list or grid * has been scrolled. */ @@ -773,14 +788,18 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te this(context, attrs, com.android.internal.R.attr.absListViewStyle); } - public AbsListView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public AbsListView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public AbsListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); initAbsListView(); mOwnerThread = Thread.currentThread(); - TypedArray a = context.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.AbsListView, defStyle, 0); + final TypedArray a = context.obtainStyledAttributes( + attrs, com.android.internal.R.styleable.AbsListView, defStyleAttr, defStyleRes); Drawable d = a.getDrawable(com.android.internal.R.styleable.AbsListView_listSelector); if (d != null) { @@ -809,6 +828,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te boolean enableFastScroll = a.getBoolean(R.styleable.AbsListView_fastScrollEnabled, false); setFastScrollEnabled(enableFastScroll); + int fastScrollStyle = a.getResourceId(R.styleable.AbsListView_fastScrollStyle, 0); + setFastScrollStyle(fastScrollStyle); + boolean smoothScrollbar = a.getBoolean(R.styleable.AbsListView_smoothScrollbar, true); setSmoothScrollbarEnabled(smoothScrollbar); @@ -1238,17 +1260,31 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } private void setFastScrollerEnabledUiThread(boolean enabled) { - if (mFastScroller != null) { - mFastScroller.setEnabled(enabled); + if (mFastScroll != null) { + mFastScroll.setEnabled(enabled); } else if (enabled) { - mFastScroller = new FastScroller(this); - mFastScroller.setEnabled(true); + mFastScroll = new FastScroller(this, mFastScrollStyle); + mFastScroll.setEnabled(true); } resolvePadding(); - if (mFastScroller != null) { - mFastScroller.updateLayout(); + if (mFastScroll != null) { + mFastScroll.updateLayout(); + } + } + + /** + * Specifies the style of the fast scroller decorations. + * + * @param styleResId style resource containing fast scroller properties + * @see android.R.styleable#FastScroll + */ + public void setFastScrollStyle(int styleResId) { + if (mFastScroll == null) { + mFastScrollStyle = styleResId; + } else { + mFastScroll.setStyle(styleResId); } } @@ -1288,8 +1324,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } private void setFastScrollerAlwaysVisibleUiThread(boolean alwaysShow) { - if (mFastScroller != null) { - mFastScroller.setAlwaysShow(alwaysShow); + if (mFastScroll != null) { + mFastScroll.setAlwaysShow(alwaysShow); } } @@ -1307,17 +1343,17 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te * @see #setFastScrollAlwaysVisible(boolean) */ public boolean isFastScrollAlwaysVisible() { - if (mFastScroller == null) { + if (mFastScroll == null) { return mFastScrollEnabled && mFastScrollAlwaysVisible; } else { - return mFastScroller.isEnabled() && mFastScroller.isAlwaysShowEnabled(); + return mFastScroll.isEnabled() && mFastScroll.isAlwaysShowEnabled(); } } @Override public int getVerticalScrollbarWidth() { - if (mFastScroller != null && mFastScroller.isEnabled()) { - return Math.max(super.getVerticalScrollbarWidth(), mFastScroller.getWidth()); + if (mFastScroll != null && mFastScroll.isEnabled()) { + return Math.max(super.getVerticalScrollbarWidth(), mFastScroll.getWidth()); } return super.getVerticalScrollbarWidth(); } @@ -1330,26 +1366,26 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te */ @ViewDebug.ExportedProperty public boolean isFastScrollEnabled() { - if (mFastScroller == null) { + if (mFastScroll == null) { return mFastScrollEnabled; } else { - return mFastScroller.isEnabled(); + return mFastScroll.isEnabled(); } } @Override public void setVerticalScrollbarPosition(int position) { super.setVerticalScrollbarPosition(position); - if (mFastScroller != null) { - mFastScroller.setScrollbarPosition(position); + if (mFastScroll != null) { + mFastScroll.setScrollbarPosition(position); } } @Override public void setScrollBarStyle(int style) { super.setScrollBarStyle(style); - if (mFastScroller != null) { - mFastScroller.setScrollBarStyle(style); + if (mFastScroll != null) { + mFastScroll.setScrollBarStyle(style); } } @@ -1410,8 +1446,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te * Notify our scroll listener (if there is one) of a change in scroll state */ void invokeOnItemScrollListener() { - if (mFastScroller != null) { - mFastScroller.onScroll(mFirstPosition, getChildCount(), mItemCount); + if (mFastScroll != null) { + mFastScroll.onScroll(mFirstPosition, getChildCount(), mItemCount); } if (mOnScrollListener != null) { mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount); @@ -1460,6 +1496,21 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } } + int getSelectionModeForAccessibility() { + final int choiceMode = getChoiceMode(); + switch (choiceMode) { + case CHOICE_MODE_NONE: + return CollectionInfo.SELECTION_MODE_NONE; + case CHOICE_MODE_SINGLE: + return CollectionInfo.SELECTION_MODE_SINGLE; + case CHOICE_MODE_MULTIPLE: + case CHOICE_MODE_MULTIPLE_MODAL: + return CollectionInfo.SELECTION_MODE_MULTIPLE; + default: + return CollectionInfo.SELECTION_MODE_NONE; + } + } + @Override public boolean performAccessibilityAction(int action, Bundle arguments) { if (super.performAccessibilityAction(action, arguments)) { @@ -1576,7 +1627,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } private void useDefaultSelector() { - setSelector(getResources().getDrawable( + setSelector(getContext().getDrawable( com.android.internal.R.drawable.list_selector_background)); } @@ -2090,8 +2141,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR; // TODO: Move somewhere sane. This doesn't belong in onLayout(). - if (mFastScroller != null) { - mFastScroller.onItemCountChanged(getChildCount(), mItemCount); + if (mFastScroll != null) { + mFastScroll.onItemCountChanged(getChildCount(), mItemCount); } } @@ -2121,6 +2172,25 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te protected void layoutChildren() { } + /** + * @param focusedView view that holds accessibility focus + * @return direct child that contains accessibility focus, or null if no + * child contains accessibility focus + */ + View getAccessibilityFocusedChild(View focusedView) { + ViewParent viewParent = focusedView.getParent(); + while ((viewParent instanceof View) && (viewParent != this)) { + focusedView = (View) viewParent; + viewParent = viewParent.getParent(); + } + + if (!(viewParent instanceof View)) { + return null; + } + + return focusedView; + } + void updateScrollIndicators() { if (mScrollUp != null) { boolean canScrollUp; @@ -2242,6 +2312,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te // If we failed to re-bind the data, scrap the obtained view. if (updatedView != transientView) { + setItemViewLayoutParams(updatedView, position); mRecycler.addScrapView(updatedView, position); } } @@ -2260,12 +2331,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } else { isScrap[0] = true; - // Clear any system-managed transient state so that we can - // recycle this view and bind it to different data. - if (child.isAccessibilityFocused()) { - child.clearAccessibilityFocus(); - } - child.dispatchFinishTemporaryDetach(); } } @@ -2278,19 +2343,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); } - if (mAdapterHasStableIds) { - final ViewGroup.LayoutParams vlp = child.getLayoutParams(); - LayoutParams lp; - if (vlp == null) { - lp = (LayoutParams) generateDefaultLayoutParams(); - } else if (!checkLayoutParams(vlp)) { - lp = (LayoutParams) generateLayoutParams(vlp); - } else { - lp = (LayoutParams) vlp; - } - lp.itemId = mAdapter.getItemId(position); - child.setLayoutParams(lp); - } + setItemViewLayoutParams(child, position); if (AccessibilityManager.getInstance(mContext).isEnabled()) { if (mAccessibilityDelegate == null) { @@ -2306,6 +2359,24 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te return child; } + private void setItemViewLayoutParams(View child, int position) { + final ViewGroup.LayoutParams vlp = child.getLayoutParams(); + LayoutParams lp; + if (vlp == null) { + lp = (LayoutParams) generateDefaultLayoutParams(); + } else if (!checkLayoutParams(vlp)) { + lp = (LayoutParams) generateLayoutParams(vlp); + } else { + lp = (LayoutParams) vlp; + } + + if (mAdapterHasStableIds) { + lp.itemId = mAdapter.getItemId(position); + } + lp.viewType = mAdapter.getItemViewType(position); + child.setLayoutParams(lp); + } + class ListItemAccessibilityDelegate extends AccessibilityDelegate { @Override public AccessibilityNodeInfo createAccessibilityNodeInfo(View host) { @@ -2506,8 +2577,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te rememberSyncState(); } - if (mFastScroller != null) { - mFastScroller.onSizeChanged(w, h, oldw, oldh); + if (mFastScroll != null) { + mFastScroll.onSizeChanged(w, h, oldw, oldh); } } @@ -2566,7 +2637,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te * @attr ref android.R.styleable#AbsListView_listSelector */ public void setSelector(int resID) { - setSelector(getResources().getDrawable(resID)); + setSelector(getContext().getDrawable(resID)); } public void setSelector(Drawable sel) { @@ -2728,6 +2799,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te protected void onDetachedFromWindow() { super.onDetachedFromWindow(); + mIsDetaching = true; + // Dismiss the popup in case onSaveInstanceState() was not invoked dismissPopup(); @@ -2776,6 +2849,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te removeCallbacks(mTouchModeReset); mTouchModeReset.run(); } + + mIsDetaching = false; } @Override @@ -2836,8 +2911,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te @Override public void onRtlPropertiesChanged(int layoutDirection) { super.onRtlPropertiesChanged(layoutDirection); - if (mFastScroller != null) { - mFastScroller.setScrollbarPosition(getVerticalScrollbarPosition()); + if (mFastScroll != null) { + mFastScroll.setScrollbarPosition(getVerticalScrollbarPosition()); } } @@ -3402,7 +3477,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mPositionScroller.stop(); } - if (!isAttachedToWindow()) { + if (mIsDetaching || !isAttachedToWindow()) { // Something isn't right. // Since we rely on being attached to get data set change notifications, // don't risk doing anything where we might try to resync and find things @@ -3410,8 +3485,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te return false; } - if (mFastScroller != null) { - boolean intercepted = mFastScroller.onTouchEvent(ev); + if (mFastScroll != null) { + boolean intercepted = mFastScroll.onTouchEvent(ev); if (intercepted) { return true; } @@ -3641,7 +3716,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mTouchMode = TOUCH_MODE_REST; child.setPressed(false); setPressed(false); - if (!mDataChanged && isAttachedToWindow()) { + if (!mDataChanged && !mIsDetaching && isAttachedToWindow()) { performClick.run(); } } @@ -3900,7 +3975,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te @Override public boolean onInterceptHoverEvent(MotionEvent event) { - if (mFastScroller != null && mFastScroller.onInterceptHoverEvent(event)) { + if (mFastScroll != null && mFastScroll.onInterceptHoverEvent(event)) { return true; } @@ -3916,7 +3991,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mPositionScroller.stop(); } - if (!isAttachedToWindow()) { + if (mIsDetaching || !isAttachedToWindow()) { // Something isn't right. // Since we rely on being attached to get data set change notifications, // don't risk doing anything where we might try to resync and find things @@ -3924,7 +3999,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te return false; } - if (mFastScroller != null && mFastScroller.onInterceptTouchEvent(ev)) { + if (mFastScroll != null && mFastScroll.onInterceptTouchEvent(ev)) { return true; } @@ -4321,447 +4396,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } } - class PositionScroller implements Runnable { - private static final int SCROLL_DURATION = 200; - - private static final int MOVE_DOWN_POS = 1; - private static final int MOVE_UP_POS = 2; - private static final int MOVE_DOWN_BOUND = 3; - private static final int MOVE_UP_BOUND = 4; - private static final int MOVE_OFFSET = 5; - - private int mMode; - private int mTargetPos; - private int mBoundPos; - private int mLastSeenPos; - private int mScrollDuration; - private final int mExtraScroll; - - private int mOffsetFromTop; - - PositionScroller() { - mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength(); - } - - void start(final int position) { - stop(); - - if (mDataChanged) { - // Wait until we're back in a stable state to try this. - mPositionScrollAfterLayout = new Runnable() { - @Override public void run() { - start(position); - } - }; - return; - } - - final int childCount = getChildCount(); - if (childCount == 0) { - // Can't scroll without children. - return; - } - - final int firstPos = mFirstPosition; - final int lastPos = firstPos + childCount - 1; - - int viewTravelCount; - int clampedPosition = Math.max(0, Math.min(getCount() - 1, position)); - if (clampedPosition < firstPos) { - viewTravelCount = firstPos - clampedPosition + 1; - mMode = MOVE_UP_POS; - } else if (clampedPosition > lastPos) { - viewTravelCount = clampedPosition - lastPos + 1; - mMode = MOVE_DOWN_POS; - } else { - scrollToVisible(clampedPosition, INVALID_POSITION, SCROLL_DURATION); - return; - } - - if (viewTravelCount > 0) { - mScrollDuration = SCROLL_DURATION / viewTravelCount; - } else { - mScrollDuration = SCROLL_DURATION; - } - mTargetPos = clampedPosition; - mBoundPos = INVALID_POSITION; - mLastSeenPos = INVALID_POSITION; - - postOnAnimation(this); - } - - void start(final int position, final int boundPosition) { - stop(); - - if (boundPosition == INVALID_POSITION) { - start(position); - return; - } - - if (mDataChanged) { - // Wait until we're back in a stable state to try this. - mPositionScrollAfterLayout = new Runnable() { - @Override public void run() { - start(position, boundPosition); - } - }; - return; - } - - final int childCount = getChildCount(); - if (childCount == 0) { - // Can't scroll without children. - return; - } - - final int firstPos = mFirstPosition; - final int lastPos = firstPos + childCount - 1; - - int viewTravelCount; - int clampedPosition = Math.max(0, Math.min(getCount() - 1, position)); - if (clampedPosition < firstPos) { - final int boundPosFromLast = lastPos - boundPosition; - if (boundPosFromLast < 1) { - // Moving would shift our bound position off the screen. Abort. - return; - } - - final int posTravel = firstPos - clampedPosition + 1; - final int boundTravel = boundPosFromLast - 1; - if (boundTravel < posTravel) { - viewTravelCount = boundTravel; - mMode = MOVE_UP_BOUND; - } else { - viewTravelCount = posTravel; - mMode = MOVE_UP_POS; - } - } else if (clampedPosition > lastPos) { - final int boundPosFromFirst = boundPosition - firstPos; - if (boundPosFromFirst < 1) { - // Moving would shift our bound position off the screen. Abort. - return; - } - - final int posTravel = clampedPosition - lastPos + 1; - final int boundTravel = boundPosFromFirst - 1; - if (boundTravel < posTravel) { - viewTravelCount = boundTravel; - mMode = MOVE_DOWN_BOUND; - } else { - viewTravelCount = posTravel; - mMode = MOVE_DOWN_POS; - } - } else { - scrollToVisible(clampedPosition, boundPosition, SCROLL_DURATION); - return; - } - - if (viewTravelCount > 0) { - mScrollDuration = SCROLL_DURATION / viewTravelCount; - } else { - mScrollDuration = SCROLL_DURATION; - } - mTargetPos = clampedPosition; - mBoundPos = boundPosition; - mLastSeenPos = INVALID_POSITION; - - postOnAnimation(this); - } - - void startWithOffset(int position, int offset) { - startWithOffset(position, offset, SCROLL_DURATION); - } - - void startWithOffset(final int position, int offset, final int duration) { - stop(); - - if (mDataChanged) { - // Wait until we're back in a stable state to try this. - final int postOffset = offset; - mPositionScrollAfterLayout = new Runnable() { - @Override public void run() { - startWithOffset(position, postOffset, duration); - } - }; - return; - } - - final int childCount = getChildCount(); - if (childCount == 0) { - // Can't scroll without children. - return; - } - - offset += getPaddingTop(); - - mTargetPos = Math.max(0, Math.min(getCount() - 1, position)); - mOffsetFromTop = offset; - mBoundPos = INVALID_POSITION; - mLastSeenPos = INVALID_POSITION; - mMode = MOVE_OFFSET; - - final int firstPos = mFirstPosition; - final int lastPos = firstPos + childCount - 1; - - int viewTravelCount; - if (mTargetPos < firstPos) { - viewTravelCount = firstPos - mTargetPos; - } else if (mTargetPos > lastPos) { - viewTravelCount = mTargetPos - lastPos; - } else { - // On-screen, just scroll. - final int targetTop = getChildAt(mTargetPos - firstPos).getTop(); - smoothScrollBy(targetTop - offset, duration, true); - return; - } - - // Estimate how many screens we should travel - final float screenTravelCount = (float) viewTravelCount / childCount; - mScrollDuration = screenTravelCount < 1 ? - duration : (int) (duration / screenTravelCount); - mLastSeenPos = INVALID_POSITION; - - postOnAnimation(this); - } - - /** - * Scroll such that targetPos is in the visible padded region without scrolling - * boundPos out of view. Assumes targetPos is onscreen. - */ - void scrollToVisible(int targetPos, int boundPos, int duration) { - final int firstPos = mFirstPosition; - final int childCount = getChildCount(); - final int lastPos = firstPos + childCount - 1; - final int paddedTop = mListPadding.top; - final int paddedBottom = getHeight() - mListPadding.bottom; - - if (targetPos < firstPos || targetPos > lastPos) { - Log.w(TAG, "scrollToVisible called with targetPos " + targetPos + - " not visible [" + firstPos + ", " + lastPos + "]"); - } - if (boundPos < firstPos || boundPos > lastPos) { - // boundPos doesn't matter, it's already offscreen. - boundPos = INVALID_POSITION; - } - - final View targetChild = getChildAt(targetPos - firstPos); - final int targetTop = targetChild.getTop(); - final int targetBottom = targetChild.getBottom(); - int scrollBy = 0; - - if (targetBottom > paddedBottom) { - scrollBy = targetBottom - paddedBottom; - } - if (targetTop < paddedTop) { - scrollBy = targetTop - paddedTop; - } - - if (scrollBy == 0) { - return; - } - - if (boundPos >= 0) { - final View boundChild = getChildAt(boundPos - firstPos); - final int boundTop = boundChild.getTop(); - final int boundBottom = boundChild.getBottom(); - final int absScroll = Math.abs(scrollBy); - - if (scrollBy < 0 && boundBottom + absScroll > paddedBottom) { - // Don't scroll the bound view off the bottom of the screen. - scrollBy = Math.max(0, boundBottom - paddedBottom); - } else if (scrollBy > 0 && boundTop - absScroll < paddedTop) { - // Don't scroll the bound view off the top of the screen. - scrollBy = Math.min(0, boundTop - paddedTop); - } - } - - smoothScrollBy(scrollBy, duration); - } - - void stop() { - removeCallbacks(this); - } - - @Override - public void run() { - final int listHeight = getHeight(); - final int firstPos = mFirstPosition; - - switch (mMode) { - case MOVE_DOWN_POS: { - final int lastViewIndex = getChildCount() - 1; - final int lastPos = firstPos + lastViewIndex; - - if (lastViewIndex < 0) { - return; - } - - if (lastPos == mLastSeenPos) { - // No new views, let things keep going. - postOnAnimation(this); - return; - } - - final View lastView = getChildAt(lastViewIndex); - final int lastViewHeight = lastView.getHeight(); - final int lastViewTop = lastView.getTop(); - final int lastViewPixelsShowing = listHeight - lastViewTop; - final int extraScroll = lastPos < mItemCount - 1 ? - Math.max(mListPadding.bottom, mExtraScroll) : mListPadding.bottom; - - final int scrollBy = lastViewHeight - lastViewPixelsShowing + extraScroll; - smoothScrollBy(scrollBy, mScrollDuration, true); - - mLastSeenPos = lastPos; - if (lastPos < mTargetPos) { - postOnAnimation(this); - } - break; - } - - case MOVE_DOWN_BOUND: { - final int nextViewIndex = 1; - final int childCount = getChildCount(); - - if (firstPos == mBoundPos || childCount <= nextViewIndex - || firstPos + childCount >= mItemCount) { - return; - } - final int nextPos = firstPos + nextViewIndex; - - if (nextPos == mLastSeenPos) { - // No new views, let things keep going. - postOnAnimation(this); - return; - } - - final View nextView = getChildAt(nextViewIndex); - final int nextViewHeight = nextView.getHeight(); - final int nextViewTop = nextView.getTop(); - final int extraScroll = Math.max(mListPadding.bottom, mExtraScroll); - if (nextPos < mBoundPos) { - smoothScrollBy(Math.max(0, nextViewHeight + nextViewTop - extraScroll), - mScrollDuration, true); - - mLastSeenPos = nextPos; - - postOnAnimation(this); - } else { - if (nextViewTop > extraScroll) { - smoothScrollBy(nextViewTop - extraScroll, mScrollDuration, true); - } - } - break; - } - - case MOVE_UP_POS: { - if (firstPos == mLastSeenPos) { - // No new views, let things keep going. - postOnAnimation(this); - return; - } - - final View firstView = getChildAt(0); - if (firstView == null) { - return; - } - final int firstViewTop = firstView.getTop(); - final int extraScroll = firstPos > 0 ? - Math.max(mExtraScroll, mListPadding.top) : mListPadding.top; - - smoothScrollBy(firstViewTop - extraScroll, mScrollDuration, true); - - mLastSeenPos = firstPos; - - if (firstPos > mTargetPos) { - postOnAnimation(this); - } - break; - } - - case MOVE_UP_BOUND: { - final int lastViewIndex = getChildCount() - 2; - if (lastViewIndex < 0) { - return; - } - final int lastPos = firstPos + lastViewIndex; - - if (lastPos == mLastSeenPos) { - // No new views, let things keep going. - postOnAnimation(this); - return; - } - - final View lastView = getChildAt(lastViewIndex); - final int lastViewHeight = lastView.getHeight(); - final int lastViewTop = lastView.getTop(); - final int lastViewPixelsShowing = listHeight - lastViewTop; - final int extraScroll = Math.max(mListPadding.top, mExtraScroll); - mLastSeenPos = lastPos; - if (lastPos > mBoundPos) { - smoothScrollBy(-(lastViewPixelsShowing - extraScroll), mScrollDuration, true); - postOnAnimation(this); - } else { - final int bottom = listHeight - extraScroll; - final int lastViewBottom = lastViewTop + lastViewHeight; - if (bottom > lastViewBottom) { - smoothScrollBy(-(bottom - lastViewBottom), mScrollDuration, true); - } - } - break; - } - - case MOVE_OFFSET: { - if (mLastSeenPos == firstPos) { - // No new views, let things keep going. - postOnAnimation(this); - return; - } - - mLastSeenPos = firstPos; - - final int childCount = getChildCount(); - final int position = mTargetPos; - final int lastPos = firstPos + childCount - 1; - - int viewTravelCount = 0; - if (position < firstPos) { - viewTravelCount = firstPos - position + 1; - } else if (position > lastPos) { - viewTravelCount = position - lastPos; - } - - // Estimate how many screens we should travel - final float screenTravelCount = (float) viewTravelCount / childCount; - - final float modifier = Math.min(Math.abs(screenTravelCount), 1.f); - if (position < firstPos) { - final int distance = (int) (-getHeight() * modifier); - final int duration = (int) (mScrollDuration * modifier); - smoothScrollBy(distance, duration, true); - postOnAnimation(this); - } else if (position > lastPos) { - final int distance = (int) (getHeight() * modifier); - final int duration = (int) (mScrollDuration * modifier); - smoothScrollBy(distance, duration, true); - postOnAnimation(this); - } else { - // On-screen, just scroll. - final int targetTop = getChildAt(position - firstPos).getTop(); - final int distance = targetTop - mOffsetFromTop; - final int duration = (int) (mScrollDuration * - ((float) Math.abs(distance) / getHeight())); - smoothScrollBy(distance, duration, true); - } - break; - } - - default: - break; - } - } - } - /** * The amount of friction applied to flings. The default value * is {@link ViewConfiguration#getScrollFriction}. @@ -4784,20 +4418,27 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } /** + * Override this for better control over position scrolling. + */ + AbsPositionScroller createPositionScroller() { + return new PositionScroller(); + } + + /** * Smoothly scroll to the specified adapter position. The view will * scroll such that the indicated position is displayed. * @param position Scroll to this adapter position. */ public void smoothScrollToPosition(int position) { if (mPositionScroller == null) { - mPositionScroller = new PositionScroller(); + mPositionScroller = createPositionScroller(); } mPositionScroller.start(position); } /** * Smoothly scroll to the specified adapter position. The view will scroll - * such that the indicated position is displayed <code>offset</code> pixels from + * such that the indicated position is displayed <code>offset</code> pixels below * the top edge of the view. If this is impossible, (e.g. the offset would scroll * the first or last item beyond the boundaries of the list) it will get as close * as possible. The scroll will take <code>duration</code> milliseconds to complete. @@ -4809,14 +4450,14 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te */ public void smoothScrollToPositionFromTop(int position, int offset, int duration) { if (mPositionScroller == null) { - mPositionScroller = new PositionScroller(); + mPositionScroller = createPositionScroller(); } mPositionScroller.startWithOffset(position, offset, duration); } /** * Smoothly scroll to the specified adapter position. The view will scroll - * such that the indicated position is displayed <code>offset</code> pixels from + * such that the indicated position is displayed <code>offset</code> pixels below * the top edge of the view. If this is impossible, (e.g. the offset would scroll * the first or last item beyond the boundaries of the list) it will get as close * as possible. @@ -4827,9 +4468,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te */ public void smoothScrollToPositionFromTop(int position, int offset) { if (mPositionScroller == null) { - mPositionScroller = new PositionScroller(); + mPositionScroller = createPositionScroller(); } - mPositionScroller.startWithOffset(position, offset); + mPositionScroller.startWithOffset(position, offset, offset); } /** @@ -4837,13 +4478,14 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te * scroll such that the indicated position is displayed, but it will * stop early if scrolling further would scroll boundPosition out of * view. + * * @param position Scroll to this adapter position. * @param boundPosition Do not scroll if it would move this adapter * position out of view. */ public void smoothScrollToPosition(int position, int boundPosition) { if (mPositionScroller == null) { - mPositionScroller = new PositionScroller(); + mPositionScroller = createPositionScroller(); } mPositionScroller.start(position, boundPosition); } @@ -6285,16 +5927,16 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te @Override public void onChanged() { super.onChanged(); - if (mFastScroller != null) { - mFastScroller.onSectionsChanged(); + if (mFastScroll != null) { + mFastScroll.onSectionsChanged(); } } @Override public void onInvalidated() { super.onInvalidated(); - if (mFastScroller != null) { - mFastScroller.onSectionsChanged(); + if (mFastScroll != null) { + mFastScroll.onSectionsChanged(); } } } @@ -6555,18 +6197,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te void clear() { if (mViewTypeCount == 1) { final ArrayList<View> scrap = mCurrentScrap; - final int scrapCount = scrap.size(); - for (int i = 0; i < scrapCount; i++) { - removeDetachedView(scrap.remove(scrapCount - 1 - i), false); - } + clearScrap(scrap); } else { final int typeCount = mViewTypeCount; for (int i = 0; i < typeCount; i++) { final ArrayList<View> scrap = mScrapViews[i]; - final int scrapCount = scrap.size(); - for (int j = 0; j < scrapCount; j++) { - removeDetachedView(scrap.remove(scrapCount - 1 - j), false); - } + clearScrap(scrap); } } @@ -6667,7 +6303,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te if (mViewTypeCount == 1) { return retrieveFromScrap(mCurrentScrap, position); } else { - int whichScrap = mAdapter.getItemViewType(position); + final int whichScrap = mAdapter.getItemViewType(position); if (whichScrap >= 0 && whichScrap < mScrapViews.length) { return retrieveFromScrap(mScrapViews[whichScrap], position); } @@ -6739,13 +6375,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mScrapViews[viewType].add(scrap); } - // Clear any system-managed transient state. - if (scrap.isAccessibilityFocused()) { - scrap.clearAccessibilityFocus(); - } - - scrap.setAccessibilityDelegate(null); - if (mRecyclerListener != null) { mRecyclerListener.onMovedToScrapHeap(scrap); } @@ -6819,7 +6448,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te lp.scrappedFromPosition = mFirstActivePosition + i; scrapViews.add(victim); - victim.setAccessibilityDelegate(null); if (hasListener) { mRecyclerListener.onMovedToScrapHeap(victim); } @@ -6923,23 +6551,861 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } } } - } - static View retrieveFromScrap(ArrayList<View> scrapViews, int position) { - int size = scrapViews.size(); - if (size > 0) { - // See if we still have a view for this position. - for (int i=0; i<size; i++) { - View view = scrapViews.get(i); - if (((AbsListView.LayoutParams)view.getLayoutParams()) - .scrappedFromPosition == position) { - scrapViews.remove(i); - return view; + private View retrieveFromScrap(ArrayList<View> scrapViews, int position) { + final int size = scrapViews.size(); + if (size > 0) { + // See if we still have a view for this position or ID. + for (int i = 0; i < size; i++) { + final View view = scrapViews.get(i); + final AbsListView.LayoutParams params = + (AbsListView.LayoutParams) view.getLayoutParams(); + + if (mAdapterHasStableIds) { + final long id = mAdapter.getItemId(position); + if (id == params.itemId) { + return scrapViews.remove(i); + } + } else if (params.scrappedFromPosition == position) { + final View scrap = scrapViews.remove(i); + clearAccessibilityFromScrap(scrap); + return scrap; + } } + final View scrap = scrapViews.remove(size - 1); + clearAccessibilityFromScrap(scrap); + return scrap; + } else { + return null; } - return scrapViews.remove(size - 1); + } + + private void clearScrap(final ArrayList<View> scrap) { + final int scrapCount = scrap.size(); + for (int j = 0; j < scrapCount; j++) { + removeDetachedView(scrap.remove(scrapCount - 1 - j), false); + } + } + + private void clearAccessibilityFromScrap(View view) { + if (view.isAccessibilityFocused()) { + view.clearAccessibilityFocus(); + } + view.setAccessibilityDelegate(null); + } + + private void removeDetachedView(View child, boolean animate) { + child.setAccessibilityDelegate(null); + AbsListView.this.removeDetachedView(child, animate); + } + } + + /** + * Returns the height of the view for the specified position. + * + * @param position the item position + * @return view height in pixels + */ + int getHeightForPosition(int position) { + final int firstVisiblePosition = getFirstVisiblePosition(); + final int childCount = getChildCount(); + final int index = position - firstVisiblePosition; + if (index >= 0 && index < childCount) { + // Position is on-screen, use existing view. + final View view = getChildAt(index); + return view.getHeight(); } else { - return null; + // Position is off-screen, obtain & recycle view. + final View view = obtainView(position, mIsScrap); + view.measure(mWidthMeasureSpec, MeasureSpec.UNSPECIFIED); + final int height = view.getMeasuredHeight(); + mRecycler.addScrapView(view, position); + return height; + } + } + + /** + * Sets the selected item and positions the selection y pixels from the top edge + * of the ListView. (If in touch mode, the item will not be selected but it will + * still be positioned appropriately.) + * + * @param position Index (starting at 0) of the data item to be selected. + * @param y The distance from the top edge of the ListView (plus padding) that the + * item will be positioned. + */ + public void setSelectionFromTop(int position, int y) { + if (mAdapter == null) { + return; + } + + if (!isInTouchMode()) { + position = lookForSelectablePosition(position, true); + if (position >= 0) { + setNextSelectedPositionInt(position); + } + } else { + mResurrectToPosition = position; + } + + if (position >= 0) { + mLayoutMode = LAYOUT_SPECIFIC; + mSpecificTop = mListPadding.top + y; + + if (mNeedSync) { + mSyncPosition = position; + mSyncRowId = mAdapter.getItemId(position); + } + + if (mPositionScroller != null) { + mPositionScroller.stop(); + } + requestLayout(); + } + } + + /** + * Abstract positon scroller used to handle smooth scrolling. + */ + static abstract class AbsPositionScroller { + public abstract void start(int position); + public abstract void start(int position, int boundPosition); + public abstract void startWithOffset(int position, int offset); + public abstract void startWithOffset(int position, int offset, int duration); + public abstract void stop(); + } + + /** + * Default position scroller that simulates a fling. + */ + class PositionScroller extends AbsPositionScroller implements Runnable { + private static final int SCROLL_DURATION = 200; + + private static final int MOVE_DOWN_POS = 1; + private static final int MOVE_UP_POS = 2; + private static final int MOVE_DOWN_BOUND = 3; + private static final int MOVE_UP_BOUND = 4; + private static final int MOVE_OFFSET = 5; + + private int mMode; + private int mTargetPos; + private int mBoundPos; + private int mLastSeenPos; + private int mScrollDuration; + private final int mExtraScroll; + + private int mOffsetFromTop; + + PositionScroller() { + mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength(); + } + + @Override + public void start(final int position) { + stop(); + + if (mDataChanged) { + // Wait until we're back in a stable state to try this. + mPositionScrollAfterLayout = new Runnable() { + @Override public void run() { + start(position); + } + }; + return; + } + + final int childCount = getChildCount(); + if (childCount == 0) { + // Can't scroll without children. + return; + } + + final int firstPos = mFirstPosition; + final int lastPos = firstPos + childCount - 1; + + int viewTravelCount; + int clampedPosition = Math.max(0, Math.min(getCount() - 1, position)); + if (clampedPosition < firstPos) { + viewTravelCount = firstPos - clampedPosition + 1; + mMode = MOVE_UP_POS; + } else if (clampedPosition > lastPos) { + viewTravelCount = clampedPosition - lastPos + 1; + mMode = MOVE_DOWN_POS; + } else { + scrollToVisible(clampedPosition, INVALID_POSITION, SCROLL_DURATION); + return; + } + + if (viewTravelCount > 0) { + mScrollDuration = SCROLL_DURATION / viewTravelCount; + } else { + mScrollDuration = SCROLL_DURATION; + } + mTargetPos = clampedPosition; + mBoundPos = INVALID_POSITION; + mLastSeenPos = INVALID_POSITION; + + postOnAnimation(this); + } + + @Override + public void start(final int position, final int boundPosition) { + stop(); + + if (boundPosition == INVALID_POSITION) { + start(position); + return; + } + + if (mDataChanged) { + // Wait until we're back in a stable state to try this. + mPositionScrollAfterLayout = new Runnable() { + @Override public void run() { + start(position, boundPosition); + } + }; + return; + } + + final int childCount = getChildCount(); + if (childCount == 0) { + // Can't scroll without children. + return; + } + + final int firstPos = mFirstPosition; + final int lastPos = firstPos + childCount - 1; + + int viewTravelCount; + int clampedPosition = Math.max(0, Math.min(getCount() - 1, position)); + if (clampedPosition < firstPos) { + final int boundPosFromLast = lastPos - boundPosition; + if (boundPosFromLast < 1) { + // Moving would shift our bound position off the screen. Abort. + return; + } + + final int posTravel = firstPos - clampedPosition + 1; + final int boundTravel = boundPosFromLast - 1; + if (boundTravel < posTravel) { + viewTravelCount = boundTravel; + mMode = MOVE_UP_BOUND; + } else { + viewTravelCount = posTravel; + mMode = MOVE_UP_POS; + } + } else if (clampedPosition > lastPos) { + final int boundPosFromFirst = boundPosition - firstPos; + if (boundPosFromFirst < 1) { + // Moving would shift our bound position off the screen. Abort. + return; + } + + final int posTravel = clampedPosition - lastPos + 1; + final int boundTravel = boundPosFromFirst - 1; + if (boundTravel < posTravel) { + viewTravelCount = boundTravel; + mMode = MOVE_DOWN_BOUND; + } else { + viewTravelCount = posTravel; + mMode = MOVE_DOWN_POS; + } + } else { + scrollToVisible(clampedPosition, boundPosition, SCROLL_DURATION); + return; + } + + if (viewTravelCount > 0) { + mScrollDuration = SCROLL_DURATION / viewTravelCount; + } else { + mScrollDuration = SCROLL_DURATION; + } + mTargetPos = clampedPosition; + mBoundPos = boundPosition; + mLastSeenPos = INVALID_POSITION; + + postOnAnimation(this); + } + + @Override + public void startWithOffset(int position, int offset) { + startWithOffset(position, offset, SCROLL_DURATION); + } + + @Override + public void startWithOffset(final int position, int offset, final int duration) { + stop(); + + if (mDataChanged) { + // Wait until we're back in a stable state to try this. + final int postOffset = offset; + mPositionScrollAfterLayout = new Runnable() { + @Override public void run() { + startWithOffset(position, postOffset, duration); + } + }; + return; + } + + final int childCount = getChildCount(); + if (childCount == 0) { + // Can't scroll without children. + return; + } + + offset += getPaddingTop(); + + mTargetPos = Math.max(0, Math.min(getCount() - 1, position)); + mOffsetFromTop = offset; + mBoundPos = INVALID_POSITION; + mLastSeenPos = INVALID_POSITION; + mMode = MOVE_OFFSET; + + final int firstPos = mFirstPosition; + final int lastPos = firstPos + childCount - 1; + + int viewTravelCount; + if (mTargetPos < firstPos) { + viewTravelCount = firstPos - mTargetPos; + } else if (mTargetPos > lastPos) { + viewTravelCount = mTargetPos - lastPos; + } else { + // On-screen, just scroll. + final int targetTop = getChildAt(mTargetPos - firstPos).getTop(); + smoothScrollBy(targetTop - offset, duration, true); + return; + } + + // Estimate how many screens we should travel + final float screenTravelCount = (float) viewTravelCount / childCount; + mScrollDuration = screenTravelCount < 1 ? + duration : (int) (duration / screenTravelCount); + mLastSeenPos = INVALID_POSITION; + + postOnAnimation(this); + } + + /** + * Scroll such that targetPos is in the visible padded region without scrolling + * boundPos out of view. Assumes targetPos is onscreen. + */ + private void scrollToVisible(int targetPos, int boundPos, int duration) { + final int firstPos = mFirstPosition; + final int childCount = getChildCount(); + final int lastPos = firstPos + childCount - 1; + final int paddedTop = mListPadding.top; + final int paddedBottom = getHeight() - mListPadding.bottom; + + if (targetPos < firstPos || targetPos > lastPos) { + Log.w(TAG, "scrollToVisible called with targetPos " + targetPos + + " not visible [" + firstPos + ", " + lastPos + "]"); + } + if (boundPos < firstPos || boundPos > lastPos) { + // boundPos doesn't matter, it's already offscreen. + boundPos = INVALID_POSITION; + } + + final View targetChild = getChildAt(targetPos - firstPos); + final int targetTop = targetChild.getTop(); + final int targetBottom = targetChild.getBottom(); + int scrollBy = 0; + + if (targetBottom > paddedBottom) { + scrollBy = targetBottom - paddedBottom; + } + if (targetTop < paddedTop) { + scrollBy = targetTop - paddedTop; + } + + if (scrollBy == 0) { + return; + } + + if (boundPos >= 0) { + final View boundChild = getChildAt(boundPos - firstPos); + final int boundTop = boundChild.getTop(); + final int boundBottom = boundChild.getBottom(); + final int absScroll = Math.abs(scrollBy); + + if (scrollBy < 0 && boundBottom + absScroll > paddedBottom) { + // Don't scroll the bound view off the bottom of the screen. + scrollBy = Math.max(0, boundBottom - paddedBottom); + } else if (scrollBy > 0 && boundTop - absScroll < paddedTop) { + // Don't scroll the bound view off the top of the screen. + scrollBy = Math.min(0, boundTop - paddedTop); + } + } + + smoothScrollBy(scrollBy, duration); + } + + @Override + public void stop() { + removeCallbacks(this); + } + + @Override + public void run() { + final int listHeight = getHeight(); + final int firstPos = mFirstPosition; + + switch (mMode) { + case MOVE_DOWN_POS: { + final int lastViewIndex = getChildCount() - 1; + final int lastPos = firstPos + lastViewIndex; + + if (lastViewIndex < 0) { + return; + } + + if (lastPos == mLastSeenPos) { + // No new views, let things keep going. + postOnAnimation(this); + return; + } + + final View lastView = getChildAt(lastViewIndex); + final int lastViewHeight = lastView.getHeight(); + final int lastViewTop = lastView.getTop(); + final int lastViewPixelsShowing = listHeight - lastViewTop; + final int extraScroll = lastPos < mItemCount - 1 ? + Math.max(mListPadding.bottom, mExtraScroll) : mListPadding.bottom; + + final int scrollBy = lastViewHeight - lastViewPixelsShowing + extraScroll; + smoothScrollBy(scrollBy, mScrollDuration, true); + + mLastSeenPos = lastPos; + if (lastPos < mTargetPos) { + postOnAnimation(this); + } + break; + } + + case MOVE_DOWN_BOUND: { + final int nextViewIndex = 1; + final int childCount = getChildCount(); + + if (firstPos == mBoundPos || childCount <= nextViewIndex + || firstPos + childCount >= mItemCount) { + return; + } + final int nextPos = firstPos + nextViewIndex; + + if (nextPos == mLastSeenPos) { + // No new views, let things keep going. + postOnAnimation(this); + return; + } + + final View nextView = getChildAt(nextViewIndex); + final int nextViewHeight = nextView.getHeight(); + final int nextViewTop = nextView.getTop(); + final int extraScroll = Math.max(mListPadding.bottom, mExtraScroll); + if (nextPos < mBoundPos) { + smoothScrollBy(Math.max(0, nextViewHeight + nextViewTop - extraScroll), + mScrollDuration, true); + + mLastSeenPos = nextPos; + + postOnAnimation(this); + } else { + if (nextViewTop > extraScroll) { + smoothScrollBy(nextViewTop - extraScroll, mScrollDuration, true); + } + } + break; + } + + case MOVE_UP_POS: { + if (firstPos == mLastSeenPos) { + // No new views, let things keep going. + postOnAnimation(this); + return; + } + + final View firstView = getChildAt(0); + if (firstView == null) { + return; + } + final int firstViewTop = firstView.getTop(); + final int extraScroll = firstPos > 0 ? + Math.max(mExtraScroll, mListPadding.top) : mListPadding.top; + + smoothScrollBy(firstViewTop - extraScroll, mScrollDuration, true); + + mLastSeenPos = firstPos; + + if (firstPos > mTargetPos) { + postOnAnimation(this); + } + break; + } + + case MOVE_UP_BOUND: { + final int lastViewIndex = getChildCount() - 2; + if (lastViewIndex < 0) { + return; + } + final int lastPos = firstPos + lastViewIndex; + + if (lastPos == mLastSeenPos) { + // No new views, let things keep going. + postOnAnimation(this); + return; + } + + final View lastView = getChildAt(lastViewIndex); + final int lastViewHeight = lastView.getHeight(); + final int lastViewTop = lastView.getTop(); + final int lastViewPixelsShowing = listHeight - lastViewTop; + final int extraScroll = Math.max(mListPadding.top, mExtraScroll); + mLastSeenPos = lastPos; + if (lastPos > mBoundPos) { + smoothScrollBy(-(lastViewPixelsShowing - extraScroll), mScrollDuration, true); + postOnAnimation(this); + } else { + final int bottom = listHeight - extraScroll; + final int lastViewBottom = lastViewTop + lastViewHeight; + if (bottom > lastViewBottom) { + smoothScrollBy(-(bottom - lastViewBottom), mScrollDuration, true); + } + } + break; + } + + case MOVE_OFFSET: { + if (mLastSeenPos == firstPos) { + // No new views, let things keep going. + postOnAnimation(this); + return; + } + + mLastSeenPos = firstPos; + + final int childCount = getChildCount(); + final int position = mTargetPos; + final int lastPos = firstPos + childCount - 1; + + int viewTravelCount = 0; + if (position < firstPos) { + viewTravelCount = firstPos - position + 1; + } else if (position > lastPos) { + viewTravelCount = position - lastPos; + } + + // Estimate how many screens we should travel + final float screenTravelCount = (float) viewTravelCount / childCount; + + final float modifier = Math.min(Math.abs(screenTravelCount), 1.f); + if (position < firstPos) { + final int distance = (int) (-getHeight() * modifier); + final int duration = (int) (mScrollDuration * modifier); + smoothScrollBy(distance, duration, true); + postOnAnimation(this); + } else if (position > lastPos) { + final int distance = (int) (getHeight() * modifier); + final int duration = (int) (mScrollDuration * modifier); + smoothScrollBy(distance, duration, true); + postOnAnimation(this); + } else { + // On-screen, just scroll. + final int targetTop = getChildAt(position - firstPos).getTop(); + final int distance = targetTop - mOffsetFromTop; + final int duration = (int) (mScrollDuration * + ((float) Math.abs(distance) / getHeight())); + smoothScrollBy(distance, duration, true); + } + break; + } + + default: + break; + } + } + } + + /** + * Abstract position scroller that handles sub-position scrolling but has no + * understanding of layout. + */ + abstract class AbsSubPositionScroller extends AbsPositionScroller { + private static final int DURATION_AUTO = -1; + + private static final int DURATION_AUTO_MIN = 100; + private static final int DURATION_AUTO_MAX = 500; + + private final SubScroller mSubScroller = new SubScroller(); + + /** + * The target offset in pixels between the top of the list and the top + * of the target position. + */ + private int mOffset; + + /** + * Scroll the minimum amount to get the target view entirely on-screen. + */ + private void scrollToPosition(final int targetPosition, final boolean useOffset, + final int offset, final int boundPosition, final int duration) { + stop(); + + if (mDataChanged) { + // Wait until we're back in a stable state to try this. + mPositionScrollAfterLayout = new Runnable() { + @Override + public void run() { + scrollToPosition( + targetPosition, useOffset, offset, boundPosition, duration); + } + }; + return; + } + + if (mAdapter == null) { + // Can't scroll anywhere without an adapter. + return; + } + + final int itemCount = getCount(); + final int clampedPosition = MathUtils.constrain(targetPosition, 0, itemCount - 1); + final int clampedBoundPosition = MathUtils.constrain(boundPosition, 0, itemCount - 1); + final int firstPosition = getFirstVisiblePosition(); + final int lastPosition = firstPosition + getChildCount(); + final int targetRow = getRowForPosition(clampedPosition); + final int firstRow = getRowForPosition(firstPosition); + final int lastRow = getRowForPosition(lastPosition); + if (useOffset || targetRow <= firstRow) { + // Offset so the target row is top-aligned. + mOffset = offset; + } else if (targetRow >= lastRow - 1) { + // Offset so the target row is bottom-aligned. + final int listHeight = getHeight() - getPaddingTop() - getPaddingBottom(); + mOffset = getHeightForPosition(clampedPosition) - listHeight; + } else { + // Don't scroll, target is entirely on-screen. + return; + } + + float endSubRow = targetRow; + if (clampedBoundPosition != INVALID_POSITION) { + final int boundRow = getRowForPosition(clampedBoundPosition); + if (boundRow >= firstRow && boundRow < lastRow && boundRow != targetRow) { + endSubRow = computeBoundSubRow(targetRow, boundRow); + } + } + + final View firstChild = getChildAt(0); + if (firstChild == null) { + return; + } + + final int firstChildHeight = firstChild.getHeight(); + final float startOffsetRatio; + if (firstChildHeight == 0) { + startOffsetRatio = 0; + } else { + startOffsetRatio = -firstChild.getTop() / (float) firstChildHeight; + } + + final float startSubRow = MathUtils.constrain( + firstRow + startOffsetRatio, 0, getCount()); + if (startSubRow == endSubRow && mOffset == 0) { + // Don't scroll, target is already in position. + return; + } + + final int durationMillis; + if (duration == DURATION_AUTO) { + final float subRowDelta = Math.abs(startSubRow - endSubRow); + durationMillis = (int) MathUtils.lerp( + DURATION_AUTO_MIN, DURATION_AUTO_MAX, subRowDelta / getCount()); + } else { + durationMillis = duration; + } + + mSubScroller.startScroll(startSubRow, endSubRow, durationMillis); + + postOnAnimation(mAnimationFrame); + } + + /** + * Given a target row and offset, computes the sub-row position that + * aligns with the top of the list. If the offset is negative, the + * resulting sub-row will be smaller than the target row. + */ + private float resolveOffset(int targetRow, int offset) { + // Compute the target sub-row position by finding the actual row + // indicated by the target and offset. + int remainingOffset = offset; + int targetHeight = getHeightForRow(targetRow); + if (offset < 0) { + // Subtract row heights until we find the right row. + while (targetRow > 0 && remainingOffset < 0) { + remainingOffset += targetHeight; + targetRow--; + targetHeight = getHeightForRow(targetRow); + } + } else if (offset > 0) { + // Add row heights until we find the right row. + while (targetRow < getCount() - 1 && remainingOffset > targetHeight) { + remainingOffset -= targetHeight; + targetRow++; + targetHeight = getHeightForRow(targetRow); + } + } + + final float targetOffsetRatio; + if (remainingOffset < 0 || targetHeight == 0) { + targetOffsetRatio = 0; + } else { + targetOffsetRatio = remainingOffset / (float) targetHeight; + } + + return targetRow + targetOffsetRatio; + } + + private float computeBoundSubRow(int targetRow, int boundRow) { + final float targetSubRow = resolveOffset(targetRow, mOffset); + mOffset = 0; + + // The target row is below the bound row, so the end position would + // push the bound position above the list. Abort! + if (targetSubRow >= boundRow) { + return boundRow; + } + + // Compute the closest possible sub-position that wouldn't push the + // bound position's view further below the list. + final int listHeight = getHeight() - getPaddingTop() - getPaddingBottom(); + final int boundHeight = getHeightForRow(boundRow); + final float boundSubRow = resolveOffset(boundRow, -listHeight + boundHeight); + + return Math.max(boundSubRow, targetSubRow); + } + + @Override + public void start(int position) { + scrollToPosition(position, false, 0, INVALID_POSITION, DURATION_AUTO); + } + + @Override + public void start(int position, int boundPosition) { + scrollToPosition(position, false, 0, boundPosition, DURATION_AUTO); + } + + @Override + public void startWithOffset(int position, int offset) { + scrollToPosition(position, true, offset, INVALID_POSITION, DURATION_AUTO); + } + + @Override + public void startWithOffset(int position, int offset, int duration) { + scrollToPosition(position, true, offset, INVALID_POSITION, duration); + } + + @Override + public void stop() { + removeCallbacks(mAnimationFrame); + } + + /** + * Returns the height of a row, which is computed as the maximum height of + * the items in the row. + * + * @param row the row index + * @return row height in pixels + */ + public abstract int getHeightForRow(int row); + + /** + * Returns the row for the specified item position. + * + * @param position the item position + * @return the row index + */ + public abstract int getRowForPosition(int position); + + /** + * Returns the first item position within the specified row. + * + * @param row the row + * @return the position of the first item in the row + */ + public abstract int getFirstPositionForRow(int row); + + private void onAnimationFrame() { + final boolean shouldPost = mSubScroller.computePosition(); + final float subRow = mSubScroller.getPosition(); + + final int row = (int) subRow; + final int position = getFirstPositionForRow(row); + if (position >= getCount()) { + // Invalid position, abort scrolling. + return; + } + + final int rowHeight = getHeightForRow(row); + final int offset = (int) (rowHeight * (subRow - row)); + final int addOffset = (int) (mOffset * mSubScroller.getInterpolatedValue()); + setSelectionFromTop(position, -offset - addOffset); + + if (shouldPost) { + postOnAnimation(mAnimationFrame); + } + } + + private Runnable mAnimationFrame = new Runnable() { + @Override + public void run() { + onAnimationFrame(); + } + }; + } + + /** + * Scroller capable of returning floating point positions. + */ + static class SubScroller { + private static final Interpolator INTERPOLATOR = new AccelerateDecelerateInterpolator(); + + private float mStartPosition; + private float mEndPosition; + private long mStartTime; + private long mDuration; + + private float mPosition; + private float mInterpolatedValue; + + public void startScroll(float startPosition, float endPosition, int duration) { + mStartPosition = startPosition; + mEndPosition = endPosition; + mDuration = duration; + + mStartTime = AnimationUtils.currentAnimationTimeMillis(); + mPosition = startPosition; + mInterpolatedValue = 0; + } + + public boolean computePosition() { + final long elapsed = AnimationUtils.currentAnimationTimeMillis() - mStartTime; + final float value; + if (mDuration <= 0) { + value = 1; + } else { + value = MathUtils.constrain(elapsed / (float) mDuration, 0, 1); + } + + mInterpolatedValue = INTERPOLATOR.getInterpolation(value); + mPosition = (mEndPosition - mStartPosition) * mInterpolatedValue + mStartPosition; + + return elapsed < mDuration; + } + + public float getPosition() { + return mPosition; + } + + public float getInterpolatedValue() { + return mInterpolatedValue; } } } diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java index fe2fc96..438a9da 100644 --- a/core/java/android/widget/AbsSeekBar.java +++ b/core/java/android/widget/AbsSeekBar.java @@ -65,11 +65,15 @@ public abstract class AbsSeekBar extends ProgressBar { super(context, attrs); } - public AbsSeekBar(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public AbsSeekBar(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public AbsSeekBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); - TypedArray a = context.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.SeekBar, defStyle, 0); + TypedArray a = context.obtainStyledAttributes( + attrs, com.android.internal.R.styleable.SeekBar, defStyleAttr, defStyleRes); Drawable thumb = a.getDrawable(com.android.internal.R.styleable.SeekBar_thumb); setThumb(thumb); // will guess mThumbOffset if thumb != null... // ...but allow layout to override this diff --git a/core/java/android/widget/AbsSpinner.java b/core/java/android/widget/AbsSpinner.java index f26527f..6a4ad75 100644 --- a/core/java/android/widget/AbsSpinner.java +++ b/core/java/android/widget/AbsSpinner.java @@ -64,12 +64,16 @@ public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> { this(context, attrs, 0); } - public AbsSpinner(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public AbsSpinner(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public AbsSpinner(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); initAbsSpinner(); - TypedArray a = context.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.AbsSpinner, defStyle, 0); + final TypedArray a = context.obtainStyledAttributes( + attrs, com.android.internal.R.styleable.AbsSpinner, defStyleAttr, defStyleRes); CharSequence[] entries = a.getTextArray(R.styleable.AbsSpinner_entries); if (entries != null) { diff --git a/core/java/android/widget/AbsoluteLayout.java b/core/java/android/widget/AbsoluteLayout.java index 7df6aab..4ce0d5d 100644 --- a/core/java/android/widget/AbsoluteLayout.java +++ b/core/java/android/widget/AbsoluteLayout.java @@ -40,16 +40,19 @@ import android.widget.RemoteViews.RemoteView; @RemoteView public class AbsoluteLayout extends ViewGroup { public AbsoluteLayout(Context context) { - super(context); + this(context, null); } public AbsoluteLayout(Context context, AttributeSet attrs) { - super(context, attrs); + this(context, attrs, 0); } - public AbsoluteLayout(Context context, AttributeSet attrs, - int defStyle) { - super(context, attrs, defStyle); + public AbsoluteLayout(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public AbsoluteLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); } @Override diff --git a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java index fe1cf72..1f405a7 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java +++ b/core/java/android/widget/ActionMenuPresenter.java @@ -14,15 +14,13 @@ * limitations under the License. */ -package com.android.internal.view.menu; +package android.widget; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.os.Parcel; import android.os.Parcelable; -import android.transition.Transition; -import android.transition.TransitionManager; import android.util.SparseBooleanArray; import android.view.ActionProvider; import android.view.Gravity; @@ -32,17 +30,23 @@ import android.view.View; import android.view.View.MeasureSpec; import android.view.ViewGroup; import android.view.accessibility.AccessibilityNodeInfo; -import android.widget.ImageButton; -import android.widget.ListPopupWindow; import android.widget.ListPopupWindow.ForwardingListener; import com.android.internal.transition.ActionBarTransition; import com.android.internal.view.ActionBarPolicy; -import com.android.internal.view.menu.ActionMenuView.ActionMenuChildView; +import com.android.internal.view.menu.ActionMenuItemView; +import com.android.internal.view.menu.BaseMenuPresenter; +import com.android.internal.view.menu.MenuBuilder; +import com.android.internal.view.menu.MenuItemImpl; +import com.android.internal.view.menu.MenuPopupHelper; +import com.android.internal.view.menu.MenuView; +import com.android.internal.view.menu.SubMenuBuilder; import java.util.ArrayList; /** * MenuPresenter for building action menus as seen in the action bar and action modes. + * + * @hide */ public class ActionMenuPresenter extends BaseMenuPresenter implements ActionProvider.SubUiVisibilityListener { @@ -70,6 +74,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter private ActionButtonSubmenu mActionButtonPopup; private OpenOverflowRunnable mPostedOpenRunnable; + private ActionMenuPopupCallback mPopupCallback; final PopupPresenterCallback mPopupPresenterCallback = new PopupPresenterCallback(); int mOpenSubMenuId; @@ -173,33 +178,17 @@ public class ActionMenuPresenter extends BaseMenuPresenter } @Override - public void bindItemView(final MenuItemImpl item, MenuView.ItemView itemView) { + public void bindItemView(MenuItemImpl item, MenuView.ItemView itemView) { itemView.initialize(item, 0); final ActionMenuView menuView = (ActionMenuView) mMenuView; final ActionMenuItemView actionItemView = (ActionMenuItemView) itemView; actionItemView.setItemInvoker(menuView); - if (item.hasSubMenu()) { - actionItemView.setOnTouchListener(new ForwardingListener(actionItemView) { - @Override - public ListPopupWindow getPopup() { - return mActionButtonPopup != null ? mActionButtonPopup.getPopup() : null; - } - - @Override - protected boolean onForwardingStarted() { - return onSubMenuSelected((SubMenuBuilder) item.getSubMenu()); - } - - @Override - protected boolean onForwardingStopped() { - return dismissPopupMenus(); - } - }); - } else { - actionItemView.setOnTouchListener(null); + if (mPopupCallback == null) { + mPopupCallback = new ActionMenuPopupCallback(); } + actionItemView.setPopupCallback(mPopupCallback); } @Override @@ -553,6 +542,10 @@ public class ActionMenuPresenter extends BaseMenuPresenter } } + public void setMenuView(ActionMenuView menuView) { + mMenuView = menuView; + } + private static class SavedState implements Parcelable { public int openSubMenuId; @@ -585,7 +578,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter }; } - private class OverflowMenuButton extends ImageButton implements ActionMenuChildView { + private class OverflowMenuButton extends ImageButton implements ActionMenuView.ActionMenuChildView { public OverflowMenuButton(Context context) { super(context, null, com.android.internal.R.attr.actionOverflowButtonStyle); @@ -714,14 +707,14 @@ public class ActionMenuPresenter extends BaseMenuPresenter } } - private class PopupPresenterCallback implements MenuPresenter.Callback { + private class PopupPresenterCallback implements Callback { @Override public boolean onOpenSubMenu(MenuBuilder subMenu) { if (subMenu == null) return false; mOpenSubMenuId = ((SubMenuBuilder) subMenu).getItem().getItemId(); - final MenuPresenter.Callback cb = getCallback(); + final Callback cb = getCallback(); return cb != null ? cb.onOpenSubMenu(subMenu) : false; } @@ -730,7 +723,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter if (menu instanceof SubMenuBuilder) { ((SubMenuBuilder) menu).getRootMenu().close(false); } - final MenuPresenter.Callback cb = getCallback(); + final Callback cb = getCallback(); if (cb != null) { cb.onCloseMenu(menu, allMenusAreClosing); } @@ -753,4 +746,11 @@ public class ActionMenuPresenter extends BaseMenuPresenter mPostedOpenRunnable = null; } } + + private class ActionMenuPopupCallback extends ActionMenuItemView.PopupCallback { + @Override + public ListPopupWindow getPopup() { + return mActionButtonPopup != null ? mActionButtonPopup.getPopup() : null; + } + } } diff --git a/core/java/com/android/internal/view/menu/ActionMenuView.java b/core/java/android/widget/ActionMenuView.java index 16a2031..32c7086 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuView.java +++ b/core/java/android/widget/ActionMenuView.java @@ -13,22 +13,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.internal.view.menu; +package android.widget; import android.content.Context; import android.content.res.Configuration; -import android.content.res.TypedArray; import android.util.AttributeSet; import android.view.Gravity; +import android.view.Menu; import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; -import android.widget.LinearLayout; -import com.android.internal.R; +import com.android.internal.view.menu.ActionMenuItemView; +import com.android.internal.view.menu.MenuBuilder; +import com.android.internal.view.menu.MenuItemImpl; +import com.android.internal.view.menu.MenuView; /** - * @hide + * ActionMenuView is a presentation of a series of menu options as a View. It provides + * several top level options as action buttons while spilling remaining options over as + * items in an overflow menu. This allows applications to present packs of actions inline with + * specific or repeating content. */ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvoker, MenuView { private static final String TAG = "ActionMenuView"; @@ -44,8 +49,6 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo private int mFormatItemsWidth; private int mMinCellSize; private int mGeneratedItemPadding; - private int mMeasuredExtraWidth; - private int mMaxItemHeight; public ActionMenuView(Context context) { this(context, null); @@ -57,26 +60,13 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo final float density = context.getResources().getDisplayMetrics().density; mMinCellSize = (int) (MIN_CELL_SIZE * density); mGeneratedItemPadding = (int) (GENERATED_ITEM_PADDING * density); - - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ActionBar, - R.attr.actionBarStyle, 0); - mMaxItemHeight = a.getDimensionPixelSize(R.styleable.ActionBar_height, 0); - a.recycle(); } + /** @hide */ public void setPresenter(ActionMenuPresenter presenter) { mPresenter = presenter; } - public boolean isExpandedFormat() { - return mFormatItems; - } - - public void setMaxItemHeight(int maxItemHeight) { - mMaxItemHeight = maxItemHeight; - requestLayout(); - } - @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); @@ -129,10 +119,8 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo final int widthPadding = getPaddingLeft() + getPaddingRight(); final int heightPadding = getPaddingTop() + getPaddingBottom(); - final int itemHeightSpec = heightMode == MeasureSpec.EXACTLY - ? MeasureSpec.makeMeasureSpec(heightSize - heightPadding, MeasureSpec.EXACTLY) - : MeasureSpec.makeMeasureSpec( - Math.min(mMaxItemHeight, heightSize - heightPadding), MeasureSpec.AT_MOST); + final int itemHeightSpec = getChildMeasureSpec(heightMeasureSpec, heightPadding, + ViewGroup.LayoutParams.WRAP_CONTENT); widthSize -= widthPadding; @@ -333,7 +321,6 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo } setMeasuredDimension(widthSize, heightSize); - mMeasuredExtraWidth = cellsRemaining * cellSize; } /** @@ -496,10 +483,12 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo mPresenter.dismissPopupMenus(); } + /** @hide */ public boolean isOverflowReserved() { return mReserveOverflow; } - + + /** @hide */ public void setOverflowReserved(boolean reserveOverflow) { mReserveOverflow = reserveOverflow; } @@ -536,24 +525,51 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo return p != null && p instanceof LayoutParams; } + /** @hide */ public LayoutParams generateOverflowButtonLayoutParams() { LayoutParams result = generateDefaultLayoutParams(); result.isOverflowButton = true; return result; } + /** @hide */ public boolean invokeItem(MenuItemImpl item) { return mMenu.performItemAction(item, 0); } + /** @hide */ public int getWindowAnimations() { return 0; } + /** @hide */ public void initialize(MenuBuilder menu) { mMenu = menu; } + /** + * Returns the Menu object that this ActionMenuView is currently presenting. + * + * <p>Applications should use this method to obtain the ActionMenuView's Menu object + * and inflate or add content to it as necessary.</p> + * + * @return the Menu presented by this view + */ + public Menu getMenu() { + if (mMenu == null) { + final Context context = getContext(); + mMenu = new MenuBuilder(context); + mPresenter = new ActionMenuPresenter(context); + mPresenter.initForMenu(context, mMenu); + mPresenter.setMenuView(this); + } + + return mMenu; + } + + /** + * @hide Private LinearLayout (superclass) API. Un-hide if LinearLayout API is made public. + */ @Override protected boolean hasDividerBeforeChildAt(int childIndex) { if (childIndex == 0) { @@ -575,23 +591,34 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo return false; } + /** @hide */ public interface ActionMenuChildView { public boolean needsDividerBefore(); public boolean needsDividerAfter(); } public static class LayoutParams extends LinearLayout.LayoutParams { + /** @hide */ @ViewDebug.ExportedProperty(category = "layout") public boolean isOverflowButton; + + /** @hide */ @ViewDebug.ExportedProperty(category = "layout") public int cellsUsed; + + /** @hide */ @ViewDebug.ExportedProperty(category = "layout") public int extraPixels; + + /** @hide */ @ViewDebug.ExportedProperty(category = "layout") public boolean expandable; + + /** @hide */ @ViewDebug.ExportedProperty(category = "layout") public boolean preventEdgeOffset; + /** @hide */ public boolean expanded; public LayoutParams(Context c, AttributeSet attrs) { @@ -612,6 +639,7 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo isOverflowButton = false; } + /** @hide */ public LayoutParams(int width, int height, boolean isOverflowButton) { super(width, height); this.isOverflowButton = isOverflowButton; diff --git a/core/java/android/widget/ActivityChooserView.java b/core/java/android/widget/ActivityChooserView.java index 8612964..f9af2f9 100644 --- a/core/java/android/widget/ActivityChooserView.java +++ b/core/java/android/widget/ActivityChooserView.java @@ -18,7 +18,6 @@ package android.widget; import com.android.internal.R; -import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -31,7 +30,6 @@ import android.util.AttributeSet; import android.util.Log; import android.view.ActionProvider; import android.view.LayoutInflater; -import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; @@ -204,13 +202,32 @@ public class ActivityChooserView extends ViewGroup implements ActivityChooserMod * * @param context The application environment. * @param attrs A collection of attributes. - * @param defStyle The default style to apply to this view. + * @param defStyleAttr An attribute in the current theme that contains a + * reference to a style resource that supplies default values for + * the view. Can be 0 to not look for defaults. */ - public ActivityChooserView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public ActivityChooserView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + /** + * Create a new instance. + * + * @param context The application environment. + * @param attrs A collection of attributes. + * @param defStyleAttr An attribute in the current theme that contains a + * reference to a style resource that supplies default values for + * the view. Can be 0 to not look for defaults. + * @param defStyleRes A resource identifier of a style resource that + * supplies default values for the view, used only if + * defStyleAttr is 0 or can not be found in the theme. Can be 0 + * to not look for defaults. + */ + public ActivityChooserView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); TypedArray attributesArray = context.obtainStyledAttributes(attrs, - R.styleable.ActivityChooserView, defStyle, 0); + R.styleable.ActivityChooserView, defStyleAttr, defStyleRes); mInitialActivityCount = attributesArray.getInt( R.styleable.ActivityChooserView_initialActivityCount, diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java index a06344f..1da22ca 100644 --- a/core/java/android/widget/AdapterView.java +++ b/core/java/android/widget/AdapterView.java @@ -223,15 +223,19 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { boolean mBlockLayoutRequests = false; public AdapterView(Context context) { - super(context); + this(context, null); } public AdapterView(Context context, AttributeSet attrs) { - super(context, attrs); + this(context, attrs, 0); } - public AdapterView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public AdapterView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public AdapterView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); // If not explicitly specified this view is important for accessibility. if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java index 90e949a..1bc2f4b 100644 --- a/core/java/android/widget/AdapterViewAnimator.java +++ b/core/java/android/widget/AdapterViewAnimator.java @@ -173,10 +173,15 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter> } public AdapterViewAnimator(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); + this(context, attrs, defStyleAttr, 0); + } + + public AdapterViewAnimator( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); - TypedArray a = context.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.AdapterViewAnimator, defStyleAttr, 0); + final TypedArray a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.AdapterViewAnimator, defStyleAttr, defStyleRes); int resource = a.getResourceId( com.android.internal.R.styleable.AdapterViewAnimator_inAnimation, 0); if (resource > 0) { diff --git a/core/java/android/widget/AdapterViewFlipper.java b/core/java/android/widget/AdapterViewFlipper.java index aea029b..3b026bd 100644 --- a/core/java/android/widget/AdapterViewFlipper.java +++ b/core/java/android/widget/AdapterViewFlipper.java @@ -59,10 +59,19 @@ public class AdapterViewFlipper extends AdapterViewAnimator { } public AdapterViewFlipper(Context context, AttributeSet attrs) { - super(context, attrs); + this(context, attrs, 0); + } + + public AdapterViewFlipper(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public AdapterViewFlipper( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); - TypedArray a = context.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.AdapterViewFlipper); + final TypedArray a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.AdapterViewFlipper, defStyleAttr, defStyleRes); mFlipInterval = a.getInt( com.android.internal.R.styleable.AdapterViewFlipper_flipInterval, DEFAULT_INTERVAL); mAutoStart = a.getBoolean( diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java index c7da818..5b80648 100644 --- a/core/java/android/widget/AnalogClock.java +++ b/core/java/android/widget/AnalogClock.java @@ -67,27 +67,30 @@ public class AnalogClock extends View { this(context, attrs, 0); } - public AnalogClock(Context context, AttributeSet attrs, - int defStyle) { - super(context, attrs, defStyle); - Resources r = mContext.getResources(); - TypedArray a = - context.obtainStyledAttributes( - attrs, com.android.internal.R.styleable.AnalogClock, defStyle, 0); + public AnalogClock(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public AnalogClock(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + final Resources r = context.getResources(); + final TypedArray a = context.obtainStyledAttributes( + attrs, com.android.internal.R.styleable.AnalogClock, defStyleAttr, defStyleRes); mDial = a.getDrawable(com.android.internal.R.styleable.AnalogClock_dial); if (mDial == null) { - mDial = r.getDrawable(com.android.internal.R.drawable.clock_dial); + mDial = context.getDrawable(com.android.internal.R.drawable.clock_dial); } mHourHand = a.getDrawable(com.android.internal.R.styleable.AnalogClock_hand_hour); if (mHourHand == null) { - mHourHand = r.getDrawable(com.android.internal.R.drawable.clock_hand_hour); + mHourHand = context.getDrawable(com.android.internal.R.drawable.clock_hand_hour); } mMinuteHand = a.getDrawable(com.android.internal.R.styleable.AnalogClock_hand_minute); if (mMinuteHand == null) { - mMinuteHand = r.getDrawable(com.android.internal.R.drawable.clock_hand_minute); + mMinuteHand = context.getDrawable(com.android.internal.R.drawable.clock_hand_minute); } mCalendar = new Time(); diff --git a/core/java/android/widget/AppSecurityPermissions.java b/core/java/android/widget/AppSecurityPermissions.java index 34cfea5..10e56c7 100644 --- a/core/java/android/widget/AppSecurityPermissions.java +++ b/core/java/android/widget/AppSecurityPermissions.java @@ -322,7 +322,7 @@ public class AppSecurityPermissions { CharSequence grpName, CharSequence description, boolean dangerous) { LayoutInflater inflater = (LayoutInflater)context.getSystemService( Context.LAYOUT_INFLATER_SERVICE); - Drawable icon = context.getResources().getDrawable(dangerous + Drawable icon = context.getDrawable(dangerous ? R.drawable.ic_bullet_key_permission : R.drawable.ic_text_dot); return getPermissionItemViewOld(context, inflater, grpName, description, dangerous, icon); diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java index f0eb94f..eb232fd 100644 --- a/core/java/android/widget/AutoCompleteTextView.java +++ b/core/java/android/widget/AutoCompleteTextView.java @@ -133,17 +133,21 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe this(context, attrs, com.android.internal.R.attr.autoCompleteTextViewStyle); } - public AutoCompleteTextView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public AutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public AutoCompleteTextView( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); mPopup = new ListPopupWindow(context, attrs, com.android.internal.R.attr.autoCompleteTextViewStyle); mPopup.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); mPopup.setPromptPosition(ListPopupWindow.POSITION_PROMPT_BELOW); - TypedArray a = - context.obtainStyledAttributes( - attrs, com.android.internal.R.styleable.AutoCompleteTextView, defStyle, 0); + final TypedArray a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.AutoCompleteTextView, defStyleAttr, defStyleRes); mThreshold = a.getInt( R.styleable.AutoCompleteTextView_completionThreshold, 2); @@ -362,7 +366,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe * @attr ref android.R.styleable#PopupWindow_popupBackground */ public void setDropDownBackgroundResource(int id) { - mPopup.setBackgroundDrawable(getResources().getDrawable(id)); + mPopup.setBackgroundDrawable(getContext().getDrawable(id)); } /** diff --git a/core/java/android/widget/Button.java b/core/java/android/widget/Button.java index 2ac56ac..1663620 100644 --- a/core/java/android/widget/Button.java +++ b/core/java/android/widget/Button.java @@ -103,8 +103,12 @@ public class Button extends TextView { this(context, attrs, com.android.internal.R.attr.buttonStyle); } - public Button(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public Button(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public Button(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); } @Override diff --git a/core/java/android/widget/CalendarView.java b/core/java/android/widget/CalendarView.java index e90b460..ea60abb 100644 --- a/core/java/android/widget/CalendarView.java +++ b/core/java/android/widget/CalendarView.java @@ -80,234 +80,7 @@ public class CalendarView extends FrameLayout { */ private static final String LOG_TAG = CalendarView.class.getSimpleName(); - /** - * Default value whether to show week number. - */ - private static final boolean DEFAULT_SHOW_WEEK_NUMBER = true; - - /** - * The number of milliseconds in a day.e - */ - private static final long MILLIS_IN_DAY = 86400000L; - - /** - * The number of day in a week. - */ - private static final int DAYS_PER_WEEK = 7; - - /** - * The number of milliseconds in a week. - */ - private static final long MILLIS_IN_WEEK = DAYS_PER_WEEK * MILLIS_IN_DAY; - - /** - * Affects when the month selection will change while scrolling upe - */ - private static final int SCROLL_HYST_WEEKS = 2; - - /** - * How long the GoTo fling animation should last. - */ - private static final int GOTO_SCROLL_DURATION = 1000; - - /** - * The duration of the adjustment upon a user scroll in milliseconds. - */ - private static final int ADJUSTMENT_SCROLL_DURATION = 500; - - /** - * How long to wait after receiving an onScrollStateChanged notification - * before acting on it. - */ - private static final int SCROLL_CHANGE_DELAY = 40; - - /** - * String for parsing dates. - */ - private static final String DATE_FORMAT = "MM/dd/yyyy"; - - /** - * The default minimal date. - */ - private static final String DEFAULT_MIN_DATE = "01/01/1900"; - - /** - * The default maximal date. - */ - private static final String DEFAULT_MAX_DATE = "01/01/2100"; - - private static final int DEFAULT_SHOWN_WEEK_COUNT = 6; - - private static final int DEFAULT_DATE_TEXT_SIZE = 14; - - private static final int UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH = 6; - - private static final int UNSCALED_WEEK_MIN_VISIBLE_HEIGHT = 12; - - private static final int UNSCALED_LIST_SCROLL_TOP_OFFSET = 2; - - private static final int UNSCALED_BOTTOM_BUFFER = 20; - - private static final int UNSCALED_WEEK_SEPARATOR_LINE_WIDTH = 1; - - private static final int DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID = -1; - - private final int mWeekSeperatorLineWidth; - - private int mDateTextSize; - - private Drawable mSelectedDateVerticalBar; - - private final int mSelectedDateVerticalBarWidth; - - private int mSelectedWeekBackgroundColor; - - private int mFocusedMonthDateColor; - - private int mUnfocusedMonthDateColor; - - private int mWeekSeparatorLineColor; - - private int mWeekNumberColor; - - private int mWeekDayTextAppearanceResId; - - private int mDateTextAppearanceResId; - - /** - * The top offset of the weeks list. - */ - private int mListScrollTopOffset = 2; - - /** - * The visible height of a week view. - */ - private int mWeekMinVisibleHeight = 12; - - /** - * The visible height of a week view. - */ - private int mBottomBuffer = 20; - - /** - * The number of shown weeks. - */ - private int mShownWeekCount; - - /** - * Flag whether to show the week number. - */ - private boolean mShowWeekNumber; - - /** - * The number of day per week to be shown. - */ - private int mDaysPerWeek = 7; - - /** - * The friction of the week list while flinging. - */ - private float mFriction = .05f; - - /** - * Scale for adjusting velocity of the week list while flinging. - */ - private float mVelocityScale = 0.333f; - - /** - * The adapter for the weeks list. - */ - private WeeksAdapter mAdapter; - - /** - * The weeks list. - */ - private ListView mListView; - - /** - * The name of the month to display. - */ - private TextView mMonthName; - - /** - * The header with week day names. - */ - private ViewGroup mDayNamesHeader; - - /** - * Cached labels for the week names header. - */ - private String[] mDayLabels; - - /** - * The first day of the week. - */ - private int mFirstDayOfWeek; - - /** - * Which month should be displayed/highlighted [0-11]. - */ - private int mCurrentMonthDisplayed = -1; - - /** - * Used for tracking during a scroll. - */ - private long mPreviousScrollPosition; - - /** - * Used for tracking which direction the view is scrolling. - */ - private boolean mIsScrollingUp = false; - - /** - * The previous scroll state of the weeks ListView. - */ - private int mPreviousScrollState = OnScrollListener.SCROLL_STATE_IDLE; - - /** - * The current scroll state of the weeks ListView. - */ - private int mCurrentScrollState = OnScrollListener.SCROLL_STATE_IDLE; - - /** - * Listener for changes in the selected day. - */ - private OnDateChangeListener mOnDateChangeListener; - - /** - * Command for adjusting the position after a scroll/fling. - */ - private ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable(); - - /** - * Temporary instance to avoid multiple instantiations. - */ - private Calendar mTempDate; - - /** - * The first day of the focused month. - */ - private Calendar mFirstDayOfMonth; - - /** - * The start date of the range supported by this picker. - */ - private Calendar mMinDate; - - /** - * The end date of the range supported by this picker. - */ - private Calendar mMaxDate; - - /** - * Date format for parsing dates. - */ - private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT); - - /** - * The current locale. - */ - private Locale mCurrentLocale; + private CalendarViewDelegate mDelegate; /** * The callback used to indicate the user changes the date. @@ -330,91 +103,17 @@ public class CalendarView extends FrameLayout { } public CalendarView(Context context, AttributeSet attrs) { - this(context, attrs, 0); + this(context, attrs, R.attr.calendarViewStyle); } - public CalendarView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, 0); - - // initialization based on locale - setCurrentLocale(Locale.getDefault()); - - TypedArray attributesArray = context.obtainStyledAttributes(attrs, R.styleable.CalendarView, - R.attr.calendarViewStyle, 0); - mShowWeekNumber = attributesArray.getBoolean(R.styleable.CalendarView_showWeekNumber, - DEFAULT_SHOW_WEEK_NUMBER); - mFirstDayOfWeek = attributesArray.getInt(R.styleable.CalendarView_firstDayOfWeek, - LocaleData.get(Locale.getDefault()).firstDayOfWeek); - String minDate = attributesArray.getString(R.styleable.CalendarView_minDate); - if (TextUtils.isEmpty(minDate) || !parseDate(minDate, mMinDate)) { - parseDate(DEFAULT_MIN_DATE, mMinDate); - } - String maxDate = attributesArray.getString(R.styleable.CalendarView_maxDate); - if (TextUtils.isEmpty(maxDate) || !parseDate(maxDate, mMaxDate)) { - parseDate(DEFAULT_MAX_DATE, mMaxDate); - } - if (mMaxDate.before(mMinDate)) { - throw new IllegalArgumentException("Max date cannot be before min date."); - } - mShownWeekCount = attributesArray.getInt(R.styleable.CalendarView_shownWeekCount, - DEFAULT_SHOWN_WEEK_COUNT); - mSelectedWeekBackgroundColor = attributesArray.getColor( - R.styleable.CalendarView_selectedWeekBackgroundColor, 0); - mFocusedMonthDateColor = attributesArray.getColor( - R.styleable.CalendarView_focusedMonthDateColor, 0); - mUnfocusedMonthDateColor = attributesArray.getColor( - R.styleable.CalendarView_unfocusedMonthDateColor, 0); - mWeekSeparatorLineColor = attributesArray.getColor( - R.styleable.CalendarView_weekSeparatorLineColor, 0); - mWeekNumberColor = attributesArray.getColor(R.styleable.CalendarView_weekNumberColor, 0); - mSelectedDateVerticalBar = attributesArray.getDrawable( - R.styleable.CalendarView_selectedDateVerticalBar); - - mDateTextAppearanceResId = attributesArray.getResourceId( - R.styleable.CalendarView_dateTextAppearance, R.style.TextAppearance_Small); - updateDateTextSize(); - - mWeekDayTextAppearanceResId = attributesArray.getResourceId( - R.styleable.CalendarView_weekDayTextAppearance, - DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID); - attributesArray.recycle(); - - DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); - mWeekMinVisibleHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - UNSCALED_WEEK_MIN_VISIBLE_HEIGHT, displayMetrics); - mListScrollTopOffset = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - UNSCALED_LIST_SCROLL_TOP_OFFSET, displayMetrics); - mBottomBuffer = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - UNSCALED_BOTTOM_BUFFER, displayMetrics); - mSelectedDateVerticalBarWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH, displayMetrics); - mWeekSeperatorLineWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - UNSCALED_WEEK_SEPARATOR_LINE_WIDTH, displayMetrics); - - LayoutInflater layoutInflater = (LayoutInflater) context - .getSystemService(Service.LAYOUT_INFLATER_SERVICE); - View content = layoutInflater.inflate(R.layout.calendar_view, null, false); - addView(content); - - mListView = (ListView) findViewById(R.id.list); - mDayNamesHeader = (ViewGroup) content.findViewById(com.android.internal.R.id.day_names); - mMonthName = (TextView) content.findViewById(com.android.internal.R.id.month_name); - - setUpHeader(); - setUpListView(); - setUpAdapter(); - - // go to today or whichever is close to today min or max date - mTempDate.setTimeInMillis(System.currentTimeMillis()); - if (mTempDate.before(mMinDate)) { - goTo(mMinDate, false, true, true); - } else if (mMaxDate.before(mTempDate)) { - goTo(mMaxDate, false, true, true); - } else { - goTo(mTempDate, false, true, true); - } + public CalendarView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } - invalidate(); + public CalendarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + mDelegate = new LegacyCalendarViewDelegate(this, context, attrs, defStyleAttr, defStyleRes); } /** @@ -425,10 +124,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_shownWeekCount */ public void setShownWeekCount(int count) { - if (mShownWeekCount != count) { - mShownWeekCount = count; - invalidate(); - } + mDelegate.setShownWeekCount(count); } /** @@ -439,7 +135,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_shownWeekCount */ public int getShownWeekCount() { - return mShownWeekCount; + return mDelegate.getShownWeekCount(); } /** @@ -450,16 +146,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_selectedWeekBackgroundColor */ public void setSelectedWeekBackgroundColor(int color) { - if (mSelectedWeekBackgroundColor != color) { - mSelectedWeekBackgroundColor = color; - final int childCount = mListView.getChildCount(); - for (int i = 0; i < childCount; i++) { - WeekView weekView = (WeekView) mListView.getChildAt(i); - if (weekView.mHasSelectedDay) { - weekView.invalidate(); - } - } - } + mDelegate.setSelectedWeekBackgroundColor(color); } /** @@ -470,7 +157,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_selectedWeekBackgroundColor */ public int getSelectedWeekBackgroundColor() { - return mSelectedWeekBackgroundColor; + return mDelegate.getSelectedWeekBackgroundColor(); } /** @@ -481,16 +168,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_focusedMonthDateColor */ public void setFocusedMonthDateColor(int color) { - if (mFocusedMonthDateColor != color) { - mFocusedMonthDateColor = color; - final int childCount = mListView.getChildCount(); - for (int i = 0; i < childCount; i++) { - WeekView weekView = (WeekView) mListView.getChildAt(i); - if (weekView.mHasFocusedDay) { - weekView.invalidate(); - } - } - } + mDelegate.setFocusedMonthDateColor(color); } /** @@ -501,7 +179,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_focusedMonthDateColor */ public int getFocusedMonthDateColor() { - return mFocusedMonthDateColor; + return mDelegate.getFocusedMonthDateColor(); } /** @@ -512,16 +190,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_unfocusedMonthDateColor */ public void setUnfocusedMonthDateColor(int color) { - if (mUnfocusedMonthDateColor != color) { - mUnfocusedMonthDateColor = color; - final int childCount = mListView.getChildCount(); - for (int i = 0; i < childCount; i++) { - WeekView weekView = (WeekView) mListView.getChildAt(i); - if (weekView.mHasUnfocusedDay) { - weekView.invalidate(); - } - } - } + mDelegate.setUnfocusedMonthDateColor(color); } /** @@ -532,7 +201,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_unfocusedMonthDateColor */ public int getUnfocusedMonthDateColor() { - return mFocusedMonthDateColor; + return mDelegate.getUnfocusedMonthDateColor(); } /** @@ -543,12 +212,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_weekNumberColor */ public void setWeekNumberColor(int color) { - if (mWeekNumberColor != color) { - mWeekNumberColor = color; - if (mShowWeekNumber) { - invalidateAllWeekViews(); - } - } + mDelegate.setWeekNumberColor(color); } /** @@ -559,7 +223,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_weekNumberColor */ public int getWeekNumberColor() { - return mWeekNumberColor; + return mDelegate.getWeekNumberColor(); } /** @@ -570,10 +234,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_weekSeparatorLineColor */ public void setWeekSeparatorLineColor(int color) { - if (mWeekSeparatorLineColor != color) { - mWeekSeparatorLineColor = color; - invalidateAllWeekViews(); - } + mDelegate.setWeekSeparatorLineColor(color); } /** @@ -584,7 +245,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_weekSeparatorLineColor */ public int getWeekSeparatorLineColor() { - return mWeekSeparatorLineColor; + return mDelegate.getWeekSeparatorLineColor(); } /** @@ -596,8 +257,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_selectedDateVerticalBar */ public void setSelectedDateVerticalBar(int resourceId) { - Drawable drawable = getResources().getDrawable(resourceId); - setSelectedDateVerticalBar(drawable); + mDelegate.setSelectedDateVerticalBar(resourceId); } /** @@ -609,16 +269,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_selectedDateVerticalBar */ public void setSelectedDateVerticalBar(Drawable drawable) { - if (mSelectedDateVerticalBar != drawable) { - mSelectedDateVerticalBar = drawable; - final int childCount = mListView.getChildCount(); - for (int i = 0; i < childCount; i++) { - WeekView weekView = (WeekView) mListView.getChildAt(i); - if (weekView.mHasSelectedDay) { - weekView.invalidate(); - } - } - } + mDelegate.setSelectedDateVerticalBar(drawable); } /** @@ -628,7 +279,7 @@ public class CalendarView extends FrameLayout { * @return The vertical bar drawable. */ public Drawable getSelectedDateVerticalBar() { - return mSelectedDateVerticalBar; + return mDelegate.getSelectedDateVerticalBar(); } /** @@ -639,10 +290,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_weekDayTextAppearance */ public void setWeekDayTextAppearance(int resourceId) { - if (mWeekDayTextAppearanceResId != resourceId) { - mWeekDayTextAppearanceResId = resourceId; - setUpHeader(); - } + mDelegate.setWeekDayTextAppearance(resourceId); } /** @@ -653,7 +301,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_weekDayTextAppearance */ public int getWeekDayTextAppearance() { - return mWeekDayTextAppearanceResId; + return mDelegate.getWeekDayTextAppearance(); } /** @@ -664,11 +312,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_dateTextAppearance */ public void setDateTextAppearance(int resourceId) { - if (mDateTextAppearanceResId != resourceId) { - mDateTextAppearanceResId = resourceId; - updateDateTextSize(); - invalidateAllWeekViews(); - } + mDelegate.setDateTextAppearance(resourceId); } /** @@ -679,35 +323,17 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_dateTextAppearance */ public int getDateTextAppearance() { - return mDateTextAppearanceResId; + return mDelegate.getDateTextAppearance(); } @Override public void setEnabled(boolean enabled) { - mListView.setEnabled(enabled); + mDelegate.setEnabled(enabled); } @Override public boolean isEnabled() { - return mListView.isEnabled(); - } - - @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - setCurrentLocale(newConfig.locale); - } - - @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); - event.setClassName(CalendarView.class.getName()); - } - - @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); - info.setClassName(CalendarView.class.getName()); + return mDelegate.isEnabled(); } /** @@ -723,7 +349,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_minDate */ public long getMinDate() { - return mMinDate.getTimeInMillis(); + return mDelegate.getMinDate(); } /** @@ -736,30 +362,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_minDate */ public void setMinDate(long minDate) { - mTempDate.setTimeInMillis(minDate); - if (isSameDate(mTempDate, mMinDate)) { - return; - } - mMinDate.setTimeInMillis(minDate); - // make sure the current date is not earlier than - // the new min date since the latter is used for - // calculating the indices in the adapter thus - // avoiding out of bounds error - Calendar date = mAdapter.mSelectedDate; - if (date.before(mMinDate)) { - mAdapter.setSelectedDay(mMinDate); - } - // reinitialize the adapter since its range depends on min date - mAdapter.init(); - if (date.before(mMinDate)) { - setDate(mTempDate.getTimeInMillis()); - } else { - // we go to the current date to force the ListView to query its - // adapter for the shown views since we have changed the adapter - // range and the base from which the later calculates item indices - // note that calling setDate will not work since the date is the same - goTo(date, false, true, false); - } + mDelegate.setMinDate(minDate); } /** @@ -775,7 +378,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_maxDate */ public long getMaxDate() { - return mMaxDate.getTimeInMillis(); + return mDelegate.getMaxDate(); } /** @@ -788,23 +391,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_maxDate */ public void setMaxDate(long maxDate) { - mTempDate.setTimeInMillis(maxDate); - if (isSameDate(mTempDate, mMaxDate)) { - return; - } - mMaxDate.setTimeInMillis(maxDate); - // reinitialize the adapter since its range depends on max date - mAdapter.init(); - Calendar date = mAdapter.mSelectedDate; - if (date.after(mMaxDate)) { - setDate(mMaxDate.getTimeInMillis()); - } else { - // we go to the current date to force the ListView to query its - // adapter for the shown views since we have changed the adapter - // range and the base from which the later calculates item indices - // note that calling setDate will not work since the date is the same - goTo(date, false, true, false); - } + mDelegate.setMaxDate(maxDate); } /** @@ -815,12 +402,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_showWeekNumber */ public void setShowWeekNumber(boolean showWeekNumber) { - if (mShowWeekNumber == showWeekNumber) { - return; - } - mShowWeekNumber = showWeekNumber; - mAdapter.notifyDataSetChanged(); - setUpHeader(); + mDelegate.setShowWeekNumber(showWeekNumber); } /** @@ -831,7 +413,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_showWeekNumber */ public boolean getShowWeekNumber() { - return mShowWeekNumber; + return mDelegate.getShowWeekNumber(); } /** @@ -850,7 +432,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_firstDayOfWeek */ public int getFirstDayOfWeek() { - return mFirstDayOfWeek; + return mDelegate.getFirstDayOfWeek(); } /** @@ -869,12 +451,7 @@ public class CalendarView extends FrameLayout { * @attr ref android.R.styleable#CalendarView_firstDayOfWeek */ public void setFirstDayOfWeek(int firstDayOfWeek) { - if (mFirstDayOfWeek == firstDayOfWeek) { - return; - } - mFirstDayOfWeek = firstDayOfWeek; - mAdapter.init(); - setUpHeader(); + mDelegate.setFirstDayOfWeek(firstDayOfWeek); } /** @@ -883,7 +460,7 @@ public class CalendarView extends FrameLayout { * @param listener The listener to be notified. */ public void setOnDateChangeListener(OnDateChangeListener listener) { - mOnDateChangeListener = listener; + mDelegate.setOnDateChangeListener(listener); } /** @@ -893,7 +470,7 @@ public class CalendarView extends FrameLayout { * @return The selected date. */ public long getDate() { - return mAdapter.mSelectedDate.getTimeInMillis(); + return mDelegate.getDate(); } /** @@ -910,7 +487,7 @@ public class CalendarView extends FrameLayout { * @see #setMaxDate(long) */ public void setDate(long date) { - setDate(date, false, false); + mDelegate.setDate(date); } /** @@ -928,937 +505,1648 @@ public class CalendarView extends FrameLayout { * @see #setMaxDate(long) */ public void setDate(long date, boolean animate, boolean center) { - mTempDate.setTimeInMillis(date); - if (isSameDate(mTempDate, mAdapter.mSelectedDate)) { - return; - } - goTo(mTempDate, animate, true, center); + mDelegate.setDate(date, animate, center); } - private void updateDateTextSize() { - TypedArray dateTextAppearance = mContext.obtainStyledAttributes( - mDateTextAppearanceResId, R.styleable.TextAppearance); - mDateTextSize = dateTextAppearance.getDimensionPixelSize( - R.styleable.TextAppearance_textSize, DEFAULT_DATE_TEXT_SIZE); - dateTextAppearance.recycle(); + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + mDelegate.onConfigurationChanged(newConfig); } - /** - * Invalidates all week views. - */ - private void invalidateAllWeekViews() { - final int childCount = mListView.getChildCount(); - for (int i = 0; i < childCount; i++) { - View view = mListView.getChildAt(i); - view.invalidate(); - } + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + mDelegate.onInitializeAccessibilityEvent(event); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + mDelegate.onInitializeAccessibilityNodeInfo(info); } /** - * Sets the current locale. - * - * @param locale The current locale. + * A delegate interface that defined the public API of the CalendarView. Allows different + * CalendarView implementations. This would need to be implemented by the CalendarView delegates + * for the real behavior. */ - private void setCurrentLocale(Locale locale) { - if (locale.equals(mCurrentLocale)) { - return; - } + private interface CalendarViewDelegate { + void setShownWeekCount(int count); + int getShownWeekCount(); - mCurrentLocale = locale; + void setSelectedWeekBackgroundColor(int color); + int getSelectedWeekBackgroundColor(); - mTempDate = getCalendarForLocale(mTempDate, locale); - mFirstDayOfMonth = getCalendarForLocale(mFirstDayOfMonth, locale); - mMinDate = getCalendarForLocale(mMinDate, locale); - mMaxDate = getCalendarForLocale(mMaxDate, locale); - } + void setFocusedMonthDateColor(int color); + int getFocusedMonthDateColor(); - /** - * Gets a calendar for locale bootstrapped with the value of a given calendar. - * - * @param oldCalendar The old calendar. - * @param locale The locale. - */ - private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) { - if (oldCalendar == null) { - return Calendar.getInstance(locale); - } else { - final long currentTimeMillis = oldCalendar.getTimeInMillis(); - Calendar newCalendar = Calendar.getInstance(locale); - newCalendar.setTimeInMillis(currentTimeMillis); - return newCalendar; - } - } + void setUnfocusedMonthDateColor(int color); + int getUnfocusedMonthDateColor(); - /** - * @return True if the <code>firstDate</code> is the same as the <code> - * secondDate</code>. - */ - private boolean isSameDate(Calendar firstDate, Calendar secondDate) { - return (firstDate.get(Calendar.DAY_OF_YEAR) == secondDate.get(Calendar.DAY_OF_YEAR) - && firstDate.get(Calendar.YEAR) == secondDate.get(Calendar.YEAR)); - } + void setWeekNumberColor(int color); + int getWeekNumberColor(); - /** - * Creates a new adapter if necessary and sets up its parameters. - */ - private void setUpAdapter() { - if (mAdapter == null) { - mAdapter = new WeeksAdapter(); - mAdapter.registerDataSetObserver(new DataSetObserver() { - @Override - public void onChanged() { - if (mOnDateChangeListener != null) { - Calendar selectedDay = mAdapter.getSelectedDay(); - mOnDateChangeListener.onSelectedDayChange(CalendarView.this, - selectedDay.get(Calendar.YEAR), - selectedDay.get(Calendar.MONTH), - selectedDay.get(Calendar.DAY_OF_MONTH)); - } - } - }); - mListView.setAdapter(mAdapter); - } + void setWeekSeparatorLineColor(int color); + int getWeekSeparatorLineColor(); - // refresh the view with the new parameters - mAdapter.notifyDataSetChanged(); + void setSelectedDateVerticalBar(int resourceId); + void setSelectedDateVerticalBar(Drawable drawable); + Drawable getSelectedDateVerticalBar(); + + void setWeekDayTextAppearance(int resourceId); + int getWeekDayTextAppearance(); + + void setDateTextAppearance(int resourceId); + int getDateTextAppearance(); + + void setEnabled(boolean enabled); + boolean isEnabled(); + + void setMinDate(long minDate); + long getMinDate(); + + void setMaxDate(long maxDate); + long getMaxDate(); + + void setShowWeekNumber(boolean showWeekNumber); + boolean getShowWeekNumber(); + + void setFirstDayOfWeek(int firstDayOfWeek); + int getFirstDayOfWeek(); + + void setDate(long date); + void setDate(long date, boolean animate, boolean center); + long getDate(); + + void setOnDateChangeListener(OnDateChangeListener listener); + + void onConfigurationChanged(Configuration newConfig); + void onInitializeAccessibilityEvent(AccessibilityEvent event); + void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info); } /** - * Sets up the strings to be used by the header. + * An abstract class which can be used as a start for CalendarView implementations */ - private void setUpHeader() { - final String[] tinyWeekdayNames = LocaleData.get(Locale.getDefault()).tinyWeekdayNames; - mDayLabels = new String[mDaysPerWeek]; - for (int i = 0; i < mDaysPerWeek; i++) { - final int j = i + mFirstDayOfWeek; - final int calendarDay = (j > Calendar.SATURDAY) ? j - Calendar.SATURDAY : j; - mDayLabels[i] = tinyWeekdayNames[calendarDay]; - } - // Deal with week number - TextView label = (TextView) mDayNamesHeader.getChildAt(0); - if (mShowWeekNumber) { - label.setVisibility(View.VISIBLE); - } else { - label.setVisibility(View.GONE); + abstract static class AbstractCalendarViewDelegate implements CalendarViewDelegate { + // The delegator + protected CalendarView mDelegator; + + // The context + protected Context mContext; + + // The current locale + protected Locale mCurrentLocale; + + AbstractCalendarViewDelegate(CalendarView delegator, Context context) { + mDelegator = delegator; + mContext = context; + + // Initialization based on locale + setCurrentLocale(Locale.getDefault()); } - // Deal with day labels - final int count = mDayNamesHeader.getChildCount(); - for (int i = 0; i < count - 1; i++) { - label = (TextView) mDayNamesHeader.getChildAt(i + 1); - if (mWeekDayTextAppearanceResId > -1) { - label.setTextAppearance(mContext, mWeekDayTextAppearanceResId); - } - if (i < mDaysPerWeek) { - label.setText(mDayLabels[i]); - label.setVisibility(View.VISIBLE); - } else { - label.setVisibility(View.GONE); + + protected void setCurrentLocale(Locale locale) { + if (locale.equals(mCurrentLocale)) { + return; } + mCurrentLocale = locale; } - mDayNamesHeader.invalidate(); } /** - * Sets all the required fields for the list view. + * A delegate implementing the legacy CalendarView */ - private void setUpListView() { - // Configure the listview - mListView.setDivider(null); - mListView.setItemsCanFocus(true); - mListView.setVerticalScrollBarEnabled(false); - mListView.setOnScrollListener(new OnScrollListener() { - public void onScrollStateChanged(AbsListView view, int scrollState) { - CalendarView.this.onScrollStateChanged(view, scrollState); - } - - public void onScroll( - AbsListView view, int firstVisibleItem, int visibleItemCount, - int totalItemCount) { - CalendarView.this.onScroll(view, firstVisibleItem, visibleItemCount, - totalItemCount); - } - }); - // Make the scrolling behavior nicer - mListView.setFriction(mFriction); - mListView.setVelocityScale(mVelocityScale); - } + private static class LegacyCalendarViewDelegate extends AbstractCalendarViewDelegate { - /** - * This moves to the specified time in the view. If the time is not already - * in range it will move the list so that the first of the month containing - * the time is at the top of the view. If the new time is already in view - * the list will not be scrolled unless forceScroll is true. This time may - * optionally be highlighted as selected as well. - * - * @param date The time to move to. - * @param animate Whether to scroll to the given time or just redraw at the - * new location. - * @param setSelected Whether to set the given time as selected. - * @param forceScroll Whether to recenter even if the time is already - * visible. - * - * @throws IllegalArgumentException of the provided date is before the - * range start of after the range end. - */ - private void goTo(Calendar date, boolean animate, boolean setSelected, boolean forceScroll) { - if (date.before(mMinDate) || date.after(mMaxDate)) { - throw new IllegalArgumentException("Time not between " + mMinDate.getTime() - + " and " + mMaxDate.getTime()); - } - // Find the first and last entirely visible weeks - int firstFullyVisiblePosition = mListView.getFirstVisiblePosition(); - View firstChild = mListView.getChildAt(0); - if (firstChild != null && firstChild.getTop() < 0) { - firstFullyVisiblePosition++; - } - int lastFullyVisiblePosition = firstFullyVisiblePosition + mShownWeekCount - 1; - if (firstChild != null && firstChild.getTop() > mBottomBuffer) { - lastFullyVisiblePosition--; - } - if (setSelected) { - mAdapter.setSelectedDay(date); - } - // Get the week we're going to - int position = getWeeksSinceMinDate(date); + /** + * Default value whether to show week number. + */ + private static final boolean DEFAULT_SHOW_WEEK_NUMBER = true; - // Check if the selected day is now outside of our visible range - // and if so scroll to the month that contains it - if (position < firstFullyVisiblePosition || position > lastFullyVisiblePosition - || forceScroll) { - mFirstDayOfMonth.setTimeInMillis(date.getTimeInMillis()); - mFirstDayOfMonth.set(Calendar.DAY_OF_MONTH, 1); + /** + * The number of milliseconds in a day.e + */ + private static final long MILLIS_IN_DAY = 86400000L; - setMonthDisplayed(mFirstDayOfMonth); + /** + * The number of day in a week. + */ + private static final int DAYS_PER_WEEK = 7; - // the earliest time we can scroll to is the min date - if (mFirstDayOfMonth.before(mMinDate)) { - position = 0; - } else { - position = getWeeksSinceMinDate(mFirstDayOfMonth); - } + /** + * The number of milliseconds in a week. + */ + private static final long MILLIS_IN_WEEK = DAYS_PER_WEEK * MILLIS_IN_DAY; - mPreviousScrollState = OnScrollListener.SCROLL_STATE_FLING; - if (animate) { - mListView.smoothScrollToPositionFromTop(position, mListScrollTopOffset, - GOTO_SCROLL_DURATION); - } else { - mListView.setSelectionFromTop(position, mListScrollTopOffset); - // Perform any after scroll operations that are needed - onScrollStateChanged(mListView, OnScrollListener.SCROLL_STATE_IDLE); - } - } else if (setSelected) { - // Otherwise just set the selection - setMonthDisplayed(date); - } - } + /** + * Affects when the month selection will change while scrolling upe + */ + private static final int SCROLL_HYST_WEEKS = 2; - /** - * Parses the given <code>date</code> and in case of success sets - * the result to the <code>outDate</code>. - * - * @return True if the date was parsed. - */ - private boolean parseDate(String date, Calendar outDate) { - try { - outDate.setTime(mDateFormat.parse(date)); - return true; - } catch (ParseException e) { - Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT); - return false; - } - } + /** + * How long the GoTo fling animation should last. + */ + private static final int GOTO_SCROLL_DURATION = 1000; - /** - * Called when a <code>view</code> transitions to a new <code>scrollState - * </code>. - */ - private void onScrollStateChanged(AbsListView view, int scrollState) { - mScrollStateChangedRunnable.doScrollStateChange(view, scrollState); - } + /** + * The duration of the adjustment upon a user scroll in milliseconds. + */ + private static final int ADJUSTMENT_SCROLL_DURATION = 500; - /** - * Updates the title and selected month if the <code>view</code> has moved to a new - * month. - */ - private void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, - int totalItemCount) { - WeekView child = (WeekView) view.getChildAt(0); - if (child == null) { - return; - } + /** + * How long to wait after receiving an onScrollStateChanged notification + * before acting on it. + */ + private static final int SCROLL_CHANGE_DELAY = 40; - // Figure out where we are - long currScroll = view.getFirstVisiblePosition() * child.getHeight() - child.getBottom(); + /** + * String for parsing dates. + */ + private static final String DATE_FORMAT = "MM/dd/yyyy"; - // If we have moved since our last call update the direction - if (currScroll < mPreviousScrollPosition) { - mIsScrollingUp = true; - } else if (currScroll > mPreviousScrollPosition) { - mIsScrollingUp = false; - } else { - return; - } + /** + * The default minimal date. + */ + private static final String DEFAULT_MIN_DATE = "01/01/1900"; - // Use some hysteresis for checking which month to highlight. This - // causes the month to transition when two full weeks of a month are - // visible when scrolling up, and when the first day in a month reaches - // the top of the screen when scrolling down. - int offset = child.getBottom() < mWeekMinVisibleHeight ? 1 : 0; - if (mIsScrollingUp) { - child = (WeekView) view.getChildAt(SCROLL_HYST_WEEKS + offset); - } else if (offset != 0) { - child = (WeekView) view.getChildAt(offset); - } + /** + * The default maximal date. + */ + private static final String DEFAULT_MAX_DATE = "01/01/2100"; - if (child != null) { - // Find out which month we're moving into - int month; - if (mIsScrollingUp) { - month = child.getMonthOfFirstWeekDay(); - } else { - month = child.getMonthOfLastWeekDay(); - } + private static final int DEFAULT_SHOWN_WEEK_COUNT = 6; - // And how it relates to our current highlighted month - int monthDiff; - if (mCurrentMonthDisplayed == 11 && month == 0) { - monthDiff = 1; - } else if (mCurrentMonthDisplayed == 0 && month == 11) { - monthDiff = -1; - } else { - monthDiff = month - mCurrentMonthDisplayed; - } + private static final int DEFAULT_DATE_TEXT_SIZE = 14; - // Only switch months if we're scrolling away from the currently - // selected month - if ((!mIsScrollingUp && monthDiff > 0) || (mIsScrollingUp && monthDiff < 0)) { - Calendar firstDay = child.getFirstDay(); - if (mIsScrollingUp) { - firstDay.add(Calendar.DAY_OF_MONTH, -DAYS_PER_WEEK); - } else { - firstDay.add(Calendar.DAY_OF_MONTH, DAYS_PER_WEEK); - } - setMonthDisplayed(firstDay); - } - } + private static final int UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH = 6; - mPreviousScrollPosition = currScroll; - mPreviousScrollState = mCurrentScrollState; - } + private static final int UNSCALED_WEEK_MIN_VISIBLE_HEIGHT = 12; - /** - * Sets the month displayed at the top of this view based on time. Override - * to add custom events when the title is changed. - * - * @param calendar A day in the new focus month. - */ - private void setMonthDisplayed(Calendar calendar) { - mCurrentMonthDisplayed = calendar.get(Calendar.MONTH); - mAdapter.setFocusMonth(mCurrentMonthDisplayed); - final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_MONTH_DAY - | DateUtils.FORMAT_SHOW_YEAR; - final long millis = calendar.getTimeInMillis(); - String newMonthName = DateUtils.formatDateRange(mContext, millis, millis, flags); - mMonthName.setText(newMonthName); - mMonthName.invalidate(); - } + private static final int UNSCALED_LIST_SCROLL_TOP_OFFSET = 2; - /** - * @return Returns the number of weeks between the current <code>date</code> - * and the <code>mMinDate</code>. - */ - private int getWeeksSinceMinDate(Calendar date) { - if (date.before(mMinDate)) { - throw new IllegalArgumentException("fromDate: " + mMinDate.getTime() - + " does not precede toDate: " + date.getTime()); - } - long endTimeMillis = date.getTimeInMillis() - + date.getTimeZone().getOffset(date.getTimeInMillis()); - long startTimeMillis = mMinDate.getTimeInMillis() - + mMinDate.getTimeZone().getOffset(mMinDate.getTimeInMillis()); - long dayOffsetMillis = (mMinDate.get(Calendar.DAY_OF_WEEK) - mFirstDayOfWeek) - * MILLIS_IN_DAY; - return (int) ((endTimeMillis - startTimeMillis + dayOffsetMillis) / MILLIS_IN_WEEK); - } + private static final int UNSCALED_BOTTOM_BUFFER = 20; - /** - * Command responsible for acting upon scroll state changes. - */ - private class ScrollStateRunnable implements Runnable { - private AbsListView mView; + private static final int UNSCALED_WEEK_SEPARATOR_LINE_WIDTH = 1; + + private static final int DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID = -1; + + private final int mWeekSeperatorLineWidth; + + private int mDateTextSize; + + private Drawable mSelectedDateVerticalBar; + + private final int mSelectedDateVerticalBarWidth; + + private int mSelectedWeekBackgroundColor; + + private int mFocusedMonthDateColor; - private int mNewState; + private int mUnfocusedMonthDateColor; + + private int mWeekSeparatorLineColor; + + private int mWeekNumberColor; + + private int mWeekDayTextAppearanceResId; + + private int mDateTextAppearanceResId; /** - * Sets up the runnable with a short delay in case the scroll state - * immediately changes again. - * - * @param view The list view that changed state - * @param scrollState The new state it changed to + * The top offset of the weeks list. */ - public void doScrollStateChange(AbsListView view, int scrollState) { - mView = view; - mNewState = scrollState; - removeCallbacks(this); - postDelayed(this, SCROLL_CHANGE_DELAY); - } + private int mListScrollTopOffset = 2; - public void run() { - mCurrentScrollState = mNewState; - // Fix the position after a scroll or a fling ends - if (mNewState == OnScrollListener.SCROLL_STATE_IDLE - && mPreviousScrollState != OnScrollListener.SCROLL_STATE_IDLE) { - View child = mView.getChildAt(0); - if (child == null) { - // The view is no longer visible, just return - return; - } - int dist = child.getBottom() - mListScrollTopOffset; - if (dist > mListScrollTopOffset) { - if (mIsScrollingUp) { - mView.smoothScrollBy(dist - child.getHeight(), ADJUSTMENT_SCROLL_DURATION); - } else { - mView.smoothScrollBy(dist, ADJUSTMENT_SCROLL_DURATION); - } - } - } - mPreviousScrollState = mNewState; - } - } + /** + * The visible height of a week view. + */ + private int mWeekMinVisibleHeight = 12; - /** - * <p> - * This is a specialized adapter for creating a list of weeks with - * selectable days. It can be configured to display the week number, start - * the week on a given day, show a reduced number of days, or display an - * arbitrary number of weeks at a time. - * </p> - */ - private class WeeksAdapter extends BaseAdapter implements OnTouchListener { - private final Calendar mSelectedDate = Calendar.getInstance(); - private final GestureDetector mGestureDetector; + /** + * The visible height of a week view. + */ + private int mBottomBuffer = 20; - private int mSelectedWeek; + /** + * The number of shown weeks. + */ + private int mShownWeekCount; - private int mFocusedMonth; + /** + * Flag whether to show the week number. + */ + private boolean mShowWeekNumber; - private int mTotalWeekCount; + /** + * The number of day per week to be shown. + */ + private int mDaysPerWeek = 7; - public WeeksAdapter() { - mGestureDetector = new GestureDetector(mContext, new CalendarGestureListener()); - init(); - } + /** + * The friction of the week list while flinging. + */ + private float mFriction = .05f; /** - * Set up the gesture detector and selected time + * Scale for adjusting velocity of the week list while flinging. */ - private void init() { - mSelectedWeek = getWeeksSinceMinDate(mSelectedDate); - mTotalWeekCount = getWeeksSinceMinDate(mMaxDate); - if (mMinDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek - || mMaxDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek) { - mTotalWeekCount++; - } - notifyDataSetChanged(); - } + private float mVelocityScale = 0.333f; /** - * Updates the selected day and related parameters. - * - * @param selectedDay The time to highlight + * The adapter for the weeks list. */ - public void setSelectedDay(Calendar selectedDay) { - if (selectedDay.get(Calendar.DAY_OF_YEAR) == mSelectedDate.get(Calendar.DAY_OF_YEAR) - && selectedDay.get(Calendar.YEAR) == mSelectedDate.get(Calendar.YEAR)) { - return; - } - mSelectedDate.setTimeInMillis(selectedDay.getTimeInMillis()); - mSelectedWeek = getWeeksSinceMinDate(mSelectedDate); - mFocusedMonth = mSelectedDate.get(Calendar.MONTH); - notifyDataSetChanged(); - } + private WeeksAdapter mAdapter; /** - * @return The selected day of month. + * The weeks list. */ - public Calendar getSelectedDay() { - return mSelectedDate; + private ListView mListView; + + /** + * The name of the month to display. + */ + private TextView mMonthName; + + /** + * The header with week day names. + */ + private ViewGroup mDayNamesHeader; + + /** + * Cached labels for the week names header. + */ + private String[] mDayLabels; + + /** + * The first day of the week. + */ + private int mFirstDayOfWeek; + + /** + * Which month should be displayed/highlighted [0-11]. + */ + private int mCurrentMonthDisplayed = -1; + + /** + * Used for tracking during a scroll. + */ + private long mPreviousScrollPosition; + + /** + * Used for tracking which direction the view is scrolling. + */ + private boolean mIsScrollingUp = false; + + /** + * The previous scroll state of the weeks ListView. + */ + private int mPreviousScrollState = OnScrollListener.SCROLL_STATE_IDLE; + + /** + * The current scroll state of the weeks ListView. + */ + private int mCurrentScrollState = OnScrollListener.SCROLL_STATE_IDLE; + + /** + * Listener for changes in the selected day. + */ + private OnDateChangeListener mOnDateChangeListener; + + /** + * Command for adjusting the position after a scroll/fling. + */ + private ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable(); + + /** + * Temporary instance to avoid multiple instantiations. + */ + private Calendar mTempDate; + + /** + * The first day of the focused month. + */ + private Calendar mFirstDayOfMonth; + + /** + * The start date of the range supported by this picker. + */ + private Calendar mMinDate; + + /** + * The end date of the range supported by this picker. + */ + private Calendar mMaxDate; + + /** + * Date format for parsing dates. + */ + private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT); + + LegacyCalendarViewDelegate(CalendarView delegator, Context context, AttributeSet attrs, + int defStyleAttr, int defStyleRes) { + super(delegator, context); + + // initialization based on locale + setCurrentLocale(Locale.getDefault()); + + TypedArray attributesArray = context.obtainStyledAttributes(attrs, + R.styleable.CalendarView, defStyleAttr, defStyleRes); + mShowWeekNumber = attributesArray.getBoolean(R.styleable.CalendarView_showWeekNumber, + DEFAULT_SHOW_WEEK_NUMBER); + mFirstDayOfWeek = attributesArray.getInt(R.styleable.CalendarView_firstDayOfWeek, + LocaleData.get(Locale.getDefault()).firstDayOfWeek); + String minDate = attributesArray.getString(R.styleable.CalendarView_minDate); + if (TextUtils.isEmpty(minDate) || !parseDate(minDate, mMinDate)) { + parseDate(DEFAULT_MIN_DATE, mMinDate); + } + String maxDate = attributesArray.getString(R.styleable.CalendarView_maxDate); + if (TextUtils.isEmpty(maxDate) || !parseDate(maxDate, mMaxDate)) { + parseDate(DEFAULT_MAX_DATE, mMaxDate); + } + if (mMaxDate.before(mMinDate)) { + throw new IllegalArgumentException("Max date cannot be before min date."); + } + mShownWeekCount = attributesArray.getInt(R.styleable.CalendarView_shownWeekCount, + DEFAULT_SHOWN_WEEK_COUNT); + mSelectedWeekBackgroundColor = attributesArray.getColor( + R.styleable.CalendarView_selectedWeekBackgroundColor, 0); + mFocusedMonthDateColor = attributesArray.getColor( + R.styleable.CalendarView_focusedMonthDateColor, 0); + mUnfocusedMonthDateColor = attributesArray.getColor( + R.styleable.CalendarView_unfocusedMonthDateColor, 0); + mWeekSeparatorLineColor = attributesArray.getColor( + R.styleable.CalendarView_weekSeparatorLineColor, 0); + mWeekNumberColor = attributesArray.getColor(R.styleable.CalendarView_weekNumberColor, 0); + mSelectedDateVerticalBar = attributesArray.getDrawable( + R.styleable.CalendarView_selectedDateVerticalBar); + + mDateTextAppearanceResId = attributesArray.getResourceId( + R.styleable.CalendarView_dateTextAppearance, R.style.TextAppearance_Small); + updateDateTextSize(); + + mWeekDayTextAppearanceResId = attributesArray.getResourceId( + R.styleable.CalendarView_weekDayTextAppearance, + DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID); + attributesArray.recycle(); + + DisplayMetrics displayMetrics = mDelegator.getResources().getDisplayMetrics(); + mWeekMinVisibleHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + UNSCALED_WEEK_MIN_VISIBLE_HEIGHT, displayMetrics); + mListScrollTopOffset = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + UNSCALED_LIST_SCROLL_TOP_OFFSET, displayMetrics); + mBottomBuffer = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + UNSCALED_BOTTOM_BUFFER, displayMetrics); + mSelectedDateVerticalBarWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH, displayMetrics); + mWeekSeperatorLineWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + UNSCALED_WEEK_SEPARATOR_LINE_WIDTH, displayMetrics); + + LayoutInflater layoutInflater = (LayoutInflater) mContext + .getSystemService(Service.LAYOUT_INFLATER_SERVICE); + View content = layoutInflater.inflate(R.layout.calendar_view, null, false); + mDelegator.addView(content); + + mListView = (ListView) mDelegator.findViewById(R.id.list); + mDayNamesHeader = (ViewGroup) content.findViewById(com.android.internal.R.id.day_names); + mMonthName = (TextView) content.findViewById(com.android.internal.R.id.month_name); + + setUpHeader(); + setUpListView(); + setUpAdapter(); + + // go to today or whichever is close to today min or max date + mTempDate.setTimeInMillis(System.currentTimeMillis()); + if (mTempDate.before(mMinDate)) { + goTo(mMinDate, false, true, true); + } else if (mMaxDate.before(mTempDate)) { + goTo(mMaxDate, false, true, true); + } else { + goTo(mTempDate, false, true, true); + } + + mDelegator.invalidate(); } @Override - public int getCount() { - return mTotalWeekCount; + public void setShownWeekCount(int count) { + if (mShownWeekCount != count) { + mShownWeekCount = count; + mDelegator.invalidate(); + } } @Override - public Object getItem(int position) { - return null; + public int getShownWeekCount() { + return mShownWeekCount; } @Override - public long getItemId(int position) { - return position; + public void setSelectedWeekBackgroundColor(int color) { + if (mSelectedWeekBackgroundColor != color) { + mSelectedWeekBackgroundColor = color; + final int childCount = mListView.getChildCount(); + for (int i = 0; i < childCount; i++) { + WeekView weekView = (WeekView) mListView.getChildAt(i); + if (weekView.mHasSelectedDay) { + weekView.invalidate(); + } + } + } } @Override - public View getView(int position, View convertView, ViewGroup parent) { - WeekView weekView = null; - if (convertView != null) { - weekView = (WeekView) convertView; - } else { - weekView = new WeekView(mContext); - android.widget.AbsListView.LayoutParams params = - new android.widget.AbsListView.LayoutParams(LayoutParams.WRAP_CONTENT, - LayoutParams.WRAP_CONTENT); - weekView.setLayoutParams(params); - weekView.setClickable(true); - weekView.setOnTouchListener(this); - } + public int getSelectedWeekBackgroundColor() { + return mSelectedWeekBackgroundColor; + } - int selectedWeekDay = (mSelectedWeek == position) ? mSelectedDate.get( - Calendar.DAY_OF_WEEK) : -1; - weekView.init(position, selectedWeekDay, mFocusedMonth); + @Override + public void setFocusedMonthDateColor(int color) { + if (mFocusedMonthDateColor != color) { + mFocusedMonthDateColor = color; + final int childCount = mListView.getChildCount(); + for (int i = 0; i < childCount; i++) { + WeekView weekView = (WeekView) mListView.getChildAt(i); + if (weekView.mHasFocusedDay) { + weekView.invalidate(); + } + } + } + } - return weekView; + @Override + public int getFocusedMonthDateColor() { + return mFocusedMonthDateColor; } - /** - * Changes which month is in focus and updates the view. - * - * @param month The month to show as in focus [0-11] - */ - public void setFocusMonth(int month) { - if (mFocusedMonth == month) { - return; + @Override + public void setUnfocusedMonthDateColor(int color) { + if (mUnfocusedMonthDateColor != color) { + mUnfocusedMonthDateColor = color; + final int childCount = mListView.getChildCount(); + for (int i = 0; i < childCount; i++) { + WeekView weekView = (WeekView) mListView.getChildAt(i); + if (weekView.mHasUnfocusedDay) { + weekView.invalidate(); + } + } } - mFocusedMonth = month; - notifyDataSetChanged(); } @Override - public boolean onTouch(View v, MotionEvent event) { - if (mListView.isEnabled() && mGestureDetector.onTouchEvent(event)) { - WeekView weekView = (WeekView) v; - // if we cannot find a day for the given location we are done - if (!weekView.getDayFromLocation(event.getX(), mTempDate)) { - return true; - } - // it is possible that the touched day is outside the valid range - // we draw whole weeks but range end can fall not on the week end - if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) { - return true; + public int getUnfocusedMonthDateColor() { + return mFocusedMonthDateColor; + } + + @Override + public void setWeekNumberColor(int color) { + if (mWeekNumberColor != color) { + mWeekNumberColor = color; + if (mShowWeekNumber) { + invalidateAllWeekViews(); } - onDateTapped(mTempDate); - return true; } - return false; } - /** - * Maintains the same hour/min/sec but moves the day to the tapped day. - * - * @param day The day that was tapped - */ - private void onDateTapped(Calendar day) { - setSelectedDay(day); - setMonthDisplayed(day); + @Override + public int getWeekNumberColor() { + return mWeekNumberColor; } - /** - * This is here so we can identify single tap events and set the - * selected day correctly - */ - class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener { - @Override - public boolean onSingleTapUp(MotionEvent e) { - return true; + @Override + public void setWeekSeparatorLineColor(int color) { + if (mWeekSeparatorLineColor != color) { + mWeekSeparatorLineColor = color; + invalidateAllWeekViews(); } } - } - /** - * <p> - * This is a dynamic view for drawing a single week. It can be configured to - * display the week number, start the week on a given day, or show a reduced - * number of days. It is intended for use as a single view within a - * ListView. See {@link WeeksAdapter} for usage. - * </p> - */ - private class WeekView extends View { + @Override + public int getWeekSeparatorLineColor() { + return mWeekSeparatorLineColor; + } - private final Rect mTempRect = new Rect(); + @Override + public void setSelectedDateVerticalBar(int resourceId) { + Drawable drawable = mDelegator.getContext().getDrawable(resourceId); + setSelectedDateVerticalBar(drawable); + } - private final Paint mDrawPaint = new Paint(); + @Override + public void setSelectedDateVerticalBar(Drawable drawable) { + if (mSelectedDateVerticalBar != drawable) { + mSelectedDateVerticalBar = drawable; + final int childCount = mListView.getChildCount(); + for (int i = 0; i < childCount; i++) { + WeekView weekView = (WeekView) mListView.getChildAt(i); + if (weekView.mHasSelectedDay) { + weekView.invalidate(); + } + } + } + } - private final Paint mMonthNumDrawPaint = new Paint(); + @Override + public Drawable getSelectedDateVerticalBar() { + return mSelectedDateVerticalBar; + } - // Cache the number strings so we don't have to recompute them each time - private String[] mDayNumbers; + @Override + public void setWeekDayTextAppearance(int resourceId) { + if (mWeekDayTextAppearanceResId != resourceId) { + mWeekDayTextAppearanceResId = resourceId; + setUpHeader(); + } + } + + @Override + public int getWeekDayTextAppearance() { + return mWeekDayTextAppearanceResId; + } + + @Override + public void setDateTextAppearance(int resourceId) { + if (mDateTextAppearanceResId != resourceId) { + mDateTextAppearanceResId = resourceId; + updateDateTextSize(); + invalidateAllWeekViews(); + } + } + + @Override + public int getDateTextAppearance() { + return mDateTextAppearanceResId; + } - // Quick lookup for checking which days are in the focus month - private boolean[] mFocusDay; + @Override + public void setEnabled(boolean enabled) { + mListView.setEnabled(enabled); + } + + @Override + public boolean isEnabled() { + return mListView.isEnabled(); + } - // Whether this view has a focused day. - private boolean mHasFocusedDay; + @Override + public void setMinDate(long minDate) { + mTempDate.setTimeInMillis(minDate); + if (isSameDate(mTempDate, mMinDate)) { + return; + } + mMinDate.setTimeInMillis(minDate); + // make sure the current date is not earlier than + // the new min date since the latter is used for + // calculating the indices in the adapter thus + // avoiding out of bounds error + Calendar date = mAdapter.mSelectedDate; + if (date.before(mMinDate)) { + mAdapter.setSelectedDay(mMinDate); + } + // reinitialize the adapter since its range depends on min date + mAdapter.init(); + if (date.before(mMinDate)) { + setDate(mTempDate.getTimeInMillis()); + } else { + // we go to the current date to force the ListView to query its + // adapter for the shown views since we have changed the adapter + // range and the base from which the later calculates item indices + // note that calling setDate will not work since the date is the same + goTo(date, false, true, false); + } + } - // Whether this view has only focused days. - private boolean mHasUnfocusedDay; + @Override + public long getMinDate() { + return mMinDate.getTimeInMillis(); + } - // The first day displayed by this item - private Calendar mFirstDay; + @Override + public void setMaxDate(long maxDate) { + mTempDate.setTimeInMillis(maxDate); + if (isSameDate(mTempDate, mMaxDate)) { + return; + } + mMaxDate.setTimeInMillis(maxDate); + // reinitialize the adapter since its range depends on max date + mAdapter.init(); + Calendar date = mAdapter.mSelectedDate; + if (date.after(mMaxDate)) { + setDate(mMaxDate.getTimeInMillis()); + } else { + // we go to the current date to force the ListView to query its + // adapter for the shown views since we have changed the adapter + // range and the base from which the later calculates item indices + // note that calling setDate will not work since the date is the same + goTo(date, false, true, false); + } + } - // The month of the first day in this week - private int mMonthOfFirstWeekDay = -1; + @Override + public long getMaxDate() { + return mMaxDate.getTimeInMillis(); + } - // The month of the last day in this week - private int mLastWeekDayMonth = -1; + @Override + public void setShowWeekNumber(boolean showWeekNumber) { + if (mShowWeekNumber == showWeekNumber) { + return; + } + mShowWeekNumber = showWeekNumber; + mAdapter.notifyDataSetChanged(); + setUpHeader(); + } - // The position of this week, equivalent to weeks since the week of Jan - // 1st, 1900 - private int mWeek = -1; + @Override + public boolean getShowWeekNumber() { + return mShowWeekNumber; + } - // Quick reference to the width of this view, matches parent - private int mWidth; + @Override + public void setFirstDayOfWeek(int firstDayOfWeek) { + if (mFirstDayOfWeek == firstDayOfWeek) { + return; + } + mFirstDayOfWeek = firstDayOfWeek; + mAdapter.init(); + mAdapter.notifyDataSetChanged(); + setUpHeader(); + } - // The height this view should draw at in pixels, set by height param - private int mHeight; + @Override + public int getFirstDayOfWeek() { + return mFirstDayOfWeek; + } - // If this view contains the selected day - private boolean mHasSelectedDay = false; + @Override + public void setDate(long date) { + setDate(date, false, false); + } - // Which day is selected [0-6] or -1 if no day is selected - private int mSelectedDay = -1; + @Override + public void setDate(long date, boolean animate, boolean center) { + mTempDate.setTimeInMillis(date); + if (isSameDate(mTempDate, mAdapter.mSelectedDate)) { + return; + } + goTo(mTempDate, animate, true, center); + } - // The number of days + a spot for week number if it is displayed - private int mNumCells; + @Override + public long getDate() { + return mAdapter.mSelectedDate.getTimeInMillis(); + } - // The left edge of the selected day - private int mSelectedLeft = -1; + @Override + public void setOnDateChangeListener(OnDateChangeListener listener) { + mOnDateChangeListener = listener; + } - // The right edge of the selected day - private int mSelectedRight = -1; + @Override + public void onConfigurationChanged(Configuration newConfig) { + setCurrentLocale(newConfig.locale); + } - public WeekView(Context context) { - super(context); + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + event.setClassName(CalendarView.class.getName()); + } - // Sets up any standard paints that will be used - initilaizePaints(); + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + info.setClassName(CalendarView.class.getName()); } /** - * Initializes this week view. + * Sets the current locale. * - * @param weekNumber The number of the week this view represents. The - * week number is a zero based index of the weeks since - * {@link CalendarView#getMinDate()}. - * @param selectedWeekDay The selected day of the week from 0 to 6, -1 if no - * selected day. - * @param focusedMonth The month that is currently in focus i.e. - * highlighted. + * @param locale The current locale. */ - public void init(int weekNumber, int selectedWeekDay, int focusedMonth) { - mSelectedDay = selectedWeekDay; - mHasSelectedDay = mSelectedDay != -1; - mNumCells = mShowWeekNumber ? mDaysPerWeek + 1 : mDaysPerWeek; - mWeek = weekNumber; - mTempDate.setTimeInMillis(mMinDate.getTimeInMillis()); - - mTempDate.add(Calendar.WEEK_OF_YEAR, mWeek); - mTempDate.setFirstDayOfWeek(mFirstDayOfWeek); - - // Allocate space for caching the day numbers and focus values - mDayNumbers = new String[mNumCells]; - mFocusDay = new boolean[mNumCells]; - - // If we're showing the week number calculate it based on Monday - int i = 0; - if (mShowWeekNumber) { - mDayNumbers[0] = String.format(Locale.getDefault(), "%d", - mTempDate.get(Calendar.WEEK_OF_YEAR)); - i++; - } - - // Now adjust our starting day based on the start day of the week - int diff = mFirstDayOfWeek - mTempDate.get(Calendar.DAY_OF_WEEK); - mTempDate.add(Calendar.DAY_OF_MONTH, diff); - - mFirstDay = (Calendar) mTempDate.clone(); - mMonthOfFirstWeekDay = mTempDate.get(Calendar.MONTH); - - mHasUnfocusedDay = true; - for (; i < mNumCells; i++) { - final boolean isFocusedDay = (mTempDate.get(Calendar.MONTH) == focusedMonth); - mFocusDay[i] = isFocusedDay; - mHasFocusedDay |= isFocusedDay; - mHasUnfocusedDay &= !isFocusedDay; - // do not draw dates outside the valid range to avoid user confusion - if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) { - mDayNumbers[i] = ""; - } else { - mDayNumbers[i] = String.format(Locale.getDefault(), "%d", - mTempDate.get(Calendar.DAY_OF_MONTH)); - } - mTempDate.add(Calendar.DAY_OF_MONTH, 1); - } - // We do one extra add at the end of the loop, if that pushed us to - // new month undo it - if (mTempDate.get(Calendar.DAY_OF_MONTH) == 1) { - mTempDate.add(Calendar.DAY_OF_MONTH, -1); - } - mLastWeekDayMonth = mTempDate.get(Calendar.MONTH); + @Override + protected void setCurrentLocale(Locale locale) { + super.setCurrentLocale(locale); - updateSelectionPositions(); + mTempDate = getCalendarForLocale(mTempDate, locale); + mFirstDayOfMonth = getCalendarForLocale(mFirstDayOfMonth, locale); + mMinDate = getCalendarForLocale(mMinDate, locale); + mMaxDate = getCalendarForLocale(mMaxDate, locale); + } + private void updateDateTextSize() { + TypedArray dateTextAppearance = mDelegator.getContext().obtainStyledAttributes( + mDateTextAppearanceResId, R.styleable.TextAppearance); + mDateTextSize = dateTextAppearance.getDimensionPixelSize( + R.styleable.TextAppearance_textSize, DEFAULT_DATE_TEXT_SIZE); + dateTextAppearance.recycle(); } /** - * Initialize the paint instances. + * Invalidates all week views. */ - private void initilaizePaints() { - mDrawPaint.setFakeBoldText(false); - mDrawPaint.setAntiAlias(true); - mDrawPaint.setStyle(Style.FILL); - - mMonthNumDrawPaint.setFakeBoldText(true); - mMonthNumDrawPaint.setAntiAlias(true); - mMonthNumDrawPaint.setStyle(Style.FILL); - mMonthNumDrawPaint.setTextAlign(Align.CENTER); - mMonthNumDrawPaint.setTextSize(mDateTextSize); + private void invalidateAllWeekViews() { + final int childCount = mListView.getChildCount(); + for (int i = 0; i < childCount; i++) { + View view = mListView.getChildAt(i); + view.invalidate(); + } } /** - * Returns the month of the first day in this week. + * Gets a calendar for locale bootstrapped with the value of a given calendar. * - * @return The month the first day of this view is in. + * @param oldCalendar The old calendar. + * @param locale The locale. */ - public int getMonthOfFirstWeekDay() { - return mMonthOfFirstWeekDay; + private static Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) { + if (oldCalendar == null) { + return Calendar.getInstance(locale); + } else { + final long currentTimeMillis = oldCalendar.getTimeInMillis(); + Calendar newCalendar = Calendar.getInstance(locale); + newCalendar.setTimeInMillis(currentTimeMillis); + return newCalendar; + } } /** - * Returns the month of the last day in this week - * - * @return The month the last day of this view is in + * @return True if the <code>firstDate</code> is the same as the <code> + * secondDate</code>. */ - public int getMonthOfLastWeekDay() { - return mLastWeekDayMonth; + private static boolean isSameDate(Calendar firstDate, Calendar secondDate) { + return (firstDate.get(Calendar.DAY_OF_YEAR) == secondDate.get(Calendar.DAY_OF_YEAR) + && firstDate.get(Calendar.YEAR) == secondDate.get(Calendar.YEAR)); } /** - * Returns the first day in this view. - * - * @return The first day in the view. + * Creates a new adapter if necessary and sets up its parameters. */ - public Calendar getFirstDay() { - return mFirstDay; + private void setUpAdapter() { + if (mAdapter == null) { + mAdapter = new WeeksAdapter(mContext); + mAdapter.registerDataSetObserver(new DataSetObserver() { + @Override + public void onChanged() { + if (mOnDateChangeListener != null) { + Calendar selectedDay = mAdapter.getSelectedDay(); + mOnDateChangeListener.onSelectedDayChange(mDelegator, + selectedDay.get(Calendar.YEAR), + selectedDay.get(Calendar.MONTH), + selectedDay.get(Calendar.DAY_OF_MONTH)); + } + } + }); + mListView.setAdapter(mAdapter); + } + + // refresh the view with the new parameters + mAdapter.notifyDataSetChanged(); } /** - * Calculates the day that the given x position is in, accounting for - * week number. - * - * @param x The x position of the touch event. - * @return True if a day was found for the given location. + * Sets up the strings to be used by the header. */ - public boolean getDayFromLocation(float x, Calendar outCalendar) { - final boolean isLayoutRtl = isLayoutRtl(); - - int start; - int end; + private void setUpHeader() { + mDayLabels = new String[mDaysPerWeek]; + for (int i = mFirstDayOfWeek, count = mFirstDayOfWeek + mDaysPerWeek; i < count; i++) { + int calendarDay = (i > Calendar.SATURDAY) ? i - Calendar.SATURDAY : i; + mDayLabels[i - mFirstDayOfWeek] = DateUtils.getDayOfWeekString(calendarDay, + DateUtils.LENGTH_SHORTEST); + } - if (isLayoutRtl) { - start = 0; - end = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth; + TextView label = (TextView) mDayNamesHeader.getChildAt(0); + if (mShowWeekNumber) { + label.setVisibility(View.VISIBLE); } else { - start = mShowWeekNumber ? mWidth / mNumCells : 0; - end = mWidth; + label.setVisibility(View.GONE); } - - if (x < start || x > end) { - outCalendar.clear(); - return false; + for (int i = 1, count = mDayNamesHeader.getChildCount(); i < count; i++) { + label = (TextView) mDayNamesHeader.getChildAt(i); + if (mWeekDayTextAppearanceResId > -1) { + label.setTextAppearance(mContext, mWeekDayTextAppearanceResId); + } + if (i < mDaysPerWeek + 1) { + label.setText(mDayLabels[i - 1]); + label.setVisibility(View.VISIBLE); + } else { + label.setVisibility(View.GONE); + } } + mDayNamesHeader.invalidate(); + } + + /** + * Sets all the required fields for the list view. + */ + private void setUpListView() { + // Configure the listview + mListView.setDivider(null); + mListView.setItemsCanFocus(true); + mListView.setVerticalScrollBarEnabled(false); + mListView.setOnScrollListener(new OnScrollListener() { + public void onScrollStateChanged(AbsListView view, int scrollState) { + LegacyCalendarViewDelegate.this.onScrollStateChanged(view, scrollState); + } - // Selection is (x - start) / (pixels/day) which is (x - start) * day / pixels - int dayPosition = (int) ((x - start) * mDaysPerWeek / (end - start)); + public void onScroll( + AbsListView view, int firstVisibleItem, int visibleItemCount, + int totalItemCount) { + LegacyCalendarViewDelegate.this.onScroll(view, firstVisibleItem, + visibleItemCount, totalItemCount); + } + }); + // Make the scrolling behavior nicer + mListView.setFriction(mFriction); + mListView.setVelocityScale(mVelocityScale); + } - if (isLayoutRtl) { - dayPosition = mDaysPerWeek - 1 - dayPosition; + /** + * This moves to the specified time in the view. If the time is not already + * in range it will move the list so that the first of the month containing + * the time is at the top of the view. If the new time is already in view + * the list will not be scrolled unless forceScroll is true. This time may + * optionally be highlighted as selected as well. + * + * @param date The time to move to. + * @param animate Whether to scroll to the given time or just redraw at the + * new location. + * @param setSelected Whether to set the given time as selected. + * @param forceScroll Whether to recenter even if the time is already + * visible. + * + * @throws IllegalArgumentException of the provided date is before the + * range start of after the range end. + */ + private void goTo(Calendar date, boolean animate, boolean setSelected, + boolean forceScroll) { + if (date.before(mMinDate) || date.after(mMaxDate)) { + throw new IllegalArgumentException("Time not between " + mMinDate.getTime() + + " and " + mMaxDate.getTime()); + } + // Find the first and last entirely visible weeks + int firstFullyVisiblePosition = mListView.getFirstVisiblePosition(); + View firstChild = mListView.getChildAt(0); + if (firstChild != null && firstChild.getTop() < 0) { + firstFullyVisiblePosition++; + } + int lastFullyVisiblePosition = firstFullyVisiblePosition + mShownWeekCount - 1; + if (firstChild != null && firstChild.getTop() > mBottomBuffer) { + lastFullyVisiblePosition--; + } + if (setSelected) { + mAdapter.setSelectedDay(date); } + // Get the week we're going to + int position = getWeeksSinceMinDate(date); - outCalendar.setTimeInMillis(mFirstDay.getTimeInMillis()); - outCalendar.add(Calendar.DAY_OF_MONTH, dayPosition); + // Check if the selected day is now outside of our visible range + // and if so scroll to the month that contains it + if (position < firstFullyVisiblePosition || position > lastFullyVisiblePosition + || forceScroll) { + mFirstDayOfMonth.setTimeInMillis(date.getTimeInMillis()); + mFirstDayOfMonth.set(Calendar.DAY_OF_MONTH, 1); - return true; - } + setMonthDisplayed(mFirstDayOfMonth); - @Override - protected void onDraw(Canvas canvas) { - drawBackground(canvas); - drawWeekNumbersAndDates(canvas); - drawWeekSeparators(canvas); - drawSelectedDateVerticalBars(canvas); + // the earliest time we can scroll to is the min date + if (mFirstDayOfMonth.before(mMinDate)) { + position = 0; + } else { + position = getWeeksSinceMinDate(mFirstDayOfMonth); + } + + mPreviousScrollState = OnScrollListener.SCROLL_STATE_FLING; + if (animate) { + mListView.smoothScrollToPositionFromTop(position, mListScrollTopOffset, + GOTO_SCROLL_DURATION); + } else { + mListView.setSelectionFromTop(position, mListScrollTopOffset); + // Perform any after scroll operations that are needed + onScrollStateChanged(mListView, OnScrollListener.SCROLL_STATE_IDLE); + } + } else if (setSelected) { + // Otherwise just set the selection + setMonthDisplayed(date); + } } /** - * This draws the selection highlight if a day is selected in this week. + * Parses the given <code>date</code> and in case of success sets + * the result to the <code>outDate</code>. * - * @param canvas The canvas to draw on + * @return True if the date was parsed. */ - private void drawBackground(Canvas canvas) { - if (!mHasSelectedDay) { - return; + private boolean parseDate(String date, Calendar outDate) { + try { + outDate.setTime(mDateFormat.parse(date)); + return true; + } catch (ParseException e) { + Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT); + return false; } - mDrawPaint.setColor(mSelectedWeekBackgroundColor); + } - mTempRect.top = mWeekSeperatorLineWidth; - mTempRect.bottom = mHeight; + /** + * Called when a <code>view</code> transitions to a new <code>scrollState + * </code>. + */ + private void onScrollStateChanged(AbsListView view, int scrollState) { + mScrollStateChangedRunnable.doScrollStateChange(view, scrollState); + } - final boolean isLayoutRtl = isLayoutRtl(); + /** + * Updates the title and selected month if the <code>view</code> has moved to a new + * month. + */ + private void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, + int totalItemCount) { + WeekView child = (WeekView) view.getChildAt(0); + if (child == null) { + return; + } - if (isLayoutRtl) { - mTempRect.left = 0; - mTempRect.right = mSelectedLeft - 2; + // Figure out where we are + long currScroll = + view.getFirstVisiblePosition() * child.getHeight() - child.getBottom(); + + // If we have moved since our last call update the direction + if (currScroll < mPreviousScrollPosition) { + mIsScrollingUp = true; + } else if (currScroll > mPreviousScrollPosition) { + mIsScrollingUp = false; } else { - mTempRect.left = mShowWeekNumber ? mWidth / mNumCells : 0; - mTempRect.right = mSelectedLeft - 2; + return; } - canvas.drawRect(mTempRect, mDrawPaint); - if (isLayoutRtl) { - mTempRect.left = mSelectedRight + 3; - mTempRect.right = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth; - } else { - mTempRect.left = mSelectedRight + 3; - mTempRect.right = mWidth; + // Use some hysteresis for checking which month to highlight. This + // causes the month to transition when two full weeks of a month are + // visible when scrolling up, and when the first day in a month reaches + // the top of the screen when scrolling down. + int offset = child.getBottom() < mWeekMinVisibleHeight ? 1 : 0; + if (mIsScrollingUp) { + child = (WeekView) view.getChildAt(SCROLL_HYST_WEEKS + offset); + } else if (offset != 0) { + child = (WeekView) view.getChildAt(offset); } - canvas.drawRect(mTempRect, mDrawPaint); - } - /** - * Draws the week and month day numbers for this week. - * - * @param canvas The canvas to draw on - */ - private void drawWeekNumbersAndDates(Canvas canvas) { - final float textHeight = mDrawPaint.getTextSize(); - final int y = (int) ((mHeight + textHeight) / 2) - mWeekSeperatorLineWidth; - final int nDays = mNumCells; - final int divisor = 2 * nDays; - - mDrawPaint.setTextAlign(Align.CENTER); - mDrawPaint.setTextSize(mDateTextSize); - - int i = 0; - - if (isLayoutRtl()) { - for (; i < nDays - 1; i++) { - mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor - : mUnfocusedMonthDateColor); - int x = (2 * i + 1) * mWidth / divisor; - canvas.drawText(mDayNumbers[nDays - 1 - i], x, y, mMonthNumDrawPaint); - } - if (mShowWeekNumber) { - mDrawPaint.setColor(mWeekNumberColor); - int x = mWidth - mWidth / divisor; - canvas.drawText(mDayNumbers[0], x, y, mDrawPaint); + if (child != null) { + // Find out which month we're moving into + int month; + if (mIsScrollingUp) { + month = child.getMonthOfFirstWeekDay(); + } else { + month = child.getMonthOfLastWeekDay(); } - } else { - if (mShowWeekNumber) { - mDrawPaint.setColor(mWeekNumberColor); - int x = mWidth / divisor; - canvas.drawText(mDayNumbers[0], x, y, mDrawPaint); - i++; + + // And how it relates to our current highlighted month + int monthDiff; + if (mCurrentMonthDisplayed == 11 && month == 0) { + monthDiff = 1; + } else if (mCurrentMonthDisplayed == 0 && month == 11) { + monthDiff = -1; + } else { + monthDiff = month - mCurrentMonthDisplayed; } - for (; i < nDays; i++) { - mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor - : mUnfocusedMonthDateColor); - int x = (2 * i + 1) * mWidth / divisor; - canvas.drawText(mDayNumbers[i], x, y, mMonthNumDrawPaint); + + // Only switch months if we're scrolling away from the currently + // selected month + if ((!mIsScrollingUp && monthDiff > 0) || (mIsScrollingUp && monthDiff < 0)) { + Calendar firstDay = child.getFirstDay(); + if (mIsScrollingUp) { + firstDay.add(Calendar.DAY_OF_MONTH, -DAYS_PER_WEEK); + } else { + firstDay.add(Calendar.DAY_OF_MONTH, DAYS_PER_WEEK); + } + setMonthDisplayed(firstDay); } } + mPreviousScrollPosition = currScroll; + mPreviousScrollState = mCurrentScrollState; } /** - * Draws a horizontal line for separating the weeks. + * Sets the month displayed at the top of this view based on time. Override + * to add custom events when the title is changed. * - * @param canvas The canvas to draw on. + * @param calendar A day in the new focus month. */ - private void drawWeekSeparators(Canvas canvas) { - // If it is the topmost fully visible child do not draw separator line - int firstFullyVisiblePosition = mListView.getFirstVisiblePosition(); - if (mListView.getChildAt(0).getTop() < 0) { - firstFullyVisiblePosition++; + private void setMonthDisplayed(Calendar calendar) { + mCurrentMonthDisplayed = calendar.get(Calendar.MONTH); + mAdapter.setFocusMonth(mCurrentMonthDisplayed); + final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_MONTH_DAY + | DateUtils.FORMAT_SHOW_YEAR; + final long millis = calendar.getTimeInMillis(); + String newMonthName = DateUtils.formatDateRange(mContext, millis, millis, flags); + mMonthName.setText(newMonthName); + mMonthName.invalidate(); + } + + /** + * @return Returns the number of weeks between the current <code>date</code> + * and the <code>mMinDate</code>. + */ + private int getWeeksSinceMinDate(Calendar date) { + if (date.before(mMinDate)) { + throw new IllegalArgumentException("fromDate: " + mMinDate.getTime() + + " does not precede toDate: " + date.getTime()); } - if (firstFullyVisiblePosition == mWeek) { - return; + long endTimeMillis = date.getTimeInMillis() + + date.getTimeZone().getOffset(date.getTimeInMillis()); + long startTimeMillis = mMinDate.getTimeInMillis() + + mMinDate.getTimeZone().getOffset(mMinDate.getTimeInMillis()); + long dayOffsetMillis = (mMinDate.get(Calendar.DAY_OF_WEEK) - mFirstDayOfWeek) + * MILLIS_IN_DAY; + return (int) ((endTimeMillis - startTimeMillis + dayOffsetMillis) / MILLIS_IN_WEEK); + } + + /** + * Command responsible for acting upon scroll state changes. + */ + private class ScrollStateRunnable implements Runnable { + private AbsListView mView; + + private int mNewState; + + /** + * Sets up the runnable with a short delay in case the scroll state + * immediately changes again. + * + * @param view The list view that changed state + * @param scrollState The new state it changed to + */ + public void doScrollStateChange(AbsListView view, int scrollState) { + mView = view; + mNewState = scrollState; + mDelegator.removeCallbacks(this); + mDelegator.postDelayed(this, SCROLL_CHANGE_DELAY); } - mDrawPaint.setColor(mWeekSeparatorLineColor); - mDrawPaint.setStrokeWidth(mWeekSeperatorLineWidth); - float startX; - float stopX; - if (isLayoutRtl()) { - startX = 0; - stopX = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth; - } else { - startX = mShowWeekNumber ? mWidth / mNumCells : 0; - stopX = mWidth; + + public void run() { + mCurrentScrollState = mNewState; + // Fix the position after a scroll or a fling ends + if (mNewState == OnScrollListener.SCROLL_STATE_IDLE + && mPreviousScrollState != OnScrollListener.SCROLL_STATE_IDLE) { + View child = mView.getChildAt(0); + if (child == null) { + // The view is no longer visible, just return + return; + } + int dist = child.getBottom() - mListScrollTopOffset; + if (dist > mListScrollTopOffset) { + if (mIsScrollingUp) { + mView.smoothScrollBy(dist - child.getHeight(), + ADJUSTMENT_SCROLL_DURATION); + } else { + mView.smoothScrollBy(dist, ADJUSTMENT_SCROLL_DURATION); + } + } + } + mPreviousScrollState = mNewState; } - canvas.drawLine(startX, 0, stopX, 0, mDrawPaint); } /** - * Draws the selected date bars if this week has a selected day. - * - * @param canvas The canvas to draw on + * <p> + * This is a specialized adapter for creating a list of weeks with + * selectable days. It can be configured to display the week number, start + * the week on a given day, show a reduced number of days, or display an + * arbitrary number of weeks at a time. + * </p> */ - private void drawSelectedDateVerticalBars(Canvas canvas) { - if (!mHasSelectedDay) { - return; + private class WeeksAdapter extends BaseAdapter implements OnTouchListener { + + private int mSelectedWeek; + + private GestureDetector mGestureDetector; + + private int mFocusedMonth; + + private final Calendar mSelectedDate = Calendar.getInstance(); + + private int mTotalWeekCount; + + public WeeksAdapter(Context context) { + mContext = context; + mGestureDetector = new GestureDetector(mContext, new CalendarGestureListener()); + init(); } - mSelectedDateVerticalBar.setBounds(mSelectedLeft - mSelectedDateVerticalBarWidth / 2, - mWeekSeperatorLineWidth, - mSelectedLeft + mSelectedDateVerticalBarWidth / 2, mHeight); - mSelectedDateVerticalBar.draw(canvas); - mSelectedDateVerticalBar.setBounds(mSelectedRight - mSelectedDateVerticalBarWidth / 2, - mWeekSeperatorLineWidth, - mSelectedRight + mSelectedDateVerticalBarWidth / 2, mHeight); - mSelectedDateVerticalBar.draw(canvas); - } - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - mWidth = w; - updateSelectionPositions(); + /** + * Set up the gesture detector and selected time + */ + private void init() { + mSelectedWeek = getWeeksSinceMinDate(mSelectedDate); + mTotalWeekCount = getWeeksSinceMinDate(mMaxDate); + if (mMinDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek + || mMaxDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek) { + mTotalWeekCount++; + } + notifyDataSetChanged(); + } + + /** + * Updates the selected day and related parameters. + * + * @param selectedDay The time to highlight + */ + public void setSelectedDay(Calendar selectedDay) { + if (selectedDay.get(Calendar.DAY_OF_YEAR) == mSelectedDate.get(Calendar.DAY_OF_YEAR) + && selectedDay.get(Calendar.YEAR) == mSelectedDate.get(Calendar.YEAR)) { + return; + } + mSelectedDate.setTimeInMillis(selectedDay.getTimeInMillis()); + mSelectedWeek = getWeeksSinceMinDate(mSelectedDate); + mFocusedMonth = mSelectedDate.get(Calendar.MONTH); + notifyDataSetChanged(); + } + + /** + * @return The selected day of month. + */ + public Calendar getSelectedDay() { + return mSelectedDate; + } + + @Override + public int getCount() { + return mTotalWeekCount; + } + + @Override + public Object getItem(int position) { + return null; + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + WeekView weekView = null; + if (convertView != null) { + weekView = (WeekView) convertView; + } else { + weekView = new WeekView(mContext); + android.widget.AbsListView.LayoutParams params = + new android.widget.AbsListView.LayoutParams(LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT); + weekView.setLayoutParams(params); + weekView.setClickable(true); + weekView.setOnTouchListener(this); + } + + int selectedWeekDay = (mSelectedWeek == position) ? mSelectedDate.get( + Calendar.DAY_OF_WEEK) : -1; + weekView.init(position, selectedWeekDay, mFocusedMonth); + + return weekView; + } + + /** + * Changes which month is in focus and updates the view. + * + * @param month The month to show as in focus [0-11] + */ + public void setFocusMonth(int month) { + if (mFocusedMonth == month) { + return; + } + mFocusedMonth = month; + notifyDataSetChanged(); + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + if (mListView.isEnabled() && mGestureDetector.onTouchEvent(event)) { + WeekView weekView = (WeekView) v; + // if we cannot find a day for the given location we are done + if (!weekView.getDayFromLocation(event.getX(), mTempDate)) { + return true; + } + // it is possible that the touched day is outside the valid range + // we draw whole weeks but range end can fall not on the week end + if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) { + return true; + } + onDateTapped(mTempDate); + return true; + } + return false; + } + + /** + * Maintains the same hour/min/sec but moves the day to the tapped day. + * + * @param day The day that was tapped + */ + private void onDateTapped(Calendar day) { + setSelectedDay(day); + setMonthDisplayed(day); + } + + /** + * This is here so we can identify single tap events and set the + * selected day correctly + */ + class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener { + @Override + public boolean onSingleTapUp(MotionEvent e) { + return true; + } + } } /** - * This calculates the positions for the selected day lines. + * <p> + * This is a dynamic view for drawing a single week. It can be configured to + * display the week number, start the week on a given day, or show a reduced + * number of days. It is intended for use as a single view within a + * ListView. See {@link WeeksAdapter} for usage. + * </p> */ - private void updateSelectionPositions() { - if (mHasSelectedDay) { + private class WeekView extends View { + + private final Rect mTempRect = new Rect(); + + private final Paint mDrawPaint = new Paint(); + + private final Paint mMonthNumDrawPaint = new Paint(); + + // Cache the number strings so we don't have to recompute them each time + private String[] mDayNumbers; + + // Quick lookup for checking which days are in the focus month + private boolean[] mFocusDay; + + // Whether this view has a focused day. + private boolean mHasFocusedDay; + + // Whether this view has only focused days. + private boolean mHasUnfocusedDay; + + // The first day displayed by this item + private Calendar mFirstDay; + + // The month of the first day in this week + private int mMonthOfFirstWeekDay = -1; + + // The month of the last day in this week + private int mLastWeekDayMonth = -1; + + // The position of this week, equivalent to weeks since the week of Jan + // 1st, 1900 + private int mWeek = -1; + + // Quick reference to the width of this view, matches parent + private int mWidth; + + // The height this view should draw at in pixels, set by height param + private int mHeight; + + // If this view contains the selected day + private boolean mHasSelectedDay = false; + + // Which day is selected [0-6] or -1 if no day is selected + private int mSelectedDay = -1; + + // The number of days + a spot for week number if it is displayed + private int mNumCells; + + // The left edge of the selected day + private int mSelectedLeft = -1; + + // The right edge of the selected day + private int mSelectedRight = -1; + + public WeekView(Context context) { + super(context); + + // Sets up any standard paints that will be used + initilaizePaints(); + } + + /** + * Initializes this week view. + * + * @param weekNumber The number of the week this view represents. The + * week number is a zero based index of the weeks since + * {@link CalendarView#getMinDate()}. + * @param selectedWeekDay The selected day of the week from 0 to 6, -1 if no + * selected day. + * @param focusedMonth The month that is currently in focus i.e. + * highlighted. + */ + public void init(int weekNumber, int selectedWeekDay, int focusedMonth) { + mSelectedDay = selectedWeekDay; + mHasSelectedDay = mSelectedDay != -1; + mNumCells = mShowWeekNumber ? mDaysPerWeek + 1 : mDaysPerWeek; + mWeek = weekNumber; + mTempDate.setTimeInMillis(mMinDate.getTimeInMillis()); + + mTempDate.add(Calendar.WEEK_OF_YEAR, mWeek); + mTempDate.setFirstDayOfWeek(mFirstDayOfWeek); + + // Allocate space for caching the day numbers and focus values + mDayNumbers = new String[mNumCells]; + mFocusDay = new boolean[mNumCells]; + + // If we're showing the week number calculate it based on Monday + int i = 0; + if (mShowWeekNumber) { + mDayNumbers[0] = String.format(Locale.getDefault(), "%d", + mTempDate.get(Calendar.WEEK_OF_YEAR)); + i++; + } + + // Now adjust our starting day based on the start day of the week + int diff = mFirstDayOfWeek - mTempDate.get(Calendar.DAY_OF_WEEK); + mTempDate.add(Calendar.DAY_OF_MONTH, diff); + + mFirstDay = (Calendar) mTempDate.clone(); + mMonthOfFirstWeekDay = mTempDate.get(Calendar.MONTH); + + mHasUnfocusedDay = true; + for (; i < mNumCells; i++) { + final boolean isFocusedDay = (mTempDate.get(Calendar.MONTH) == focusedMonth); + mFocusDay[i] = isFocusedDay; + mHasFocusedDay |= isFocusedDay; + mHasUnfocusedDay &= !isFocusedDay; + // do not draw dates outside the valid range to avoid user confusion + if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) { + mDayNumbers[i] = ""; + } else { + mDayNumbers[i] = String.format(Locale.getDefault(), "%d", + mTempDate.get(Calendar.DAY_OF_MONTH)); + } + mTempDate.add(Calendar.DAY_OF_MONTH, 1); + } + // We do one extra add at the end of the loop, if that pushed us to + // new month undo it + if (mTempDate.get(Calendar.DAY_OF_MONTH) == 1) { + mTempDate.add(Calendar.DAY_OF_MONTH, -1); + } + mLastWeekDayMonth = mTempDate.get(Calendar.MONTH); + + updateSelectionPositions(); + } + + /** + * Initialize the paint instances. + */ + private void initilaizePaints() { + mDrawPaint.setFakeBoldText(false); + mDrawPaint.setAntiAlias(true); + mDrawPaint.setStyle(Style.FILL); + + mMonthNumDrawPaint.setFakeBoldText(true); + mMonthNumDrawPaint.setAntiAlias(true); + mMonthNumDrawPaint.setStyle(Style.FILL); + mMonthNumDrawPaint.setTextAlign(Align.CENTER); + mMonthNumDrawPaint.setTextSize(mDateTextSize); + } + + /** + * Returns the month of the first day in this week. + * + * @return The month the first day of this view is in. + */ + public int getMonthOfFirstWeekDay() { + return mMonthOfFirstWeekDay; + } + + /** + * Returns the month of the last day in this week + * + * @return The month the last day of this view is in + */ + public int getMonthOfLastWeekDay() { + return mLastWeekDayMonth; + } + + /** + * Returns the first day in this view. + * + * @return The first day in the view. + */ + public Calendar getFirstDay() { + return mFirstDay; + } + + /** + * Calculates the day that the given x position is in, accounting for + * week number. + * + * @param x The x position of the touch event. + * @return True if a day was found for the given location. + */ + public boolean getDayFromLocation(float x, Calendar outCalendar) { final boolean isLayoutRtl = isLayoutRtl(); - int selectedPosition = mSelectedDay - mFirstDayOfWeek; - if (selectedPosition < 0) { - selectedPosition += 7; + + int start; + int end; + + if (isLayoutRtl) { + start = 0; + end = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth; + } else { + start = mShowWeekNumber ? mWidth / mNumCells : 0; + end = mWidth; + } + + if (x < start || x > end) { + outCalendar.clear(); + return false; + } + + // Selection is (x - start) / (pixels/day) which is (x - start) * day / pixels + int dayPosition = (int) ((x - start) * mDaysPerWeek / (end - start)); + + if (isLayoutRtl) { + dayPosition = mDaysPerWeek - 1 - dayPosition; + } + + outCalendar.setTimeInMillis(mFirstDay.getTimeInMillis()); + outCalendar.add(Calendar.DAY_OF_MONTH, dayPosition); + + return true; + } + + @Override + protected void onDraw(Canvas canvas) { + drawBackground(canvas); + drawWeekNumbersAndDates(canvas); + drawWeekSeparators(canvas); + drawSelectedDateVerticalBars(canvas); + } + + /** + * This draws the selection highlight if a day is selected in this week. + * + * @param canvas The canvas to draw on + */ + private void drawBackground(Canvas canvas) { + if (!mHasSelectedDay) { + return; } - if (mShowWeekNumber && !isLayoutRtl) { - selectedPosition++; + mDrawPaint.setColor(mSelectedWeekBackgroundColor); + + mTempRect.top = mWeekSeperatorLineWidth; + mTempRect.bottom = mHeight; + + final boolean isLayoutRtl = isLayoutRtl(); + + if (isLayoutRtl) { + mTempRect.left = 0; + mTempRect.right = mSelectedLeft - 2; + } else { + mTempRect.left = mShowWeekNumber ? mWidth / mNumCells : 0; + mTempRect.right = mSelectedLeft - 2; } + canvas.drawRect(mTempRect, mDrawPaint); + if (isLayoutRtl) { - mSelectedLeft = (mDaysPerWeek - 1 - selectedPosition) * mWidth / mNumCells; + mTempRect.left = mSelectedRight + 3; + mTempRect.right = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth; + } else { + mTempRect.left = mSelectedRight + 3; + mTempRect.right = mWidth; + } + canvas.drawRect(mTempRect, mDrawPaint); + } + /** + * Draws the week and month day numbers for this week. + * + * @param canvas The canvas to draw on + */ + private void drawWeekNumbersAndDates(Canvas canvas) { + final float textHeight = mDrawPaint.getTextSize(); + final int y = (int) ((mHeight + textHeight) / 2) - mWeekSeperatorLineWidth; + final int nDays = mNumCells; + final int divisor = 2 * nDays; + + mDrawPaint.setTextAlign(Align.CENTER); + mDrawPaint.setTextSize(mDateTextSize); + + int i = 0; + + if (isLayoutRtl()) { + for (; i < nDays - 1; i++) { + mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor + : mUnfocusedMonthDateColor); + int x = (2 * i + 1) * mWidth / divisor; + canvas.drawText(mDayNumbers[nDays - 1 - i], x, y, mMonthNumDrawPaint); + } + if (mShowWeekNumber) { + mDrawPaint.setColor(mWeekNumberColor); + int x = mWidth - mWidth / divisor; + canvas.drawText(mDayNumbers[0], x, y, mDrawPaint); + } } else { - mSelectedLeft = selectedPosition * mWidth / mNumCells; + if (mShowWeekNumber) { + mDrawPaint.setColor(mWeekNumberColor); + int x = mWidth / divisor; + canvas.drawText(mDayNumbers[0], x, y, mDrawPaint); + i++; + } + for (; i < nDays; i++) { + mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor + : mUnfocusedMonthDateColor); + int x = (2 * i + 1) * mWidth / divisor; + canvas.drawText(mDayNumbers[i], x, y, mMonthNumDrawPaint); + } } - mSelectedRight = mSelectedLeft + mWidth / mNumCells; } - } - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - mHeight = (mListView.getHeight() - mListView.getPaddingTop() - mListView - .getPaddingBottom()) / mShownWeekCount; - setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mHeight); + /** + * Draws a horizontal line for separating the weeks. + * + * @param canvas The canvas to draw on. + */ + private void drawWeekSeparators(Canvas canvas) { + // If it is the topmost fully visible child do not draw separator line + int firstFullyVisiblePosition = mListView.getFirstVisiblePosition(); + if (mListView.getChildAt(0).getTop() < 0) { + firstFullyVisiblePosition++; + } + if (firstFullyVisiblePosition == mWeek) { + return; + } + mDrawPaint.setColor(mWeekSeparatorLineColor); + mDrawPaint.setStrokeWidth(mWeekSeperatorLineWidth); + float startX; + float stopX; + if (isLayoutRtl()) { + startX = 0; + stopX = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth; + } else { + startX = mShowWeekNumber ? mWidth / mNumCells : 0; + stopX = mWidth; + } + canvas.drawLine(startX, 0, stopX, 0, mDrawPaint); + } + + /** + * Draws the selected date bars if this week has a selected day. + * + * @param canvas The canvas to draw on + */ + private void drawSelectedDateVerticalBars(Canvas canvas) { + if (!mHasSelectedDay) { + return; + } + mSelectedDateVerticalBar.setBounds( + mSelectedLeft - mSelectedDateVerticalBarWidth / 2, + mWeekSeperatorLineWidth, + mSelectedLeft + mSelectedDateVerticalBarWidth / 2, + mHeight); + mSelectedDateVerticalBar.draw(canvas); + mSelectedDateVerticalBar.setBounds( + mSelectedRight - mSelectedDateVerticalBarWidth / 2, + mWeekSeperatorLineWidth, + mSelectedRight + mSelectedDateVerticalBarWidth / 2, + mHeight); + mSelectedDateVerticalBar.draw(canvas); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + mWidth = w; + updateSelectionPositions(); + } + + /** + * This calculates the positions for the selected day lines. + */ + private void updateSelectionPositions() { + if (mHasSelectedDay) { + final boolean isLayoutRtl = isLayoutRtl(); + int selectedPosition = mSelectedDay - mFirstDayOfWeek; + if (selectedPosition < 0) { + selectedPosition += 7; + } + if (mShowWeekNumber && !isLayoutRtl) { + selectedPosition++; + } + if (isLayoutRtl) { + mSelectedLeft = (mDaysPerWeek - 1 - selectedPosition) * mWidth / mNumCells; + + } else { + mSelectedLeft = selectedPosition * mWidth / mNumCells; + } + mSelectedRight = mSelectedLeft + mWidth / mNumCells; + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + mHeight = (mListView.getHeight() - mListView.getPaddingTop() - mListView + .getPaddingBottom()) / mShownWeekCount; + setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mHeight); + } } + } + } diff --git a/core/java/android/widget/CheckBox.java b/core/java/android/widget/CheckBox.java index f1804f8..71438c9 100644 --- a/core/java/android/widget/CheckBox.java +++ b/core/java/android/widget/CheckBox.java @@ -64,8 +64,12 @@ public class CheckBox extends CompoundButton { this(context, attrs, com.android.internal.R.attr.checkboxStyle); } - public CheckBox(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public CheckBox(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public CheckBox(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); } @Override diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java index 5c10a77..1533510 100644 --- a/core/java/android/widget/CheckedTextView.java +++ b/core/java/android/widget/CheckedTextView.java @@ -58,11 +58,15 @@ public class CheckedTextView extends TextView implements Checkable { this(context, attrs, R.attr.checkedTextViewStyle); } - public CheckedTextView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public CheckedTextView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public CheckedTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); - TypedArray a = context.obtainStyledAttributes(attrs, - R.styleable.CheckedTextView, defStyle, 0); + final TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.CheckedTextView, defStyleAttr, defStyleRes); Drawable d = a.getDrawable(R.styleable.CheckedTextView_checkMark); if (d != null) { @@ -119,7 +123,7 @@ public class CheckedTextView extends TextView implements Checkable { Drawable d = null; if (mCheckMarkResource != 0) { - d = getResources().getDrawable(mCheckMarkResource); + d = getContext().getDrawable(mCheckMarkResource); } setCheckMarkDrawable(d); } diff --git a/core/java/android/widget/Chronometer.java b/core/java/android/widget/Chronometer.java index b7a126e..f94789d 100644 --- a/core/java/android/widget/Chronometer.java +++ b/core/java/android/widget/Chronometer.java @@ -18,14 +18,12 @@ package android.widget; import android.content.Context; import android.content.res.TypedArray; -import android.graphics.Canvas; import android.os.Handler; import android.os.Message; import android.os.SystemClock; import android.text.format.DateUtils; import android.util.AttributeSet; import android.util.Log; -import android.util.Slog; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.RemoteViews.RemoteView; @@ -96,12 +94,15 @@ public class Chronometer extends TextView { * Initialize with standard view layout information and style. * Sets the base to the current time. */ - public Chronometer(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public Chronometer(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public Chronometer(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); - TypedArray a = context.obtainStyledAttributes( - attrs, - com.android.internal.R.styleable.Chronometer, defStyle, 0); + final TypedArray a = context.obtainStyledAttributes( + attrs, com.android.internal.R.styleable.Chronometer, defStyleAttr, defStyleRes); setFormat(a.getString(com.android.internal.R.styleable.Chronometer_format)); a.recycle(); diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java index abddc90..4298545 100644 --- a/core/java/android/widget/CompoundButton.java +++ b/core/java/android/widget/CompoundButton.java @@ -64,12 +64,15 @@ public abstract class CompoundButton extends Button implements Checkable { this(context, attrs, 0); } - public CompoundButton(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public CompoundButton(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public CompoundButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); - TypedArray a = - context.obtainStyledAttributes( - attrs, com.android.internal.R.styleable.CompoundButton, defStyle, 0); + final TypedArray a = context.obtainStyledAttributes( + attrs, com.android.internal.R.styleable.CompoundButton, defStyleAttr, defStyleRes); Drawable d = a.getDrawable(com.android.internal.R.styleable.CompoundButton_button); if (d != null) { @@ -183,7 +186,7 @@ public abstract class CompoundButton extends Button implements Checkable { Drawable d = null; if (mButtonResource != 0) { - d = getResources().getDrawable(mButtonResource); + d = getContext().getDrawable(mButtonResource); } setButtonDrawable(d); } diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java index d03161e..265dbcd 100644 --- a/core/java/android/widget/DatePicker.java +++ b/core/java/android/widget/DatePicker.java @@ -24,7 +24,6 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import android.text.InputType; -import android.text.format.DateFormat; import android.text.format.DateUtils; import android.util.AttributeSet; import android.util.Log; @@ -76,53 +75,7 @@ public class DatePicker extends FrameLayout { private static final String LOG_TAG = DatePicker.class.getSimpleName(); - private static final String DATE_FORMAT = "MM/dd/yyyy"; - - private static final int DEFAULT_START_YEAR = 1900; - - private static final int DEFAULT_END_YEAR = 2100; - - private static final boolean DEFAULT_CALENDAR_VIEW_SHOWN = true; - - private static final boolean DEFAULT_SPINNERS_SHOWN = true; - - private static final boolean DEFAULT_ENABLED_STATE = true; - - private final LinearLayout mSpinners; - - private final NumberPicker mDaySpinner; - - private final NumberPicker mMonthSpinner; - - private final NumberPicker mYearSpinner; - - private final EditText mDaySpinnerInput; - - private final EditText mMonthSpinnerInput; - - private final EditText mYearSpinnerInput; - - private final CalendarView mCalendarView; - - private Locale mCurrentLocale; - - private OnDateChangedListener mOnDateChangedListener; - - private String[] mShortMonths; - - private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT); - - private int mNumberOfMonths; - - private Calendar mTempDate; - - private Calendar mMinDate; - - private Calendar mMaxDate; - - private Calendar mCurrentDate; - - private boolean mIsEnabled = DEFAULT_ENABLED_STATE; + private DatePickerDelegate mDelegate; /** * The callback used to indicate the user changes\d the date. @@ -149,147 +102,61 @@ public class DatePicker extends FrameLayout { this(context, attrs, R.attr.datePickerStyle); } - public DatePicker(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - // initialization based on locale - setCurrentLocale(Locale.getDefault()); - - TypedArray attributesArray = context.obtainStyledAttributes(attrs, R.styleable.DatePicker, - defStyle, 0); - boolean spinnersShown = attributesArray.getBoolean(R.styleable.DatePicker_spinnersShown, - DEFAULT_SPINNERS_SHOWN); - boolean calendarViewShown = attributesArray.getBoolean( - R.styleable.DatePicker_calendarViewShown, DEFAULT_CALENDAR_VIEW_SHOWN); - int startYear = attributesArray.getInt(R.styleable.DatePicker_startYear, - DEFAULT_START_YEAR); - int endYear = attributesArray.getInt(R.styleable.DatePicker_endYear, DEFAULT_END_YEAR); - String minDate = attributesArray.getString(R.styleable.DatePicker_minDate); - String maxDate = attributesArray.getString(R.styleable.DatePicker_maxDate); - int layoutResourceId = attributesArray.getResourceId(R.styleable.DatePicker_internalLayout, - R.layout.date_picker); - attributesArray.recycle(); - - LayoutInflater inflater = (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - inflater.inflate(layoutResourceId, this, true); - - OnValueChangeListener onChangeListener = new OnValueChangeListener() { - public void onValueChange(NumberPicker picker, int oldVal, int newVal) { - updateInputState(); - mTempDate.setTimeInMillis(mCurrentDate.getTimeInMillis()); - // take care of wrapping of days and months to update greater fields - if (picker == mDaySpinner) { - int maxDayOfMonth = mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH); - if (oldVal == maxDayOfMonth && newVal == 1) { - mTempDate.add(Calendar.DAY_OF_MONTH, 1); - } else if (oldVal == 1 && newVal == maxDayOfMonth) { - mTempDate.add(Calendar.DAY_OF_MONTH, -1); - } else { - mTempDate.add(Calendar.DAY_OF_MONTH, newVal - oldVal); - } - } else if (picker == mMonthSpinner) { - if (oldVal == 11 && newVal == 0) { - mTempDate.add(Calendar.MONTH, 1); - } else if (oldVal == 0 && newVal == 11) { - mTempDate.add(Calendar.MONTH, -1); - } else { - mTempDate.add(Calendar.MONTH, newVal - oldVal); - } - } else if (picker == mYearSpinner) { - mTempDate.set(Calendar.YEAR, newVal); - } else { - throw new IllegalArgumentException(); - } - // now set the date to the adjusted one - setDate(mTempDate.get(Calendar.YEAR), mTempDate.get(Calendar.MONTH), - mTempDate.get(Calendar.DAY_OF_MONTH)); - updateSpinners(); - updateCalendarView(); - notifyDateChanged(); - } - }; + public DatePicker(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } - mSpinners = (LinearLayout) findViewById(R.id.pickers); + public DatePicker(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); - // calendar view day-picker - mCalendarView = (CalendarView) findViewById(R.id.calendar_view); - mCalendarView.setOnDateChangeListener(new CalendarView.OnDateChangeListener() { - public void onSelectedDayChange(CalendarView view, int year, int month, int monthDay) { - setDate(year, month, monthDay); - updateSpinners(); - notifyDateChanged(); - } - }); - - // day - mDaySpinner = (NumberPicker) findViewById(R.id.day); - mDaySpinner.setFormatter(NumberPicker.getTwoDigitFormatter()); - mDaySpinner.setOnLongPressUpdateInterval(100); - mDaySpinner.setOnValueChangedListener(onChangeListener); - mDaySpinnerInput = (EditText) mDaySpinner.findViewById(R.id.numberpicker_input); - - // month - mMonthSpinner = (NumberPicker) findViewById(R.id.month); - mMonthSpinner.setMinValue(0); - mMonthSpinner.setMaxValue(mNumberOfMonths - 1); - mMonthSpinner.setDisplayedValues(mShortMonths); - mMonthSpinner.setOnLongPressUpdateInterval(200); - mMonthSpinner.setOnValueChangedListener(onChangeListener); - mMonthSpinnerInput = (EditText) mMonthSpinner.findViewById(R.id.numberpicker_input); - - // year - mYearSpinner = (NumberPicker) findViewById(R.id.year); - mYearSpinner.setOnLongPressUpdateInterval(100); - mYearSpinner.setOnValueChangedListener(onChangeListener); - mYearSpinnerInput = (EditText) mYearSpinner.findViewById(R.id.numberpicker_input); - - // show only what the user required but make sure we - // show something and the spinners have higher priority - if (!spinnersShown && !calendarViewShown) { - setSpinnersShown(true); - } else { - setSpinnersShown(spinnersShown); - setCalendarViewShown(calendarViewShown); - } - - // set the min date giving priority of the minDate over startYear - mTempDate.clear(); - if (!TextUtils.isEmpty(minDate)) { - if (!parseDate(minDate, mTempDate)) { - mTempDate.set(startYear, 0, 1); - } - } else { - mTempDate.set(startYear, 0, 1); - } - setMinDate(mTempDate.getTimeInMillis()); + mDelegate = new LegacyDatePickerDelegate(this, context, attrs, defStyleAttr, defStyleRes); + } - // set the max date giving priority of the maxDate over endYear - mTempDate.clear(); - if (!TextUtils.isEmpty(maxDate)) { - if (!parseDate(maxDate, mTempDate)) { - mTempDate.set(endYear, 11, 31); - } - } else { - mTempDate.set(endYear, 11, 31); - } - setMaxDate(mTempDate.getTimeInMillis()); + /** + * Initialize the state. If the provided values designate an inconsistent + * date the values are normalized before updating the spinners. + * + * @param year The initial year. + * @param monthOfYear The initial month <strong>starting from zero</strong>. + * @param dayOfMonth The initial day of the month. + * @param onDateChangedListener How user is notified date is changed by + * user, can be null. + */ + public void init(int year, int monthOfYear, int dayOfMonth, + OnDateChangedListener onDateChangedListener) { + mDelegate.init(year, monthOfYear, dayOfMonth, onDateChangedListener); + } - // initialize to current date - mCurrentDate.setTimeInMillis(System.currentTimeMillis()); - init(mCurrentDate.get(Calendar.YEAR), mCurrentDate.get(Calendar.MONTH), mCurrentDate - .get(Calendar.DAY_OF_MONTH), null); + /** + * Updates the current date. + * + * @param year The year. + * @param month The month which is <strong>starting from zero</strong>. + * @param dayOfMonth The day of the month. + */ + public void updateDate(int year, int month, int dayOfMonth) { + mDelegate.updateDate(year, month, dayOfMonth); + } - // re-order the number spinners to match the current date format - reorderSpinners(); + /** + * @return The selected year. + */ + public int getYear() { + return mDelegate.getYear(); + } - // accessibility - setContentDescriptions(); + /** + * @return The selected month. + */ + public int getMonth() { + return mDelegate.getMonth(); + } - // If not explicitly specified this view is important for accessibility. - if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { - setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); - } + /** + * @return The selected day of month. + */ + public int getDayOfMonth() { + return mDelegate.getDayOfMonth(); } /** @@ -303,7 +170,7 @@ public class DatePicker extends FrameLayout { * @return The minimal supported date. */ public long getMinDate() { - return mCalendarView.getMinDate(); + return mDelegate.getMinDate(); } /** @@ -314,18 +181,7 @@ public class DatePicker extends FrameLayout { * @param minDate The minimal supported date. */ public void setMinDate(long minDate) { - mTempDate.setTimeInMillis(minDate); - if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR) - && mTempDate.get(Calendar.DAY_OF_YEAR) != mMinDate.get(Calendar.DAY_OF_YEAR)) { - return; - } - mMinDate.setTimeInMillis(minDate); - mCalendarView.setMinDate(minDate); - if (mCurrentDate.before(mMinDate)) { - mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis()); - updateCalendarView(); - } - updateSpinners(); + mDelegate.setMinDate(minDate); } /** @@ -339,7 +195,7 @@ public class DatePicker extends FrameLayout { * @return The maximal supported date. */ public long getMaxDate() { - return mCalendarView.getMaxDate(); + return mDelegate.getMaxDate(); } /** @@ -350,70 +206,50 @@ public class DatePicker extends FrameLayout { * @param maxDate The maximal supported date. */ public void setMaxDate(long maxDate) { - mTempDate.setTimeInMillis(maxDate); - if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR) - && mTempDate.get(Calendar.DAY_OF_YEAR) != mMaxDate.get(Calendar.DAY_OF_YEAR)) { - return; - } - mMaxDate.setTimeInMillis(maxDate); - mCalendarView.setMaxDate(maxDate); - if (mCurrentDate.after(mMaxDate)) { - mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis()); - updateCalendarView(); - } - updateSpinners(); + mDelegate.setMaxDate(maxDate); } @Override public void setEnabled(boolean enabled) { - if (mIsEnabled == enabled) { + if (mDelegate.isEnabled() == enabled) { return; } super.setEnabled(enabled); - mDaySpinner.setEnabled(enabled); - mMonthSpinner.setEnabled(enabled); - mYearSpinner.setEnabled(enabled); - mCalendarView.setEnabled(enabled); - mIsEnabled = enabled; + mDelegate.setEnabled(enabled); } @Override public boolean isEnabled() { - return mIsEnabled; + return mDelegate.isEnabled(); } @Override public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { - onPopulateAccessibilityEvent(event); - return true; + return mDelegate.dispatchPopulateAccessibilityEvent(event); } @Override public void onPopulateAccessibilityEvent(AccessibilityEvent event) { super.onPopulateAccessibilityEvent(event); - - final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR; - String selectedDateUtterance = DateUtils.formatDateTime(mContext, - mCurrentDate.getTimeInMillis(), flags); - event.getText().add(selectedDateUtterance); + mDelegate.onPopulateAccessibilityEvent(event); } @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); - event.setClassName(DatePicker.class.getName()); + mDelegate.onInitializeAccessibilityEvent(event); } @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); - info.setClassName(DatePicker.class.getName()); + mDelegate.onInitializeAccessibilityNodeInfo(info); } @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - setCurrentLocale(newConfig.locale); + mDelegate.onConfigurationChanged(newConfig); } /** @@ -423,7 +259,7 @@ public class DatePicker extends FrameLayout { * @see #getCalendarView() */ public boolean getCalendarViewShown() { - return (mCalendarView.getVisibility() == View.VISIBLE); + return mDelegate.getCalendarViewShown(); } /** @@ -433,7 +269,7 @@ public class DatePicker extends FrameLayout { * @see #getCalendarViewShown() */ public CalendarView getCalendarView () { - return mCalendarView; + return mDelegate.getCalendarView(); } /** @@ -442,7 +278,7 @@ public class DatePicker extends FrameLayout { * @param shown True if the calendar view is to be shown. */ public void setCalendarViewShown(boolean shown) { - mCalendarView.setVisibility(shown ? VISIBLE : GONE); + mDelegate.setCalendarViewShown(shown); } /** @@ -451,7 +287,7 @@ public class DatePicker extends FrameLayout { * @return True if the spinners are shown. */ public boolean getSpinnersShown() { - return mSpinners.isShown(); + return mDelegate.getSpinnersShown(); } /** @@ -460,330 +296,708 @@ public class DatePicker extends FrameLayout { * @param shown True if the spinners are to be shown. */ public void setSpinnersShown(boolean shown) { - mSpinners.setVisibility(shown ? VISIBLE : GONE); + mDelegate.setSpinnersShown(shown); + } + + // Override so we are in complete control of save / restore for this widget. + @Override + protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { + mDelegate.dispatchRestoreInstanceState(container); + } + + @Override + protected Parcelable onSaveInstanceState() { + Parcelable superState = super.onSaveInstanceState(); + return mDelegate.onSaveInstanceState(superState); + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + SavedState ss = (SavedState) state; + super.onRestoreInstanceState(ss.getSuperState()); + mDelegate.onRestoreInstanceState(ss); } /** - * Sets the current locale. - * - * @param locale The current locale. + * A delegate interface that defined the public API of the DatePicker. Allows different + * DatePicker implementations. This would need to be implemented by the DatePicker delegates + * for the real behavior. */ - private void setCurrentLocale(Locale locale) { - if (locale.equals(mCurrentLocale)) { - return; - } + interface DatePickerDelegate { + void init(int year, int monthOfYear, int dayOfMonth, + OnDateChangedListener onDateChangedListener); - mCurrentLocale = locale; + void updateDate(int year, int month, int dayOfMonth); - mTempDate = getCalendarForLocale(mTempDate, locale); - mMinDate = getCalendarForLocale(mMinDate, locale); - mMaxDate = getCalendarForLocale(mMaxDate, locale); - mCurrentDate = getCalendarForLocale(mCurrentDate, locale); + int getYear(); + int getMonth(); + int getDayOfMonth(); - mNumberOfMonths = mTempDate.getActualMaximum(Calendar.MONTH) + 1; - mShortMonths = new DateFormatSymbols().getShortMonths(); + void setMinDate(long minDate); + long getMinDate(); - if (usingNumericMonths()) { - // We're in a locale where a date should either be all-numeric, or all-text. - // All-text would require custom NumberPicker formatters for day and year. - mShortMonths = new String[mNumberOfMonths]; - for (int i = 0; i < mNumberOfMonths; ++i) { - mShortMonths[i] = String.format("%d", i + 1); - } - } - } + void setMaxDate(long maxDate); + long getMaxDate(); - /** - * Tests whether the current locale is one where there are no real month names, - * such as Chinese, Japanese, or Korean locales. - */ - private boolean usingNumericMonths() { - return Character.isDigit(mShortMonths[Calendar.JANUARY].charAt(0)); + void setEnabled(boolean enabled); + boolean isEnabled(); + + CalendarView getCalendarView (); + + void setCalendarViewShown(boolean shown); + boolean getCalendarViewShown(); + + void setSpinnersShown(boolean shown); + boolean getSpinnersShown(); + + void onConfigurationChanged(Configuration newConfig); + + void dispatchRestoreInstanceState(SparseArray<Parcelable> container); + Parcelable onSaveInstanceState(Parcelable superState); + void onRestoreInstanceState(Parcelable state); + + boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event); + void onPopulateAccessibilityEvent(AccessibilityEvent event); + void onInitializeAccessibilityEvent(AccessibilityEvent event); + void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info); } /** - * Gets a calendar for locale bootstrapped with the value of a given calendar. - * - * @param oldCalendar The old calendar. - * @param locale The locale. + * An abstract class which can be used as a start for DatePicker implementations */ - private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) { - if (oldCalendar == null) { - return Calendar.getInstance(locale); - } else { - final long currentTimeMillis = oldCalendar.getTimeInMillis(); - Calendar newCalendar = Calendar.getInstance(locale); - newCalendar.setTimeInMillis(currentTimeMillis); - return newCalendar; + abstract static class AbstractTimePickerDelegate implements DatePickerDelegate { + // The delegator + protected DatePicker mDelegator; + + // The context + protected Context mContext; + + // The current locale + protected Locale mCurrentLocale; + + // Callbacks + protected OnDateChangedListener mOnDateChangedListener; + + public AbstractTimePickerDelegate(DatePicker delegator, Context context) { + mDelegator = delegator; + mContext = context; + + // initialization based on locale + setCurrentLocale(Locale.getDefault()); } - } - /** - * Reorders the spinners according to the date format that is - * explicitly set by the user and if no such is set fall back - * to the current locale's default format. - */ - private void reorderSpinners() { - mSpinners.removeAllViews(); - // We use numeric spinners for year and day, but textual months. Ask icu4c what - // order the user's locale uses for that combination. http://b/7207103. - String pattern = ICU.getBestDateTimePattern("yyyyMMMdd", Locale.getDefault().toString()); - char[] order = ICU.getDateFormatOrder(pattern); - final int spinnerCount = order.length; - for (int i = 0; i < spinnerCount; i++) { - switch (order[i]) { - case 'd': - mSpinners.addView(mDaySpinner); - setImeOptions(mDaySpinner, spinnerCount, i); - break; - case 'M': - mSpinners.addView(mMonthSpinner); - setImeOptions(mMonthSpinner, spinnerCount, i); - break; - case 'y': - mSpinners.addView(mYearSpinner); - setImeOptions(mYearSpinner, spinnerCount, i); - break; - default: - throw new IllegalArgumentException(Arrays.toString(order)); + protected void setCurrentLocale(Locale locale) { + if (locale.equals(mCurrentLocale)) { + return; } + mCurrentLocale = locale; } } /** - * Updates the current date. - * - * @param year The year. - * @param month The month which is <strong>starting from zero</strong>. - * @param dayOfMonth The day of the month. + * A delegate implementing the basic DatePicker */ - public void updateDate(int year, int month, int dayOfMonth) { - if (!isNewDate(year, month, dayOfMonth)) { - return; + private static class LegacyDatePickerDelegate extends AbstractTimePickerDelegate { + + private static final String DATE_FORMAT = "MM/dd/yyyy"; + + private static final int DEFAULT_START_YEAR = 1900; + + private static final int DEFAULT_END_YEAR = 2100; + + private static final boolean DEFAULT_CALENDAR_VIEW_SHOWN = true; + + private static final boolean DEFAULT_SPINNERS_SHOWN = true; + + private static final boolean DEFAULT_ENABLED_STATE = true; + + private final LinearLayout mSpinners; + + private final NumberPicker mDaySpinner; + + private final NumberPicker mMonthSpinner; + + private final NumberPicker mYearSpinner; + + private final EditText mDaySpinnerInput; + + private final EditText mMonthSpinnerInput; + + private final EditText mYearSpinnerInput; + + private final CalendarView mCalendarView; + + private String[] mShortMonths; + + private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT); + + private int mNumberOfMonths; + + private Calendar mTempDate; + + private Calendar mMinDate; + + private Calendar mMaxDate; + + private Calendar mCurrentDate; + + private boolean mIsEnabled = DEFAULT_ENABLED_STATE; + + LegacyDatePickerDelegate(DatePicker delegator, Context context, AttributeSet attrs, + int defStyleAttr, int defStyleRes) { + super(delegator, context); + + mDelegator = delegator; + mContext = context; + + // initialization based on locale + setCurrentLocale(Locale.getDefault()); + + final TypedArray attributesArray = context.obtainStyledAttributes(attrs, + R.styleable.DatePicker, defStyleAttr, defStyleRes); + boolean spinnersShown = attributesArray.getBoolean(R.styleable.DatePicker_spinnersShown, + DEFAULT_SPINNERS_SHOWN); + boolean calendarViewShown = attributesArray.getBoolean( + R.styleable.DatePicker_calendarViewShown, DEFAULT_CALENDAR_VIEW_SHOWN); + int startYear = attributesArray.getInt(R.styleable.DatePicker_startYear, + DEFAULT_START_YEAR); + int endYear = attributesArray.getInt(R.styleable.DatePicker_endYear, DEFAULT_END_YEAR); + String minDate = attributesArray.getString(R.styleable.DatePicker_minDate); + String maxDate = attributesArray.getString(R.styleable.DatePicker_maxDate); + int layoutResourceId = attributesArray.getResourceId( + R.styleable.DatePicker_internalLayout, R.layout.date_picker); + attributesArray.recycle(); + + LayoutInflater inflater = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + inflater.inflate(layoutResourceId, mDelegator, true); + + OnValueChangeListener onChangeListener = new OnValueChangeListener() { + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + updateInputState(); + mTempDate.setTimeInMillis(mCurrentDate.getTimeInMillis()); + // take care of wrapping of days and months to update greater fields + if (picker == mDaySpinner) { + int maxDayOfMonth = mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH); + if (oldVal == maxDayOfMonth && newVal == 1) { + mTempDate.add(Calendar.DAY_OF_MONTH, 1); + } else if (oldVal == 1 && newVal == maxDayOfMonth) { + mTempDate.add(Calendar.DAY_OF_MONTH, -1); + } else { + mTempDate.add(Calendar.DAY_OF_MONTH, newVal - oldVal); + } + } else if (picker == mMonthSpinner) { + if (oldVal == 11 && newVal == 0) { + mTempDate.add(Calendar.MONTH, 1); + } else if (oldVal == 0 && newVal == 11) { + mTempDate.add(Calendar.MONTH, -1); + } else { + mTempDate.add(Calendar.MONTH, newVal - oldVal); + } + } else if (picker == mYearSpinner) { + mTempDate.set(Calendar.YEAR, newVal); + } else { + throw new IllegalArgumentException(); + } + // now set the date to the adjusted one + setDate(mTempDate.get(Calendar.YEAR), mTempDate.get(Calendar.MONTH), + mTempDate.get(Calendar.DAY_OF_MONTH)); + updateSpinners(); + updateCalendarView(); + notifyDateChanged(); + } + }; + + mSpinners = (LinearLayout) mDelegator.findViewById(R.id.pickers); + + // calendar view day-picker + mCalendarView = (CalendarView) mDelegator.findViewById(R.id.calendar_view); + mCalendarView.setOnDateChangeListener(new CalendarView.OnDateChangeListener() { + public void onSelectedDayChange(CalendarView view, int year, int month, int monthDay) { + setDate(year, month, monthDay); + updateSpinners(); + notifyDateChanged(); + } + }); + + // day + mDaySpinner = (NumberPicker) mDelegator.findViewById(R.id.day); + mDaySpinner.setFormatter(NumberPicker.getTwoDigitFormatter()); + mDaySpinner.setOnLongPressUpdateInterval(100); + mDaySpinner.setOnValueChangedListener(onChangeListener); + mDaySpinnerInput = (EditText) mDaySpinner.findViewById(R.id.numberpicker_input); + + // month + mMonthSpinner = (NumberPicker) mDelegator.findViewById(R.id.month); + mMonthSpinner.setMinValue(0); + mMonthSpinner.setMaxValue(mNumberOfMonths - 1); + mMonthSpinner.setDisplayedValues(mShortMonths); + mMonthSpinner.setOnLongPressUpdateInterval(200); + mMonthSpinner.setOnValueChangedListener(onChangeListener); + mMonthSpinnerInput = (EditText) mMonthSpinner.findViewById(R.id.numberpicker_input); + + // year + mYearSpinner = (NumberPicker) mDelegator.findViewById(R.id.year); + mYearSpinner.setOnLongPressUpdateInterval(100); + mYearSpinner.setOnValueChangedListener(onChangeListener); + mYearSpinnerInput = (EditText) mYearSpinner.findViewById(R.id.numberpicker_input); + + // show only what the user required but make sure we + // show something and the spinners have higher priority + if (!spinnersShown && !calendarViewShown) { + setSpinnersShown(true); + } else { + setSpinnersShown(spinnersShown); + setCalendarViewShown(calendarViewShown); + } + + // set the min date giving priority of the minDate over startYear + mTempDate.clear(); + if (!TextUtils.isEmpty(minDate)) { + if (!parseDate(minDate, mTempDate)) { + mTempDate.set(startYear, 0, 1); + } + } else { + mTempDate.set(startYear, 0, 1); + } + setMinDate(mTempDate.getTimeInMillis()); + + // set the max date giving priority of the maxDate over endYear + mTempDate.clear(); + if (!TextUtils.isEmpty(maxDate)) { + if (!parseDate(maxDate, mTempDate)) { + mTempDate.set(endYear, 11, 31); + } + } else { + mTempDate.set(endYear, 11, 31); + } + setMaxDate(mTempDate.getTimeInMillis()); + + // initialize to current date + mCurrentDate.setTimeInMillis(System.currentTimeMillis()); + init(mCurrentDate.get(Calendar.YEAR), mCurrentDate.get(Calendar.MONTH), mCurrentDate + .get(Calendar.DAY_OF_MONTH), null); + + // re-order the number spinners to match the current date format + reorderSpinners(); + + // accessibility + setContentDescriptions(); + + // If not explicitly specified this view is important for accessibility. + if (mDelegator.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { + mDelegator.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); + } } - setDate(year, month, dayOfMonth); - updateSpinners(); - updateCalendarView(); - notifyDateChanged(); - } - // Override so we are in complete control of save / restore for this widget. - @Override - protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { - dispatchThawSelfOnly(container); - } + @Override + public void init(int year, int monthOfYear, int dayOfMonth, + OnDateChangedListener onDateChangedListener) { + setDate(year, monthOfYear, dayOfMonth); + updateSpinners(); + updateCalendarView(); + mOnDateChangedListener = onDateChangedListener; + } - @Override - protected Parcelable onSaveInstanceState() { - Parcelable superState = super.onSaveInstanceState(); - return new SavedState(superState, getYear(), getMonth(), getDayOfMonth()); - } + @Override + public void updateDate(int year, int month, int dayOfMonth) { + if (!isNewDate(year, month, dayOfMonth)) { + return; + } + setDate(year, month, dayOfMonth); + updateSpinners(); + updateCalendarView(); + notifyDateChanged(); + } - @Override - protected void onRestoreInstanceState(Parcelable state) { - SavedState ss = (SavedState) state; - super.onRestoreInstanceState(ss.getSuperState()); - setDate(ss.mYear, ss.mMonth, ss.mDay); - updateSpinners(); - updateCalendarView(); - } + @Override + public int getYear() { + return mCurrentDate.get(Calendar.YEAR); + } - /** - * Initialize the state. If the provided values designate an inconsistent - * date the values are normalized before updating the spinners. - * - * @param year The initial year. - * @param monthOfYear The initial month <strong>starting from zero</strong>. - * @param dayOfMonth The initial day of the month. - * @param onDateChangedListener How user is notified date is changed by - * user, can be null. - */ - public void init(int year, int monthOfYear, int dayOfMonth, - OnDateChangedListener onDateChangedListener) { - setDate(year, monthOfYear, dayOfMonth); - updateSpinners(); - updateCalendarView(); - mOnDateChangedListener = onDateChangedListener; - } + @Override + public int getMonth() { + return mCurrentDate.get(Calendar.MONTH); + } - /** - * Parses the given <code>date</code> and in case of success sets the result - * to the <code>outDate</code>. - * - * @return True if the date was parsed. - */ - private boolean parseDate(String date, Calendar outDate) { - try { - outDate.setTime(mDateFormat.parse(date)); + @Override + public int getDayOfMonth() { + return mCurrentDate.get(Calendar.DAY_OF_MONTH); + } + + @Override + public void setMinDate(long minDate) { + mTempDate.setTimeInMillis(minDate); + if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR) + && mTempDate.get(Calendar.DAY_OF_YEAR) != mMinDate.get(Calendar.DAY_OF_YEAR)) { + return; + } + mMinDate.setTimeInMillis(minDate); + mCalendarView.setMinDate(minDate); + if (mCurrentDate.before(mMinDate)) { + mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis()); + updateCalendarView(); + } + updateSpinners(); + } + + @Override + public long getMinDate() { + return mCalendarView.getMinDate(); + } + + @Override + public void setMaxDate(long maxDate) { + mTempDate.setTimeInMillis(maxDate); + if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR) + && mTempDate.get(Calendar.DAY_OF_YEAR) != mMaxDate.get(Calendar.DAY_OF_YEAR)) { + return; + } + mMaxDate.setTimeInMillis(maxDate); + mCalendarView.setMaxDate(maxDate); + if (mCurrentDate.after(mMaxDate)) { + mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis()); + updateCalendarView(); + } + updateSpinners(); + } + + @Override + public long getMaxDate() { + return mCalendarView.getMaxDate(); + } + + @Override + public void setEnabled(boolean enabled) { + mDaySpinner.setEnabled(enabled); + mMonthSpinner.setEnabled(enabled); + mYearSpinner.setEnabled(enabled); + mCalendarView.setEnabled(enabled); + mIsEnabled = enabled; + } + + @Override + public boolean isEnabled() { + return mIsEnabled; + } + + @Override + public CalendarView getCalendarView() { + return mCalendarView; + } + + @Override + public void setCalendarViewShown(boolean shown) { + mCalendarView.setVisibility(shown ? VISIBLE : GONE); + } + + @Override + public boolean getCalendarViewShown() { + return (mCalendarView.getVisibility() == View.VISIBLE); + } + + @Override + public void setSpinnersShown(boolean shown) { + mSpinners.setVisibility(shown ? VISIBLE : GONE); + } + + @Override + public boolean getSpinnersShown() { + return mSpinners.isShown(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + setCurrentLocale(newConfig.locale); + } + + @Override + public void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { + mDelegator.dispatchThawSelfOnly(container); + } + + @Override + public Parcelable onSaveInstanceState(Parcelable superState) { + return new SavedState(superState, getYear(), getMonth(), getDayOfMonth()); + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + SavedState ss = (SavedState) state; + setDate(ss.mYear, ss.mMonth, ss.mDay); + updateSpinners(); + updateCalendarView(); + } + + @Override + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + onPopulateAccessibilityEvent(event); return true; - } catch (ParseException e) { - Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT); - return false; - } - } - - private boolean isNewDate(int year, int month, int dayOfMonth) { - return (mCurrentDate.get(Calendar.YEAR) != year - || mCurrentDate.get(Calendar.MONTH) != dayOfMonth - || mCurrentDate.get(Calendar.DAY_OF_MONTH) != month); - } - - private void setDate(int year, int month, int dayOfMonth) { - mCurrentDate.set(year, month, dayOfMonth); - if (mCurrentDate.before(mMinDate)) { - mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis()); - } else if (mCurrentDate.after(mMaxDate)) { - mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis()); - } - } - - private void updateSpinners() { - // set the spinner ranges respecting the min and max dates - if (mCurrentDate.equals(mMinDate)) { - mDaySpinner.setMinValue(mCurrentDate.get(Calendar.DAY_OF_MONTH)); - mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH)); - mDaySpinner.setWrapSelectorWheel(false); - mMonthSpinner.setDisplayedValues(null); - mMonthSpinner.setMinValue(mCurrentDate.get(Calendar.MONTH)); - mMonthSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.MONTH)); - mMonthSpinner.setWrapSelectorWheel(false); - } else if (mCurrentDate.equals(mMaxDate)) { - mDaySpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.DAY_OF_MONTH)); - mDaySpinner.setMaxValue(mCurrentDate.get(Calendar.DAY_OF_MONTH)); - mDaySpinner.setWrapSelectorWheel(false); - mMonthSpinner.setDisplayedValues(null); - mMonthSpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.MONTH)); - mMonthSpinner.setMaxValue(mCurrentDate.get(Calendar.MONTH)); - mMonthSpinner.setWrapSelectorWheel(false); - } else { - mDaySpinner.setMinValue(1); - mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH)); - mDaySpinner.setWrapSelectorWheel(true); - mMonthSpinner.setDisplayedValues(null); - mMonthSpinner.setMinValue(0); - mMonthSpinner.setMaxValue(11); - mMonthSpinner.setWrapSelectorWheel(true); } - // make sure the month names are a zero based array - // with the months in the month spinner - String[] displayedValues = Arrays.copyOfRange(mShortMonths, - mMonthSpinner.getMinValue(), mMonthSpinner.getMaxValue() + 1); - mMonthSpinner.setDisplayedValues(displayedValues); + @Override + public void onPopulateAccessibilityEvent(AccessibilityEvent event) { + final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR; + String selectedDateUtterance = DateUtils.formatDateTime(mContext, + mCurrentDate.getTimeInMillis(), flags); + event.getText().add(selectedDateUtterance); + } - // year spinner range does not change based on the current date - mYearSpinner.setMinValue(mMinDate.get(Calendar.YEAR)); - mYearSpinner.setMaxValue(mMaxDate.get(Calendar.YEAR)); - mYearSpinner.setWrapSelectorWheel(false); + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + event.setClassName(DatePicker.class.getName()); + } - // set the spinner values - mYearSpinner.setValue(mCurrentDate.get(Calendar.YEAR)); - mMonthSpinner.setValue(mCurrentDate.get(Calendar.MONTH)); - mDaySpinner.setValue(mCurrentDate.get(Calendar.DAY_OF_MONTH)); + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + info.setClassName(DatePicker.class.getName()); + } - if (usingNumericMonths()) { - mMonthSpinnerInput.setRawInputType(InputType.TYPE_CLASS_NUMBER); + /** + * Sets the current locale. + * + * @param locale The current locale. + */ + @Override + protected void setCurrentLocale(Locale locale) { + super.setCurrentLocale(locale); + + mTempDate = getCalendarForLocale(mTempDate, locale); + mMinDate = getCalendarForLocale(mMinDate, locale); + mMaxDate = getCalendarForLocale(mMaxDate, locale); + mCurrentDate = getCalendarForLocale(mCurrentDate, locale); + + mNumberOfMonths = mTempDate.getActualMaximum(Calendar.MONTH) + 1; + mShortMonths = new DateFormatSymbols().getShortMonths(); + + if (usingNumericMonths()) { + // We're in a locale where a date should either be all-numeric, or all-text. + // All-text would require custom NumberPicker formatters for day and year. + mShortMonths = new String[mNumberOfMonths]; + for (int i = 0; i < mNumberOfMonths; ++i) { + mShortMonths[i] = String.format("%d", i + 1); + } + } } - } - /** - * Updates the calendar view with the current date. - */ - private void updateCalendarView() { - mCalendarView.setDate(mCurrentDate.getTimeInMillis(), false, false); - } + /** + * Tests whether the current locale is one where there are no real month names, + * such as Chinese, Japanese, or Korean locales. + */ + private boolean usingNumericMonths() { + return Character.isDigit(mShortMonths[Calendar.JANUARY].charAt(0)); + } - /** - * @return The selected year. - */ - public int getYear() { - return mCurrentDate.get(Calendar.YEAR); - } + /** + * Gets a calendar for locale bootstrapped with the value of a given calendar. + * + * @param oldCalendar The old calendar. + * @param locale The locale. + */ + private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) { + if (oldCalendar == null) { + return Calendar.getInstance(locale); + } else { + final long currentTimeMillis = oldCalendar.getTimeInMillis(); + Calendar newCalendar = Calendar.getInstance(locale); + newCalendar.setTimeInMillis(currentTimeMillis); + return newCalendar; + } + } - /** - * @return The selected month. - */ - public int getMonth() { - return mCurrentDate.get(Calendar.MONTH); - } + /** + * Reorders the spinners according to the date format that is + * explicitly set by the user and if no such is set fall back + * to the current locale's default format. + */ + private void reorderSpinners() { + mSpinners.removeAllViews(); + // We use numeric spinners for year and day, but textual months. Ask icu4c what + // order the user's locale uses for that combination. http://b/7207103. + String pattern = ICU.getBestDateTimePattern("yyyyMMMdd", + Locale.getDefault().toString()); + char[] order = ICU.getDateFormatOrder(pattern); + final int spinnerCount = order.length; + for (int i = 0; i < spinnerCount; i++) { + switch (order[i]) { + case 'd': + mSpinners.addView(mDaySpinner); + setImeOptions(mDaySpinner, spinnerCount, i); + break; + case 'M': + mSpinners.addView(mMonthSpinner); + setImeOptions(mMonthSpinner, spinnerCount, i); + break; + case 'y': + mSpinners.addView(mYearSpinner); + setImeOptions(mYearSpinner, spinnerCount, i); + break; + default: + throw new IllegalArgumentException(Arrays.toString(order)); + } + } + } - /** - * @return The selected day of month. - */ - public int getDayOfMonth() { - return mCurrentDate.get(Calendar.DAY_OF_MONTH); - } + /** + * Parses the given <code>date</code> and in case of success sets the result + * to the <code>outDate</code>. + * + * @return True if the date was parsed. + */ + private boolean parseDate(String date, Calendar outDate) { + try { + outDate.setTime(mDateFormat.parse(date)); + return true; + } catch (ParseException e) { + Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT); + return false; + } + } - /** - * Notifies the listener, if such, for a change in the selected date. - */ - private void notifyDateChanged() { - sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); - if (mOnDateChangedListener != null) { - mOnDateChangedListener.onDateChanged(this, getYear(), getMonth(), getDayOfMonth()); + private boolean isNewDate(int year, int month, int dayOfMonth) { + return (mCurrentDate.get(Calendar.YEAR) != year + || mCurrentDate.get(Calendar.MONTH) != dayOfMonth + || mCurrentDate.get(Calendar.DAY_OF_MONTH) != month); } - } - /** - * Sets the IME options for a spinner based on its ordering. - * - * @param spinner The spinner. - * @param spinnerCount The total spinner count. - * @param spinnerIndex The index of the given spinner. - */ - private void setImeOptions(NumberPicker spinner, int spinnerCount, int spinnerIndex) { - final int imeOptions; - if (spinnerIndex < spinnerCount - 1) { - imeOptions = EditorInfo.IME_ACTION_NEXT; - } else { - imeOptions = EditorInfo.IME_ACTION_DONE; - } - TextView input = (TextView) spinner.findViewById(R.id.numberpicker_input); - input.setImeOptions(imeOptions); - } - - private void setContentDescriptions() { - // Day - trySetContentDescription(mDaySpinner, R.id.increment, - R.string.date_picker_increment_day_button); - trySetContentDescription(mDaySpinner, R.id.decrement, - R.string.date_picker_decrement_day_button); - // Month - trySetContentDescription(mMonthSpinner, R.id.increment, - R.string.date_picker_increment_month_button); - trySetContentDescription(mMonthSpinner, R.id.decrement, - R.string.date_picker_decrement_month_button); - // Year - trySetContentDescription(mYearSpinner, R.id.increment, - R.string.date_picker_increment_year_button); - trySetContentDescription(mYearSpinner, R.id.decrement, - R.string.date_picker_decrement_year_button); - } - - private void trySetContentDescription(View root, int viewId, int contDescResId) { - View target = root.findViewById(viewId); - if (target != null) { - target.setContentDescription(mContext.getString(contDescResId)); - } - } - - private void updateInputState() { - // Make sure that if the user changes the value and the IME is active - // for one of the inputs if this widget, the IME is closed. If the user - // changed the value via the IME and there is a next input the IME will - // be shown, otherwise the user chose another means of changing the - // value and having the IME up makes no sense. - InputMethodManager inputMethodManager = InputMethodManager.peekInstance(); - if (inputMethodManager != null) { - if (inputMethodManager.isActive(mYearSpinnerInput)) { - mYearSpinnerInput.clearFocus(); - inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); - } else if (inputMethodManager.isActive(mMonthSpinnerInput)) { - mMonthSpinnerInput.clearFocus(); - inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); - } else if (inputMethodManager.isActive(mDaySpinnerInput)) { - mDaySpinnerInput.clearFocus(); - inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); + private void setDate(int year, int month, int dayOfMonth) { + mCurrentDate.set(year, month, dayOfMonth); + if (mCurrentDate.before(mMinDate)) { + mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis()); + } else if (mCurrentDate.after(mMaxDate)) { + mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis()); + } + } + + private void updateSpinners() { + // set the spinner ranges respecting the min and max dates + if (mCurrentDate.equals(mMinDate)) { + mDaySpinner.setMinValue(mCurrentDate.get(Calendar.DAY_OF_MONTH)); + mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH)); + mDaySpinner.setWrapSelectorWheel(false); + mMonthSpinner.setDisplayedValues(null); + mMonthSpinner.setMinValue(mCurrentDate.get(Calendar.MONTH)); + mMonthSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.MONTH)); + mMonthSpinner.setWrapSelectorWheel(false); + } else if (mCurrentDate.equals(mMaxDate)) { + mDaySpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.DAY_OF_MONTH)); + mDaySpinner.setMaxValue(mCurrentDate.get(Calendar.DAY_OF_MONTH)); + mDaySpinner.setWrapSelectorWheel(false); + mMonthSpinner.setDisplayedValues(null); + mMonthSpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.MONTH)); + mMonthSpinner.setMaxValue(mCurrentDate.get(Calendar.MONTH)); + mMonthSpinner.setWrapSelectorWheel(false); + } else { + mDaySpinner.setMinValue(1); + mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH)); + mDaySpinner.setWrapSelectorWheel(true); + mMonthSpinner.setDisplayedValues(null); + mMonthSpinner.setMinValue(0); + mMonthSpinner.setMaxValue(11); + mMonthSpinner.setWrapSelectorWheel(true); + } + + // make sure the month names are a zero based array + // with the months in the month spinner + String[] displayedValues = Arrays.copyOfRange(mShortMonths, + mMonthSpinner.getMinValue(), mMonthSpinner.getMaxValue() + 1); + mMonthSpinner.setDisplayedValues(displayedValues); + + // year spinner range does not change based on the current date + mYearSpinner.setMinValue(mMinDate.get(Calendar.YEAR)); + mYearSpinner.setMaxValue(mMaxDate.get(Calendar.YEAR)); + mYearSpinner.setWrapSelectorWheel(false); + + // set the spinner values + mYearSpinner.setValue(mCurrentDate.get(Calendar.YEAR)); + mMonthSpinner.setValue(mCurrentDate.get(Calendar.MONTH)); + mDaySpinner.setValue(mCurrentDate.get(Calendar.DAY_OF_MONTH)); + + if (usingNumericMonths()) { + mMonthSpinnerInput.setRawInputType(InputType.TYPE_CLASS_NUMBER); + } + } + + /** + * Updates the calendar view with the current date. + */ + private void updateCalendarView() { + mCalendarView.setDate(mCurrentDate.getTimeInMillis(), false, false); + } + + + /** + * Notifies the listener, if such, for a change in the selected date. + */ + private void notifyDateChanged() { + mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); + if (mOnDateChangedListener != null) { + mOnDateChangedListener.onDateChanged(mDelegator, getYear(), getMonth(), + getDayOfMonth()); + } + } + + /** + * Sets the IME options for a spinner based on its ordering. + * + * @param spinner The spinner. + * @param spinnerCount The total spinner count. + * @param spinnerIndex The index of the given spinner. + */ + private void setImeOptions(NumberPicker spinner, int spinnerCount, int spinnerIndex) { + final int imeOptions; + if (spinnerIndex < spinnerCount - 1) { + imeOptions = EditorInfo.IME_ACTION_NEXT; + } else { + imeOptions = EditorInfo.IME_ACTION_DONE; + } + TextView input = (TextView) spinner.findViewById(R.id.numberpicker_input); + input.setImeOptions(imeOptions); + } + + private void setContentDescriptions() { + // Day + trySetContentDescription(mDaySpinner, R.id.increment, + R.string.date_picker_increment_day_button); + trySetContentDescription(mDaySpinner, R.id.decrement, + R.string.date_picker_decrement_day_button); + // Month + trySetContentDescription(mMonthSpinner, R.id.increment, + R.string.date_picker_increment_month_button); + trySetContentDescription(mMonthSpinner, R.id.decrement, + R.string.date_picker_decrement_month_button); + // Year + trySetContentDescription(mYearSpinner, R.id.increment, + R.string.date_picker_increment_year_button); + trySetContentDescription(mYearSpinner, R.id.decrement, + R.string.date_picker_decrement_year_button); + } + + private void trySetContentDescription(View root, int viewId, int contDescResId) { + View target = root.findViewById(viewId); + if (target != null) { + target.setContentDescription(mContext.getString(contDescResId)); + } + } + + private void updateInputState() { + // Make sure that if the user changes the value and the IME is active + // for one of the inputs if this widget, the IME is closed. If the user + // changed the value via the IME and there is a next input the IME will + // be shown, otherwise the user chose another means of changing the + // value and having the IME up makes no sense. + InputMethodManager inputMethodManager = InputMethodManager.peekInstance(); + if (inputMethodManager != null) { + if (inputMethodManager.isActive(mYearSpinnerInput)) { + mYearSpinnerInput.clearFocus(); + inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0); + } else if (inputMethodManager.isActive(mMonthSpinnerInput)) { + mMonthSpinnerInput.clearFocus(); + inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0); + } else if (inputMethodManager.isActive(mDaySpinnerInput)) { + mDaySpinnerInput.clearFocus(); + inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0); + } } } } diff --git a/core/java/android/widget/DateTimeView.java b/core/java/android/widget/DateTimeView.java index af6bbcb..45d1403 100644 --- a/core/java/android/widget/DateTimeView.java +++ b/core/java/android/widget/DateTimeView.java @@ -27,12 +27,9 @@ import android.text.format.Time; import android.util.AttributeSet; import android.util.Log; import android.provider.Settings; -import android.provider.Settings.SettingNotFoundException; import android.widget.TextView; import android.widget.RemoteViews.RemoteView; -import com.android.internal.R; - import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; diff --git a/core/java/android/widget/DialerFilter.java b/core/java/android/widget/DialerFilter.java index 20bc114..78786e1 100644 --- a/core/java/android/widget/DialerFilter.java +++ b/core/java/android/widget/DialerFilter.java @@ -28,8 +28,6 @@ import android.text.method.DialerKeyListener; import android.text.method.KeyListener; import android.text.method.TextKeyListener; import android.util.AttributeSet; -import android.util.Log; -import android.view.KeyCharacterMap; import android.view.View; import android.graphics.Rect; diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java index 30752e0..fa37443 100644 --- a/core/java/android/widget/EdgeEffect.java +++ b/core/java/android/widget/EdgeEffect.java @@ -136,8 +136,8 @@ public class EdgeEffect { */ public EdgeEffect(Context context) { final Resources res = context.getResources(); - mEdge = res.getDrawable(R.drawable.overscroll_edge); - mGlow = res.getDrawable(R.drawable.overscroll_glow); + mEdge = context.getDrawable(R.drawable.overscroll_edge); + mGlow = context.getDrawable(R.drawable.overscroll_glow); mEdgeHeight = mEdge.getIntrinsicHeight(); mGlowHeight = mGlow.getIntrinsicHeight(); diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java index 57e51c2..a8ff562 100644 --- a/core/java/android/widget/EditText.java +++ b/core/java/android/widget/EditText.java @@ -17,6 +17,7 @@ package android.widget; import android.content.Context; +import android.os.Bundle; import android.text.Editable; import android.text.Selection; import android.text.Spannable; @@ -56,8 +57,12 @@ public class EditText extends TextView { this(context, attrs, com.android.internal.R.attr.editTextStyle); } - public EditText(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public EditText(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public EditText(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); } @Override @@ -128,4 +133,22 @@ public class EditText extends TextView { super.onInitializeAccessibilityNodeInfo(info); info.setClassName(EditText.class.getName()); } + + @Override + public boolean performAccessibilityAction(int action, Bundle arguments) { + switch (action) { + case AccessibilityNodeInfo.ACTION_SET_TEXT: { + CharSequence text = (arguments != null) ? arguments.getCharSequence( + AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null; + setText(text); + if (text != null && text.length() > 0) { + setSelection(text.length()); + } + return true; + } + default: { + return super.performAccessibilityAction(action, arguments); + } + } + } } diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 748af7b..53d9e28 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -23,6 +23,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.InputFilter; import android.text.SpannableString; + import com.android.internal.util.ArrayUtils; import com.android.internal.widget.EditableInputConnection; @@ -45,8 +46,6 @@ import android.graphics.drawable.Drawable; import android.inputmethodservice.ExtractEditText; import android.os.Bundle; import android.os.Handler; -import android.os.Message; -import android.os.Messenger; import android.os.SystemClock; import android.provider.Settings; import android.text.DynamicLayout; @@ -79,6 +78,7 @@ import android.view.DisplayList; import android.view.DragEvent; import android.view.Gravity; import android.view.HardwareCanvas; +import android.view.HardwareRenderer; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; @@ -137,7 +137,16 @@ public class Editor { InputContentType mInputContentType; InputMethodState mInputMethodState; - DisplayList[] mTextDisplayLists; + private static class TextDisplayList { + DisplayList displayList; + boolean isDirty; + public TextDisplayList(String name) { + isDirty = true; + displayList = DisplayList.create(name); + } + boolean needsRecord() { return isDirty || !displayList.isValid(); } + } + TextDisplayList[] mTextDisplayLists; boolean mFrozenWithFocus; boolean mSelectionMoved; @@ -262,7 +271,7 @@ public class Editor { mTextView.removeCallbacks(mShowSuggestionRunnable); } - invalidateTextDisplayList(); + destroyDisplayListsData(); if (mSpellChecker != null) { mSpellChecker.closeSession(); @@ -277,6 +286,18 @@ public class Editor { mTemporaryDetach = false; } + private void destroyDisplayListsData() { + if (mTextDisplayLists != null) { + for (int i = 0; i < mTextDisplayLists.length; i++) { + DisplayList displayList = mTextDisplayLists[i] != null + ? mTextDisplayLists[i].displayList : null; + if (displayList != null && displayList.isValid()) { + displayList.destroyDisplayListData(); + } + } + } + } + private void showError() { if (mTextView.getWindowToken() == null) { mShowErrorAfterAttach = true; @@ -1316,10 +1337,11 @@ public class Editor { layout.drawBackground(canvas, highlight, highlightPaint, cursorOffsetVertical, firstLine, lastLine); + final HardwareRenderer renderer = mTextView.getHardwareRenderer(); if (layout instanceof DynamicLayout) { if (mTextDisplayLists == null) { - mTextDisplayLists = new DisplayList[ArrayUtils.idealObjectArraySize(0)]; + mTextDisplayLists = new TextDisplayList[ArrayUtils.idealObjectArraySize(0)]; } DynamicLayout dynamicLayout = (DynamicLayout) layout; @@ -1343,15 +1365,13 @@ public class Editor { searchStartIndex = blockIndex + 1; } - DisplayList blockDisplayList = mTextDisplayLists[blockIndex]; - if (blockDisplayList == null) { - blockDisplayList = mTextDisplayLists[blockIndex] = - mTextView.getHardwareRenderer().createDisplayList("Text " + blockIndex); - } else { - if (blockIsInvalid) blockDisplayList.clear(); + if (mTextDisplayLists[blockIndex] == null) { + mTextDisplayLists[blockIndex] = + new TextDisplayList("Text " + blockIndex); } - final boolean blockDisplayListIsInvalid = !blockDisplayList.isValid(); + final boolean blockDisplayListIsInvalid = mTextDisplayLists[blockIndex].needsRecord(); + DisplayList blockDisplayList = mTextDisplayLists[blockIndex].displayList; if (i >= indexFirstChangedBlock || blockDisplayListIsInvalid) { final int blockBeginLine = endOfPreviousBlock + 1; final int top = layout.getLineTop(blockBeginLine); @@ -1381,7 +1401,7 @@ public class Editor { // No need to untranslate, previous context is popped after // drawDisplayList } finally { - blockDisplayList.end(); + blockDisplayList.end(renderer, hardwareCanvas); // Same as drawDisplayList below, handled by our TextView's parent blockDisplayList.setClipToBounds(false); } @@ -1422,7 +1442,7 @@ public class Editor { // No available index found, the pool has to grow int newSize = ArrayUtils.idealIntArraySize(length + 1); - DisplayList[] displayLists = new DisplayList[newSize]; + TextDisplayList[] displayLists = new TextDisplayList[newSize]; System.arraycopy(mTextDisplayLists, 0, displayLists, 0, length); mTextDisplayLists = displayLists; return length; @@ -1461,7 +1481,7 @@ public class Editor { while (i < numberOfBlocks) { final int blockIndex = blockIndices[i]; if (blockIndex != DynamicLayout.INVALID_BLOCK_INDEX) { - mTextDisplayLists[blockIndex].clear(); + mTextDisplayLists[blockIndex].isDirty = true; } if (blockEndLines[i] >= lastLine) break; i++; @@ -1472,7 +1492,7 @@ public class Editor { void invalidateTextDisplayList() { if (mTextDisplayLists != null) { for (int i = 0; i < mTextDisplayLists.length; i++) { - if (mTextDisplayLists[i] != null) mTextDisplayLists[i].clear(); + if (mTextDisplayLists[i] != null) mTextDisplayLists[i].isDirty = true; } } } @@ -1681,7 +1701,7 @@ public class Editor { private void updateCursorPosition(int cursorIndex, int top, int bottom, float horizontal) { if (mCursorDrawable[cursorIndex] == null) - mCursorDrawable[cursorIndex] = mTextView.getResources().getDrawable( + mCursorDrawable[cursorIndex] = mTextView.getContext().getDrawable( mTextView.mCursorDrawableRes); if (mTempRect == null) mTempRect = new Rect(); @@ -2321,8 +2341,8 @@ public class Editor { private final HashMap<SuggestionSpan, Integer> mSpansLengths; private class CustomPopupWindow extends PopupWindow { - public CustomPopupWindow(Context context, int defStyle) { - super(context, null, defStyle); + public CustomPopupWindow(Context context, int defStyleAttr) { + super(context, null, defStyleAttr); } @Override @@ -2971,7 +2991,7 @@ public class Editor { positionY += mContentView.getMeasuredHeight(); // Assumes insertion and selection handles share the same height - final Drawable handle = mTextView.getResources().getDrawable( + final Drawable handle = mTextView.getContext().getDrawable( mTextView.mTextSelectHandleRes); positionY += handle.getIntrinsicHeight(); } @@ -3548,7 +3568,7 @@ public class Editor { private InsertionHandleView getHandle() { if (mSelectHandleCenter == null) { - mSelectHandleCenter = mTextView.getResources().getDrawable( + mSelectHandleCenter = mTextView.getContext().getDrawable( mTextView.mTextSelectHandleRes); } if (mHandle == null) { @@ -3594,11 +3614,11 @@ public class Editor { private void initDrawables() { if (mSelectHandleLeft == null) { - mSelectHandleLeft = mTextView.getContext().getResources().getDrawable( + mSelectHandleLeft = mTextView.getContext().getDrawable( mTextView.mTextSelectHandleLeftRes); } if (mSelectHandleRight == null) { - mSelectHandleRight = mTextView.getContext().getResources().getDrawable( + mSelectHandleRight = mTextView.getContext().getDrawable( mTextView.mTextSelectHandleRightRes); } } diff --git a/core/java/android/widget/ExpandableListView.java b/core/java/android/widget/ExpandableListView.java index 7b81aa8..70089e0 100644 --- a/core/java/android/widget/ExpandableListView.java +++ b/core/java/android/widget/ExpandableListView.java @@ -227,12 +227,16 @@ public class ExpandableListView extends ListView { this(context, attrs, com.android.internal.R.attr.expandableListViewStyle); } - public ExpandableListView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public ExpandableListView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public ExpandableListView( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); - TypedArray a = - context.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.ExpandableListView, defStyle, 0); + final TypedArray a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.ExpandableListView, defStyleAttr, defStyleRes); mGroupIndicator = a.getDrawable( com.android.internal.R.styleable.ExpandableListView_groupIndicator); diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java index 4379bf6..c0961fd 100644 --- a/core/java/android/widget/FastScroller.java +++ b/core/java/android/widget/FastScroller.java @@ -24,11 +24,11 @@ import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.content.Context; import android.content.res.ColorStateList; -import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Build; +import android.os.SystemClock; import android.text.TextUtils; import android.text.TextUtils.TruncateAt; import android.util.IntProperty; @@ -43,7 +43,7 @@ import android.view.ViewConfiguration; import android.view.ViewGroup.LayoutParams; import android.view.ViewGroupOverlay; import android.widget.AbsListView.OnScrollListener; -import com.android.internal.R; +import android.widget.ImageView.ScaleType; /** * Helper class for AbsListView to draw and control the Fast Scroll thumb @@ -76,24 +76,6 @@ class FastScroller { /** Scroll thumb and preview being dragged by user. */ private static final int STATE_DRAGGING = 2; - /** Styleable attributes. */ - private static final int[] ATTRS = new int[] { - android.R.attr.fastScrollTextColor, - android.R.attr.fastScrollThumbDrawable, - android.R.attr.fastScrollTrackDrawable, - android.R.attr.fastScrollPreviewBackgroundLeft, - android.R.attr.fastScrollPreviewBackgroundRight, - android.R.attr.fastScrollOverlayPosition - }; - - // Styleable attribute indices. - private static final int TEXT_COLOR = 0; - private static final int THUMB_DRAWABLE = 1; - private static final int TRACK_DRAWABLE = 2; - private static final int PREVIEW_BACKGROUND_LEFT = 3; - private static final int PREVIEW_BACKGROUND_RIGHT = 4; - private static final int OVERLAY_POSITION = 5; - // Positions for preview image and text. private static final int OVERLAY_FLOATING = 0; private static final int OVERLAY_AT_THUMB = 1; @@ -115,7 +97,7 @@ class FastScroller { private final TextView mSecondaryText; private final ImageView mThumbImage; private final ImageView mTrackImage; - private final ImageView mPreviewImage; + private final View mPreviewImage; /** * Preview image resource IDs for left- and right-aligned layouts. See @@ -127,13 +109,25 @@ class FastScroller { * Padding in pixels around the preview text. Applied as layout margins to * the preview text and padding to the preview image. */ - private final int mPreviewPadding; + private int mPreviewPadding; + + private int mPreviewMinWidth; + private int mPreviewMinHeight; + private int mThumbMinWidth; + private int mThumbMinHeight; - /** Whether there is a track image to display. */ - private final boolean mHasTrackImage; + /** Theme-specified text size. Used only if text appearance is not set. */ + private float mTextSize; + + /** Theme-specified text color. Used only if text appearance is not set. */ + private ColorStateList mTextColor; + + private Drawable mThumbDrawable; + private Drawable mTrackDrawable; + private int mTextAppearance; /** Total width of decorations. */ - private final int mWidth; + private int mWidth; /** Set containing decoration transition animations. */ private AnimatorSet mDecorAnimation; @@ -180,7 +174,7 @@ class FastScroller { /** Whether the preview image is visible. */ private boolean mShowingPreview; - private BaseAdapter mListAdapter; + private Adapter mListAdapter; private SectionIndexer mSectionIndexer; /** Whether decorations should be laid out from right to left. */ @@ -208,22 +202,9 @@ class FastScroller { private boolean mMatchDragPosition; private float mInitialTouchY; - private boolean mHasPendingDrag; + private long mPendingDrag = -1; private int mScaledTouchSlop; - private final Runnable mDeferStartDrag = new Runnable() { - @Override - public void run() { - if (mList.isAttachedToWindow()) { - beginDrag(); - - final float pos = getPosFromMotionEvent(mInitialTouchY); - scrollTo(pos); - } - - mHasPendingDrag = false; - } - }; private int mOldItemCount; private int mOldChildCount; @@ -247,92 +228,145 @@ class FastScroller { } }; - public FastScroller(AbsListView listView) { + public FastScroller(AbsListView listView, int styleResId) { mList = listView; - mOverlay = listView.getOverlay(); mOldItemCount = listView.getCount(); mOldChildCount = listView.getChildCount(); final Context context = listView.getContext(); mScaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + mScrollBarStyle = listView.getScrollBarStyle(); - final Resources res = context.getResources(); - final TypedArray ta = context.getTheme().obtainStyledAttributes(ATTRS); + mScrollCompleted = true; + mState = STATE_VISIBLE; + mMatchDragPosition = + context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB; + + mTrackImage = new ImageView(context); + mTrackImage.setScaleType(ScaleType.FIT_XY); + mThumbImage = new ImageView(context); + mThumbImage.setScaleType(ScaleType.FIT_XY); + mPreviewImage = new View(context); + mPreviewImage.setAlpha(0f); + + mPrimaryText = createPreviewTextView(context); + mSecondaryText = createPreviewTextView(context); - final ImageView trackImage = new ImageView(context); - mTrackImage = trackImage; + setStyle(styleResId); + final ViewGroupOverlay overlay = listView.getOverlay(); + mOverlay = overlay; + overlay.add(mTrackImage); + overlay.add(mThumbImage); + overlay.add(mPreviewImage); + overlay.add(mPrimaryText); + overlay.add(mSecondaryText); + + getSectionsFromIndexer(); updateLongList(mOldChildCount, mOldItemCount); + setScrollbarPosition(listView.getVerticalScrollbarPosition()); + postAutoHide(); + } + + private void updateAppearance() { + final Context context = mList.getContext(); int width = 0; // Add track to overlay if it has an image. - final Drawable trackDrawable = ta.getDrawable(TRACK_DRAWABLE); - if (trackDrawable != null) { - mHasTrackImage = true; - trackImage.setBackground(trackDrawable); - mOverlay.add(trackImage); - width = Math.max(width, trackDrawable.getIntrinsicWidth()); - } else { - mHasTrackImage = false; + mTrackImage.setImageDrawable(mTrackDrawable); + if (mTrackDrawable != null) { + width = Math.max(width, mTrackDrawable.getIntrinsicWidth()); } - final ImageView thumbImage = new ImageView(context); - mThumbImage = thumbImage; - // Add thumb to overlay if it has an image. - final Drawable thumbDrawable = ta.getDrawable(THUMB_DRAWABLE); - if (thumbDrawable != null) { - thumbImage.setImageDrawable(thumbDrawable); - mOverlay.add(thumbImage); - width = Math.max(width, thumbDrawable.getIntrinsicWidth()); + mThumbImage.setImageDrawable(mThumbDrawable); + mThumbImage.setMinimumWidth(mThumbMinWidth); + mThumbImage.setMinimumHeight(mThumbMinHeight); + if (mThumbDrawable != null) { + width = Math.max(width, mThumbDrawable.getIntrinsicWidth()); } - // If necessary, apply minimum thumb width and height. - if (thumbDrawable.getIntrinsicWidth() <= 0 || thumbDrawable.getIntrinsicHeight() <= 0) { - final int minWidth = res.getDimensionPixelSize(R.dimen.fastscroll_thumb_width); - thumbImage.setMinimumWidth(minWidth); - thumbImage.setMinimumHeight( - res.getDimensionPixelSize(R.dimen.fastscroll_thumb_height)); - width = Math.max(width, minWidth); - } + // Account for minimum thumb width. + mWidth = Math.max(width, mThumbMinWidth); - mWidth = width; + mPreviewImage.setMinimumWidth(mPreviewMinWidth); + mPreviewImage.setMinimumHeight(mPreviewMinHeight); - final int previewSize = res.getDimensionPixelSize(R.dimen.fastscroll_overlay_size); - mPreviewImage = new ImageView(context); - mPreviewImage.setMinimumWidth(previewSize); - mPreviewImage.setMinimumHeight(previewSize); - mPreviewImage.setAlpha(0f); - mOverlay.add(mPreviewImage); + if (mTextAppearance != 0) { + mPrimaryText.setTextAppearance(context, mTextAppearance); + mSecondaryText.setTextAppearance(context, mTextAppearance); + } - mPreviewPadding = res.getDimensionPixelSize(R.dimen.fastscroll_overlay_padding); + if (mTextColor != null) { + mPrimaryText.setTextColor(mTextColor); + mSecondaryText.setTextColor(mTextColor); + } + + if (mTextSize > 0) { + mPrimaryText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize); + mSecondaryText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize); + } - final int textMinSize = Math.max(0, previewSize - mPreviewPadding); - mPrimaryText = createPreviewTextView(context, ta); + final int textMinSize = Math.max(0, mPreviewMinHeight); mPrimaryText.setMinimumWidth(textMinSize); mPrimaryText.setMinimumHeight(textMinSize); - mOverlay.add(mPrimaryText); - mSecondaryText = createPreviewTextView(context, ta); mSecondaryText.setMinimumWidth(textMinSize); mSecondaryText.setMinimumHeight(textMinSize); - mOverlay.add(mSecondaryText); - mPreviewResId[PREVIEW_LEFT] = ta.getResourceId(PREVIEW_BACKGROUND_LEFT, 0); - mPreviewResId[PREVIEW_RIGHT] = ta.getResourceId(PREVIEW_BACKGROUND_RIGHT, 0); - mOverlayPosition = ta.getInt(OVERLAY_POSITION, OVERLAY_FLOATING); - ta.recycle(); + refreshDrawablePressedState(); + } - mScrollBarStyle = listView.getScrollBarStyle(); - mScrollCompleted = true; - mState = STATE_VISIBLE; - mMatchDragPosition = context.getApplicationInfo().targetSdkVersion - >= Build.VERSION_CODES.HONEYCOMB; + public void setStyle(int resId) { + final Context context = mList.getContext(); + final TypedArray ta = context.obtainStyledAttributes(null, + com.android.internal.R.styleable.FastScroll, android.R.attr.fastScrollStyle, resId); + final int N = ta.getIndexCount(); + for (int i = 0; i < N; i++) { + final int index = ta.getIndex(i); + switch (index) { + case com.android.internal.R.styleable.FastScroll_position: + mOverlayPosition = ta.getInt(index, OVERLAY_FLOATING); + break; + case com.android.internal.R.styleable.FastScroll_backgroundLeft: + mPreviewResId[PREVIEW_LEFT] = ta.getResourceId(index, 0); + break; + case com.android.internal.R.styleable.FastScroll_backgroundRight: + mPreviewResId[PREVIEW_RIGHT] = ta.getResourceId(index, 0); + break; + case com.android.internal.R.styleable.FastScroll_thumbDrawable: + mThumbDrawable = ta.getDrawable(index); + break; + case com.android.internal.R.styleable.FastScroll_trackDrawable: + mTrackDrawable = ta.getDrawable(index); + break; + case com.android.internal.R.styleable.FastScroll_textAppearance: + mTextAppearance = ta.getResourceId(index, 0); + break; + case com.android.internal.R.styleable.FastScroll_textColor: + mTextColor = ta.getColorStateList(index); + break; + case com.android.internal.R.styleable.FastScroll_textSize: + mTextSize = ta.getDimensionPixelSize(index, 0); + break; + case com.android.internal.R.styleable.FastScroll_minWidth: + mPreviewMinWidth = ta.getDimensionPixelSize(index, 0); + break; + case com.android.internal.R.styleable.FastScroll_minHeight: + mPreviewMinHeight = ta.getDimensionPixelSize(index, 0); + break; + case com.android.internal.R.styleable.FastScroll_thumbMinWidth: + mThumbMinWidth = ta.getDimensionPixelSize(index, 0); + break; + case com.android.internal.R.styleable.FastScroll_thumbMinHeight: + mThumbMinHeight = ta.getDimensionPixelSize(index, 0); + break; + case com.android.internal.R.styleable.FastScroll_padding: + mPreviewPadding = ta.getDimensionPixelSize(index, 0); + break; + } + } - getSectionsFromIndexer(); - refreshDrawablePressedState(); - updateLongList(listView.getChildCount(), listView.getCount()); - setScrollbarPosition(mList.getVerticalScrollbarPosition()); - postAutoHide(); + updateAppearance(); } /** @@ -353,7 +387,7 @@ class FastScroller { if (mEnabled != enabled) { mEnabled = enabled; - onStateDependencyChanged(); + onStateDependencyChanged(true); } } @@ -371,7 +405,7 @@ class FastScroller { if (mAlwaysShow != alwaysShow) { mAlwaysShow = alwaysShow; - onStateDependencyChanged(); + onStateDependencyChanged(false); } } @@ -385,13 +419,18 @@ class FastScroller { /** * Called when one of the variables affecting enabled state changes. + * + * @param peekIfEnabled whether the thumb should peek, if enabled */ - private void onStateDependencyChanged() { + private void onStateDependencyChanged(boolean peekIfEnabled) { if (isEnabled()) { if (isAlwaysShowEnabled()) { setState(STATE_VISIBLE); } else if (mState == STATE_VISIBLE) { postAutoHide(); + } else if (peekIfEnabled) { + setState(STATE_VISIBLE); + postAutoHide(); } } else { stop(); @@ -470,24 +509,18 @@ class FastScroller { if (mLongList != longList) { mLongList = longList; - onStateDependencyChanged(); + onStateDependencyChanged(false); } } /** * Creates a view into which preview text can be placed. */ - private TextView createPreviewTextView(Context context, TypedArray ta) { + private TextView createPreviewTextView(Context context) { final LayoutParams params = new LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); - final Resources res = context.getResources(); - final int minSize = res.getDimensionPixelSize(R.dimen.fastscroll_overlay_size); - final ColorStateList textColor = ta.getColorStateList(TEXT_COLOR); - final float textSize = res.getDimensionPixelSize(R.dimen.fastscroll_overlay_text_size); final TextView textView = new TextView(context); textView.setLayoutParams(params); - textView.setTextColor(textColor); - textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); textView.setSingleLine(true); textView.setEllipsize(TruncateAt.MIDDLE); textView.setGravity(Gravity.CENTER); @@ -611,7 +644,7 @@ class FastScroller { view.measure(widthMeasureSpec, heightMeasureSpec); // Align to the left or right. - final int width = view.getMeasuredWidth(); + final int width = Math.min(adjMaxWidth, view.getMeasuredWidth()); final int left; final int right; if (mLayoutFromRight) { @@ -872,15 +905,15 @@ class FastScroller { .getAdapter(); if (expAdapter instanceof SectionIndexer) { mSectionIndexer = (SectionIndexer) expAdapter; - mListAdapter = (BaseAdapter) adapter; + mListAdapter = adapter; mSections = mSectionIndexer.getSections(); } } else if (adapter instanceof SectionIndexer) { - mListAdapter = (BaseAdapter) adapter; + mListAdapter = adapter; mSectionIndexer = (SectionIndexer) adapter; mSections = mSectionIndexer.getSections(); } else { - mListAdapter = (BaseAdapter) adapter; + mListAdapter = adapter; mSections = null; } } @@ -1028,7 +1061,7 @@ class FastScroller { } final Rect bounds = mTempBounds; - final ImageView preview = mPreviewImage; + final View preview = mPreviewImage; final TextView showing; final TextView target; if (mShowingPrimary) { @@ -1054,10 +1087,10 @@ class FastScroller { hideShowing.addListener(mSwitchPrimaryListener); // Apply preview image padding and animate bounds, if necessary. - bounds.left -= mPreviewImage.getPaddingLeft(); - bounds.top -= mPreviewImage.getPaddingTop(); - bounds.right += mPreviewImage.getPaddingRight(); - bounds.bottom += mPreviewImage.getPaddingBottom(); + bounds.left -= preview.getPaddingLeft(); + bounds.top -= preview.getPaddingTop(); + bounds.right += preview.getPaddingRight(); + bounds.bottom += preview.getPaddingBottom(); final Animator resizePreview = animateBounds(preview, bounds); resizePreview.setDuration(DURATION_RESIZE); @@ -1105,8 +1138,8 @@ class FastScroller { final int top = container.top; final int bottom = container.bottom; - final ImageView trackImage = mTrackImage; - final ImageView thumbImage = mThumbImage; + final View trackImage = mTrackImage; + final View thumbImage = mThumbImage; final float min = trackImage.getTop(); final float max = trackImage.getBottom(); final float offset = min; @@ -1117,7 +1150,7 @@ class FastScroller { final float previewPos = mOverlayPosition == OVERLAY_AT_THUMB ? thumbMiddle : 0; // Center the preview on the thumb, constrained to the list bounds. - final ImageView previewImage = mPreviewImage; + final View previewImage = mPreviewImage; final float previewHalfHeight = previewImage.getHeight() / 2f; final float minP = top + previewHalfHeight; final float maxP = bottom - previewHalfHeight; @@ -1130,11 +1163,7 @@ class FastScroller { } private float getPosFromMotionEvent(float y) { - final Rect container = mContainerRect; - final int top = container.top; - final int bottom = container.bottom; - - final ImageView trackImage = mTrackImage; + final View trackImage = mTrackImage; final float min = trackImage.getTop(); final float max = trackImage.getBottom(); final float offset = min; @@ -1235,8 +1264,7 @@ class FastScroller { * @see #startPendingDrag() */ private void cancelPendingDrag() { - mList.removeCallbacks(mDeferStartDrag); - mHasPendingDrag = false; + mPendingDrag = -1; } /** @@ -1244,11 +1272,12 @@ class FastScroller { * scrolling, rather than tapping. */ private void startPendingDrag() { - mHasPendingDrag = true; - mList.postDelayed(mDeferStartDrag, TAP_TIMEOUT); + mPendingDrag = SystemClock.uptimeMillis() + TAP_TIMEOUT; } private void beginDrag() { + mPendingDrag = -1; + setState(STATE_DRAGGING); if (mListAdapter == null && mList != null) { @@ -1288,6 +1317,13 @@ class FastScroller { case MotionEvent.ACTION_MOVE: if (!isPointInside(ev.getX(), ev.getY())) { cancelPendingDrag(); + } else if (mPendingDrag >= 0 && mPendingDrag <= SystemClock.uptimeMillis()) { + beginDrag(); + + final float pos = getPosFromMotionEvent(mInitialTouchY); + scrollTo(pos); + + return onTouchEvent(ev); } break; case MotionEvent.ACTION_UP: @@ -1322,7 +1358,7 @@ class FastScroller { switch (me.getActionMasked()) { case MotionEvent.ACTION_UP: { - if (mHasPendingDrag) { + if (mPendingDrag >= 0) { // Allow a tap to scroll. beginDrag(); @@ -1330,7 +1366,6 @@ class FastScroller { setThumbPos(pos); scrollTo(pos); - cancelPendingDrag(); // Will hit the STATE_DRAGGING check below } @@ -1351,20 +1386,9 @@ class FastScroller { } break; case MotionEvent.ACTION_MOVE: { - if (mHasPendingDrag && Math.abs(me.getY() - mInitialTouchY) > mScaledTouchSlop) { - setState(STATE_DRAGGING); - - if (mListAdapter == null && mList != null) { - getSectionsFromIndexer(); - } - - if (mList != null) { - mList.requestDisallowInterceptTouchEvent(true); - mList.reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); - } + if (mPendingDrag >= 0 && Math.abs(me.getY() - mInitialTouchY) > mScaledTouchSlop) { + beginDrag(); - cancelFling(); - cancelPendingDrag(); // Will hit the STATE_DRAGGING check below } @@ -1401,7 +1425,7 @@ class FastScroller { * @return Whether the coordinate is inside the scroller's activation area. */ private boolean isPointInside(float x, float y) { - return isPointInsideX(x) && (mHasTrackImage || isPointInsideY(y)); + return isPointInsideX(x) && (mTrackDrawable != null || isPointInsideY(y)); } private boolean isPointInsideX(float x) { diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java index d9d4ad7..b029328 100644 --- a/core/java/android/widget/FrameLayout.java +++ b/core/java/android/widget/FrameLayout.java @@ -97,11 +97,15 @@ public class FrameLayout extends ViewGroup { this(context, attrs, 0); } - public FrameLayout(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public FrameLayout(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public FrameLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); - TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.FrameLayout, - defStyle, 0); + final TypedArray a = context.obtainStyledAttributes( + attrs, com.android.internal.R.styleable.FrameLayout, defStyleAttr, defStyleRes); mForegroundGravity = a.getInt( com.android.internal.R.styleable.FrameLayout_foregroundGravity, mForegroundGravity); diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java index 78ba6e0..f7c839f 100644 --- a/core/java/android/widget/Gallery.java +++ b/core/java/android/widget/Gallery.java @@ -196,14 +196,18 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList this(context, attrs, R.attr.galleryStyle); } - public Gallery(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public Gallery(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public Gallery(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); mGestureDetector = new GestureDetector(context, this); mGestureDetector.setIsLongpressEnabled(true); - - TypedArray a = context.obtainStyledAttributes( - attrs, com.android.internal.R.styleable.Gallery, defStyle, 0); + + final TypedArray a = context.obtainStyledAttributes( + attrs, com.android.internal.R.styleable.Gallery, defStyleAttr, defStyleRes); int index = a.getInt(com.android.internal.R.styleable.Gallery_gravity, -1); if (index >= 0) { diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java index 54cc3f4..8511601 100644 --- a/core/java/android/widget/GridLayout.java +++ b/core/java/android/widget/GridLayout.java @@ -16,6 +16,7 @@ package android.widget; +import android.annotation.IntDef; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; @@ -35,6 +36,8 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.widget.RemoteViews.RemoteView; import com.android.internal.R; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; @@ -165,6 +168,11 @@ public class GridLayout extends ViewGroup { // Public constants + /** @hide */ + @IntDef({HORIZONTAL, VERTICAL}) + @Retention(RetentionPolicy.SOURCE) + public @interface Orientation {} + /** * The horizontal orientation. */ @@ -186,6 +194,11 @@ public class GridLayout extends ViewGroup { */ public static final int UNDEFINED = Integer.MIN_VALUE; + /** @hide */ + @IntDef({ALIGN_BOUNDS, ALIGN_MARGINS}) + @Retention(RetentionPolicy.SOURCE) + public @interface AlignmentMode {} + /** * This constant is an {@link #setAlignmentMode(int) alignmentMode}. * When the {@code alignmentMode} is set to {@link #ALIGN_BOUNDS}, alignment @@ -262,13 +275,23 @@ public class GridLayout extends ViewGroup { // Constructors - /** - * {@inheritDoc} - */ - public GridLayout(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public GridLayout(Context context) { + this(context, null); + } + + public GridLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public GridLayout(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public GridLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); mDefaultGap = context.getResources().getDimensionPixelOffset(R.dimen.default_gap); - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GridLayout); + final TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.GridLayout, defStyleAttr, defStyleRes); try { setRowCount(a.getInt(ROW_COUNT, DEFAULT_COUNT)); setColumnCount(a.getInt(COLUMN_COUNT, DEFAULT_COUNT)); @@ -282,21 +305,6 @@ public class GridLayout extends ViewGroup { } } - /** - * {@inheritDoc} - */ - public GridLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - /** - * {@inheritDoc} - */ - public GridLayout(Context context) { - //noinspection NullableProblems - this(context, null); - } - // Implementation /** @@ -308,6 +316,7 @@ public class GridLayout extends ViewGroup { * * @attr ref android.R.styleable#GridLayout_orientation */ + @Orientation public int getOrientation() { return mOrientation; } @@ -348,7 +357,7 @@ public class GridLayout extends ViewGroup { * * @attr ref android.R.styleable#GridLayout_orientation */ - public void setOrientation(int orientation) { + public void setOrientation(@Orientation int orientation) { if (this.mOrientation != orientation) { this.mOrientation = orientation; invalidateStructure(); @@ -479,6 +488,7 @@ public class GridLayout extends ViewGroup { * * @attr ref android.R.styleable#GridLayout_alignmentMode */ + @AlignmentMode public int getAlignmentMode() { return mAlignmentMode; } @@ -498,7 +508,7 @@ public class GridLayout extends ViewGroup { * * @attr ref android.R.styleable#GridLayout_alignmentMode */ - public void setAlignmentMode(int alignmentMode) { + public void setAlignmentMode(@AlignmentMode int alignmentMode) { this.mAlignmentMode = alignmentMode; requestLayout(); } diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java index 15daf83..04b18c1 100644 --- a/core/java/android/widget/GridView.java +++ b/core/java/android/widget/GridView.java @@ -16,26 +16,32 @@ package android.widget; +import android.annotation.IntDef; import android.content.Context; import android.content.Intent; import android.content.res.TypedArray; import android.graphics.Rect; import android.os.Trace; import android.util.AttributeSet; +import android.util.MathUtils; import android.view.Gravity; import android.view.KeyEvent; import android.view.SoundEffectConstants; import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; +import android.view.ViewRootImpl; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityNodeProvider; import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo; import android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo; import android.view.animation.GridLayoutAnimationController; -import android.widget.AbsListView.LayoutParams; import android.widget.RemoteViews.RemoteView; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * A view that shows items in two-dimensional scrolling grid. The items in the @@ -53,6 +59,11 @@ import android.widget.RemoteViews.RemoteView; */ @RemoteView public class GridView extends AbsListView { + /** @hide */ + @IntDef({NO_STRETCH, STRETCH_SPACING, STRETCH_COLUMN_WIDTH, STRETCH_SPACING_UNIFORM}) + @Retention(RetentionPolicy.SOURCE) + public @interface StretchMode {} + /** * Disables stretching. * @@ -110,11 +121,15 @@ public class GridView extends AbsListView { this(context, attrs, com.android.internal.R.attr.gridViewStyle); } - public GridView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public GridView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public GridView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); - TypedArray a = context.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.GridView, defStyle, 0); + final TypedArray a = context.obtainStyledAttributes( + attrs, com.android.internal.R.styleable.GridView, defStyleAttr, defStyleRes); int hSpacing = a.getDimensionPixelOffset( com.android.internal.R.styleable.GridView_horizontalSpacing, 0); @@ -1014,6 +1029,11 @@ public class GridView extends AbsListView { } @Override + AbsPositionScroller createPositionScroller() { + return new GridViewPositionScroller(); + } + + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // Sets up mListPadding super.onMeasure(widthMeasureSpec, heightMeasureSpec); @@ -1202,6 +1222,34 @@ public class GridView extends AbsListView { setSelectedPositionInt(mNextSelectedPosition); + AccessibilityNodeInfo accessibilityFocusLayoutRestoreNode = null; + View accessibilityFocusLayoutRestoreView = null; + int accessibilityFocusPosition = INVALID_POSITION; + + // Remember which child, if any, had accessibility focus. This must + // occur before recycling any views, since that will clear + // accessibility focus. + final ViewRootImpl viewRootImpl = getViewRootImpl(); + if (viewRootImpl != null) { + final View focusHost = viewRootImpl.getAccessibilityFocusedHost(); + if (focusHost != null) { + final View focusChild = getAccessibilityFocusedChild(focusHost); + if (focusChild != null) { + if (!dataChanged || focusChild.hasTransientState() + || mAdapterHasStableIds) { + // The views won't be changing, so try to maintain + // focus on the current host and virtual view. + accessibilityFocusLayoutRestoreView = focusHost; + accessibilityFocusLayoutRestoreNode = viewRootImpl + .getAccessibilityFocusedVirtualView(); + } + + // Try to maintain focus at the same position. + accessibilityFocusPosition = getPositionForView(focusChild); + } + } + } + // Pull all children into the RecycleBin. // These views will be reused if possible final int firstPosition = mFirstPosition; @@ -1216,7 +1264,6 @@ public class GridView extends AbsListView { } // Clear out old views - //removeAllViewsInLayout(); detachAllViewsFromParent(); recycleBin.removeSkippedScrap(); @@ -1287,6 +1334,35 @@ public class GridView extends AbsListView { mSelectorRect.setEmpty(); } + // Attempt to restore accessibility focus, if necessary. + if (viewRootImpl != null) { + final View newAccessibilityFocusedView = viewRootImpl.getAccessibilityFocusedHost(); + if (newAccessibilityFocusedView == null) { + if (accessibilityFocusLayoutRestoreView != null + && accessibilityFocusLayoutRestoreView.isAttachedToWindow()) { + final AccessibilityNodeProvider provider = + accessibilityFocusLayoutRestoreView.getAccessibilityNodeProvider(); + if (accessibilityFocusLayoutRestoreNode != null && provider != null) { + final int virtualViewId = AccessibilityNodeInfo.getVirtualDescendantId( + accessibilityFocusLayoutRestoreNode.getSourceNodeId()); + provider.performAction(virtualViewId, + AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null); + } else { + accessibilityFocusLayoutRestoreView.requestAccessibilityFocus(); + } + } else if (accessibilityFocusPosition != INVALID_POSITION) { + // Bound the position within the visible children. + final int position = MathUtils.constrain( + accessibilityFocusPosition - mFirstPosition, 0, + getChildCount() - 1); + final View restoreView = getChildAt(position); + if (restoreView != null) { + restoreView.requestAccessibilityFocus(); + } + } + } + } + mLayoutMode = LAYOUT_NORMAL; mDataChanged = false; if (mPositionScrollAfterLayout != null) { @@ -2056,13 +2132,14 @@ public class GridView extends AbsListView { * * @attr ref android.R.styleable#GridView_stretchMode */ - public void setStretchMode(int stretchMode) { + public void setStretchMode(@StretchMode int stretchMode) { if (stretchMode != mStretchMode) { mStretchMode = stretchMode; requestLayoutIfNecessary(); } } + @StretchMode public int getStretchMode() { return mStretchMode; } @@ -2265,7 +2342,9 @@ public class GridView extends AbsListView { final int columnsCount = getNumColumns(); final int rowsCount = getCount() / columnsCount; - final CollectionInfo collectionInfo = CollectionInfo.obtain(columnsCount, rowsCount, false); + final int selectionMode = getSelectionModeForAccessibility(); + final CollectionInfo collectionInfo = CollectionInfo.obtain( + columnsCount, rowsCount, false, selectionMode); info.setCollectionInfo(collectionInfo); } @@ -2292,7 +2371,38 @@ public class GridView extends AbsListView { final LayoutParams lp = (LayoutParams) view.getLayoutParams(); final boolean isHeading = lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER; - final CollectionItemInfo itemInfo = CollectionItemInfo.obtain(column, 1, row, 1, isHeading); + final boolean isSelected = isItemChecked(position); + final CollectionItemInfo itemInfo = CollectionItemInfo.obtain( + column, 1, row, 1, isHeading, isSelected); info.setCollectionItemInfo(itemInfo); } + + /** + * Sub-position scroller that understands the layout of a GridView. + */ + class GridViewPositionScroller extends AbsSubPositionScroller { + @Override + public int getRowForPosition(int position) { + return position / mNumColumns; + } + + @Override + public int getFirstPositionForRow(int row) { + return row * mNumColumns; + } + + @Override + public int getHeightForRow(int row) { + final int firstRowPosition = row * mNumColumns; + final int lastRowPosition = Math.min(getCount(), firstRowPosition + mNumColumns); + int maxHeight = 0; + for (int i = firstRowPosition; i < lastRowPosition; i++) { + final int height = getHeightForPosition(i); + if (height > maxHeight) { + maxHeight = height; + } + } + return maxHeight; + } + } } diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java index dab0962..25d4f42 100644 --- a/core/java/android/widget/HorizontalScrollView.java +++ b/core/java/android/widget/HorizontalScrollView.java @@ -146,12 +146,17 @@ public class HorizontalScrollView extends FrameLayout { this(context, attrs, com.android.internal.R.attr.horizontalScrollViewStyle); } - public HorizontalScrollView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public HorizontalScrollView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public HorizontalScrollView( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); initScrollView(); - TypedArray a = context.obtainStyledAttributes(attrs, - android.R.styleable.HorizontalScrollView, defStyle, 0); + final TypedArray a = context.obtainStyledAttributes( + attrs, android.R.styleable.HorizontalScrollView, defStyleAttr, defStyleRes); setFillViewport(a.getBoolean(android.R.styleable.HorizontalScrollView_fillViewport, false)); diff --git a/core/java/android/widget/ImageButton.java b/core/java/android/widget/ImageButton.java index 379354c..3a20628 100644 --- a/core/java/android/widget/ImageButton.java +++ b/core/java/android/widget/ImageButton.java @@ -17,16 +17,11 @@ package android.widget; import android.content.Context; -import android.os.Handler; -import android.os.Message; import android.util.AttributeSet; -import android.view.MotionEvent; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.RemoteViews.RemoteView; -import java.util.Map; - /** * <p> * Displays a button with an image (instead of text) that can be pressed @@ -83,8 +78,12 @@ public class ImageButton extends ImageView { this(context, attrs, com.android.internal.R.attr.imageButtonStyle); } - public ImageButton(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public ImageButton(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public ImageButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); setFocusable(true); } diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java index f05179b..572302a 100644 --- a/core/java/android/widget/ImageView.java +++ b/core/java/android/widget/ImageView.java @@ -24,8 +24,10 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Matrix; +import android.graphics.PixelFormat; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; +import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Xfermode; import android.graphics.drawable.BitmapDrawable; @@ -119,12 +121,17 @@ public class ImageView extends View { this(context, attrs, 0); } - public ImageView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public ImageView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public ImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + initImageView(); - TypedArray a = context.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.ImageView, defStyle, 0); + final TypedArray a = context.obtainStyledAttributes( + attrs, com.android.internal.R.styleable.ImageView, defStyleAttr, defStyleRes); Drawable d = a.getDrawable(com.android.internal.R.styleable.ImageView_src); if (d != null) { @@ -357,13 +364,13 @@ public class ImageView extends View { @android.view.RemotableViewMethod public void setImageResource(int resId) { if (mUri != null || mResource != resId) { + final int oldWidth = mDrawableWidth; + final int oldHeight = mDrawableHeight; + updateDrawable(null); mResource = resId; mUri = null; - final int oldWidth = mDrawableWidth; - final int oldHeight = mDrawableHeight; - resolveUri(); if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) { @@ -635,7 +642,7 @@ public class ImageView extends View { if (mResource != 0) { try { - d = rsrc.getDrawable(mResource); + d = mContext.getDrawable(mResource); } catch (Exception e) { Log.w("ImageView", "Unable to find resource: " + mResource, e); // Don't try again. @@ -648,7 +655,7 @@ public class ImageView extends View { // Load drawable through Resources, to get the source density information ContentResolver.OpenResourceIdResult r = mContext.getContentResolver().getResourceId(mUri); - d = r.r.getDrawable(r.id); + d = r.r.getDrawable(r.id, mContext.getTheme()); } catch (Exception e) { Log.w("ImageView", "Unable to open content: " + mUri, e); } @@ -1220,6 +1227,37 @@ public class ImageView extends View { } } + @Override + public boolean isOpaque() { + return super.isOpaque() || mDrawable != null && mXfermode == null + && mDrawable.getOpacity() == PixelFormat.OPAQUE + && mAlpha * mViewAlphaScale >> 8 == 255 + && isFilledByImage(); + } + + private boolean isFilledByImage() { + if (mDrawable == null) { + return false; + } + + final Rect bounds = mDrawable.getBounds(); + final Matrix matrix = mDrawMatrix; + if (matrix == null) { + return bounds.left <= 0 && bounds.top <= 0 && bounds.right >= getWidth() + && bounds.bottom >= getHeight(); + } else if (matrix.rectStaysRect()) { + final RectF boundsSrc = mTempSrc; + final RectF boundsDst = mTempDst; + boundsSrc.set(bounds); + matrix.mapRect(boundsDst, boundsSrc); + return boundsDst.left <= 0 && boundsDst.top <= 0 && boundsDst.right >= getWidth() + && boundsDst.bottom >= getHeight(); + } else { + // If the matrix doesn't map to a rectangle, assume the worst. + return false; + } + } + @RemotableViewMethod @Override public void setVisibility(int visibility) { diff --git a/core/java/android/widget/LegacyTimePickerDelegate.java b/core/java/android/widget/LegacyTimePickerDelegate.java new file mode 100644 index 0000000..1634d5f --- /dev/null +++ b/core/java/android/widget/LegacyTimePickerDelegate.java @@ -0,0 +1,638 @@ +/* + * 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.widget; + +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.TypedArray; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.format.DateFormat; +import android.text.format.DateUtils; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import com.android.internal.R; + +import java.text.DateFormatSymbols; +import java.util.Calendar; +import java.util.Locale; + +import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO; +import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES; + +/** + * A delegate implementing the basic TimePicker + */ +class LegacyTimePickerDelegate extends TimePicker.AbstractTimePickerDelegate { + + private static final boolean DEFAULT_ENABLED_STATE = true; + + private static final int HOURS_IN_HALF_DAY = 12; + + // state + private boolean mIs24HourView; + + private boolean mIsAm; + + // ui components + private final NumberPicker mHourSpinner; + + private final NumberPicker mMinuteSpinner; + + private final NumberPicker mAmPmSpinner; + + private final EditText mHourSpinnerInput; + + private final EditText mMinuteSpinnerInput; + + private final EditText mAmPmSpinnerInput; + + private final TextView mDivider; + + // Note that the legacy implementation of the TimePicker is + // using a button for toggling between AM/PM while the new + // version uses a NumberPicker spinner. Therefore the code + // accommodates these two cases to be backwards compatible. + private final Button mAmPmButton; + + private final String[] mAmPmStrings; + + private boolean mIsEnabled = DEFAULT_ENABLED_STATE; + + private Calendar mTempCalendar; + + private boolean mHourWithTwoDigit; + private char mHourFormat; + + /** + * A no-op callback used in the constructor to avoid null checks later in + * the code. + */ + private static final TimePicker.OnTimeChangedListener NO_OP_CHANGE_LISTENER = + new TimePicker.OnTimeChangedListener() { + public void onTimeChanged(TimePicker view, int hourOfDay, int minute) { + } + }; + + public LegacyTimePickerDelegate(TimePicker delegator, Context context, AttributeSet attrs, + int defStyleAttr, int defStyleRes) { + super(delegator, context); + + // process style attributes + final TypedArray attributesArray = mContext.obtainStyledAttributes( + attrs, R.styleable.TimePicker, defStyleAttr, defStyleRes); + final int layoutResourceId = attributesArray.getResourceId( + R.styleable.TimePicker_legacyLayout, R.layout.time_picker_legacy); + attributesArray.recycle(); + + final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + inflater.inflate(layoutResourceId, mDelegator, true); + + // hour + mHourSpinner = (NumberPicker) delegator.findViewById(R.id.hour); + mHourSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { + public void onValueChange(NumberPicker spinner, int oldVal, int newVal) { + updateInputState(); + if (!is24HourView()) { + if ((oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) || + (oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1)) { + mIsAm = !mIsAm; + updateAmPmControl(); + } + } + onTimeChanged(); + } + }); + mHourSpinnerInput = (EditText) mHourSpinner.findViewById(R.id.numberpicker_input); + mHourSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT); + + // divider (only for the new widget style) + mDivider = (TextView) mDelegator.findViewById(R.id.divider); + if (mDivider != null) { + setDividerText(); + } + + // minute + mMinuteSpinner = (NumberPicker) mDelegator.findViewById(R.id.minute); + mMinuteSpinner.setMinValue(0); + mMinuteSpinner.setMaxValue(59); + mMinuteSpinner.setOnLongPressUpdateInterval(100); + mMinuteSpinner.setFormatter(NumberPicker.getTwoDigitFormatter()); + mMinuteSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { + public void onValueChange(NumberPicker spinner, int oldVal, int newVal) { + updateInputState(); + int minValue = mMinuteSpinner.getMinValue(); + int maxValue = mMinuteSpinner.getMaxValue(); + if (oldVal == maxValue && newVal == minValue) { + int newHour = mHourSpinner.getValue() + 1; + if (!is24HourView() && newHour == HOURS_IN_HALF_DAY) { + mIsAm = !mIsAm; + updateAmPmControl(); + } + mHourSpinner.setValue(newHour); + } else if (oldVal == minValue && newVal == maxValue) { + int newHour = mHourSpinner.getValue() - 1; + if (!is24HourView() && newHour == HOURS_IN_HALF_DAY - 1) { + mIsAm = !mIsAm; + updateAmPmControl(); + } + mHourSpinner.setValue(newHour); + } + onTimeChanged(); + } + }); + mMinuteSpinnerInput = (EditText) mMinuteSpinner.findViewById(R.id.numberpicker_input); + mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT); + + /* Get the localized am/pm strings and use them in the spinner */ + mAmPmStrings = new DateFormatSymbols().getAmPmStrings(); + + // am/pm + View amPmView = mDelegator.findViewById(R.id.amPm); + if (amPmView instanceof Button) { + mAmPmSpinner = null; + mAmPmSpinnerInput = null; + mAmPmButton = (Button) amPmView; + mAmPmButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View button) { + button.requestFocus(); + mIsAm = !mIsAm; + updateAmPmControl(); + onTimeChanged(); + } + }); + } else { + mAmPmButton = null; + mAmPmSpinner = (NumberPicker) amPmView; + mAmPmSpinner.setMinValue(0); + mAmPmSpinner.setMaxValue(1); + mAmPmSpinner.setDisplayedValues(mAmPmStrings); + mAmPmSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + updateInputState(); + picker.requestFocus(); + mIsAm = !mIsAm; + updateAmPmControl(); + onTimeChanged(); + } + }); + mAmPmSpinnerInput = (EditText) mAmPmSpinner.findViewById(R.id.numberpicker_input); + mAmPmSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_DONE); + } + + if (isAmPmAtStart()) { + // Move the am/pm view to the beginning + ViewGroup amPmParent = (ViewGroup) delegator.findViewById(R.id.timePickerLayout); + amPmParent.removeView(amPmView); + amPmParent.addView(amPmView, 0); + // Swap layout margins if needed. They may be not symmetrical (Old Standard Theme + // for example and not for Holo Theme) + ViewGroup.MarginLayoutParams lp = + (ViewGroup.MarginLayoutParams) amPmView.getLayoutParams(); + final int startMargin = lp.getMarginStart(); + final int endMargin = lp.getMarginEnd(); + if (startMargin != endMargin) { + lp.setMarginStart(endMargin); + lp.setMarginEnd(startMargin); + } + } + + getHourFormatData(); + + // update controls to initial state + updateHourControl(); + updateMinuteControl(); + updateAmPmControl(); + + setOnTimeChangedListener(NO_OP_CHANGE_LISTENER); + + // set to current time + setCurrentHour(mTempCalendar.get(Calendar.HOUR_OF_DAY)); + setCurrentMinute(mTempCalendar.get(Calendar.MINUTE)); + + if (!isEnabled()) { + setEnabled(false); + } + + // set the content descriptions + setContentDescriptions(); + + // If not explicitly specified this view is important for accessibility. + if (mDelegator.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { + mDelegator.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); + } + } + + private void getHourFormatData() { + final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale, + (mIs24HourView) ? "Hm" : "hm"); + final int lengthPattern = bestDateTimePattern.length(); + mHourWithTwoDigit = false; + char hourFormat = '\0'; + // Check if the returned pattern is single or double 'H', 'h', 'K', 'k'. We also save + // the hour format that we found. + for (int i = 0; i < lengthPattern; i++) { + final char c = bestDateTimePattern.charAt(i); + if (c == 'H' || c == 'h' || c == 'K' || c == 'k') { + mHourFormat = c; + if (i + 1 < lengthPattern && c == bestDateTimePattern.charAt(i + 1)) { + mHourWithTwoDigit = true; + } + break; + } + } + } + + private boolean isAmPmAtStart() { + final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale, + "hm" /* skeleton */); + + return bestDateTimePattern.startsWith("a"); + } + + /** + * The time separator is defined in the Unicode CLDR and cannot be supposed to be ":". + * + * See http://unicode.org/cldr/trac/browser/trunk/common/main + * + * We pass the correct "skeleton" depending on 12 or 24 hours view and then extract the + * separator as the character which is just after the hour marker in the returned pattern. + */ + private void setDividerText() { + final String skeleton = (mIs24HourView) ? "Hm" : "hm"; + final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale, + skeleton); + final String separatorText; + int hourIndex = bestDateTimePattern.lastIndexOf('H'); + if (hourIndex == -1) { + hourIndex = bestDateTimePattern.lastIndexOf('h'); + } + if (hourIndex == -1) { + // Default case + separatorText = ":"; + } else { + int minuteIndex = bestDateTimePattern.indexOf('m', hourIndex + 1); + if (minuteIndex == -1) { + separatorText = Character.toString(bestDateTimePattern.charAt(hourIndex + 1)); + } else { + separatorText = bestDateTimePattern.substring(hourIndex + 1, minuteIndex); + } + } + mDivider.setText(separatorText); + } + + @Override + public void setCurrentHour(Integer currentHour) { + setCurrentHour(currentHour, true); + } + + private void setCurrentHour(Integer currentHour, boolean notifyTimeChanged) { + // why was Integer used in the first place? + if (currentHour == null || currentHour == getCurrentHour()) { + return; + } + if (!is24HourView()) { + // convert [0,23] ordinal to wall clock display + if (currentHour >= HOURS_IN_HALF_DAY) { + mIsAm = false; + if (currentHour > HOURS_IN_HALF_DAY) { + currentHour = currentHour - HOURS_IN_HALF_DAY; + } + } else { + mIsAm = true; + if (currentHour == 0) { + currentHour = HOURS_IN_HALF_DAY; + } + } + updateAmPmControl(); + } + mHourSpinner.setValue(currentHour); + if (notifyTimeChanged) { + onTimeChanged(); + } + } + + @Override + public Integer getCurrentHour() { + int currentHour = mHourSpinner.getValue(); + if (is24HourView()) { + return currentHour; + } else if (mIsAm) { + return currentHour % HOURS_IN_HALF_DAY; + } else { + return (currentHour % HOURS_IN_HALF_DAY) + HOURS_IN_HALF_DAY; + } + } + + @Override + public void setCurrentMinute(Integer currentMinute) { + if (currentMinute == getCurrentMinute()) { + return; + } + mMinuteSpinner.setValue(currentMinute); + onTimeChanged(); + } + + @Override + public Integer getCurrentMinute() { + return mMinuteSpinner.getValue(); + } + + @Override + public void setIs24HourView(Boolean is24HourView) { + if (mIs24HourView == is24HourView) { + return; + } + // cache the current hour since spinner range changes and BEFORE changing mIs24HourView!! + int currentHour = getCurrentHour(); + // Order is important here. + mIs24HourView = is24HourView; + getHourFormatData(); + updateHourControl(); + // set value after spinner range is updated + setCurrentHour(currentHour, false); + updateMinuteControl(); + updateAmPmControl(); + } + + @Override + public boolean is24HourView() { + return mIs24HourView; + } + + @Override + public void setOnTimeChangedListener(TimePicker.OnTimeChangedListener onTimeChangedListener) { + mOnTimeChangedListener = onTimeChangedListener; + } + + @Override + public void setEnabled(boolean enabled) { + mMinuteSpinner.setEnabled(enabled); + if (mDivider != null) { + mDivider.setEnabled(enabled); + } + mHourSpinner.setEnabled(enabled); + if (mAmPmSpinner != null) { + mAmPmSpinner.setEnabled(enabled); + } else { + mAmPmButton.setEnabled(enabled); + } + mIsEnabled = enabled; + } + + @Override + public boolean isEnabled() { + return mIsEnabled; + } + + @Override + public void setShowDoneButton(boolean showDoneButton) { + // Nothing to do + } + + @Override + public void setDismissCallback(TimePicker.TimePickerDismissCallback callback) { + // Nothing to do + } + + @Override + public int getBaseline() { + return mHourSpinner.getBaseline(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + setCurrentLocale(newConfig.locale); + } + + @Override + public Parcelable onSaveInstanceState(Parcelable superState) { + return new SavedState(superState, getCurrentHour(), getCurrentMinute()); + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + SavedState ss = (SavedState) state; + setCurrentHour(ss.getHour()); + setCurrentMinute(ss.getMinute()); + } + + @Override + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + onPopulateAccessibilityEvent(event); + return true; + } + + @Override + public void onPopulateAccessibilityEvent(AccessibilityEvent event) { + int flags = DateUtils.FORMAT_SHOW_TIME; + if (mIs24HourView) { + flags |= DateUtils.FORMAT_24HOUR; + } else { + flags |= DateUtils.FORMAT_12HOUR; + } + mTempCalendar.set(Calendar.HOUR_OF_DAY, getCurrentHour()); + mTempCalendar.set(Calendar.MINUTE, getCurrentMinute()); + String selectedDateUtterance = DateUtils.formatDateTime(mContext, + mTempCalendar.getTimeInMillis(), flags); + event.getText().add(selectedDateUtterance); + } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + event.setClassName(TimePicker.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + info.setClassName(TimePicker.class.getName()); + } + + private void updateInputState() { + // Make sure that if the user changes the value and the IME is active + // for one of the inputs if this widget, the IME is closed. If the user + // changed the value via the IME and there is a next input the IME will + // be shown, otherwise the user chose another means of changing the + // value and having the IME up makes no sense. + InputMethodManager inputMethodManager = InputMethodManager.peekInstance(); + if (inputMethodManager != null) { + if (inputMethodManager.isActive(mHourSpinnerInput)) { + mHourSpinnerInput.clearFocus(); + inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0); + } else if (inputMethodManager.isActive(mMinuteSpinnerInput)) { + mMinuteSpinnerInput.clearFocus(); + inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0); + } else if (inputMethodManager.isActive(mAmPmSpinnerInput)) { + mAmPmSpinnerInput.clearFocus(); + inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0); + } + } + } + + private void updateAmPmControl() { + if (is24HourView()) { + if (mAmPmSpinner != null) { + mAmPmSpinner.setVisibility(View.GONE); + } else { + mAmPmButton.setVisibility(View.GONE); + } + } else { + int index = mIsAm ? Calendar.AM : Calendar.PM; + if (mAmPmSpinner != null) { + mAmPmSpinner.setValue(index); + mAmPmSpinner.setVisibility(View.VISIBLE); + } else { + mAmPmButton.setText(mAmPmStrings[index]); + mAmPmButton.setVisibility(View.VISIBLE); + } + } + mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); + } + + /** + * Sets the current locale. + * + * @param locale The current locale. + */ + @Override + public void setCurrentLocale(Locale locale) { + super.setCurrentLocale(locale); + mTempCalendar = Calendar.getInstance(locale); + } + + private void onTimeChanged() { + mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); + if (mOnTimeChangedListener != null) { + mOnTimeChangedListener.onTimeChanged(mDelegator, getCurrentHour(), + getCurrentMinute()); + } + } + + private void updateHourControl() { + if (is24HourView()) { + // 'k' means 1-24 hour + if (mHourFormat == 'k') { + mHourSpinner.setMinValue(1); + mHourSpinner.setMaxValue(24); + } else { + mHourSpinner.setMinValue(0); + mHourSpinner.setMaxValue(23); + } + } else { + // 'K' means 0-11 hour + if (mHourFormat == 'K') { + mHourSpinner.setMinValue(0); + mHourSpinner.setMaxValue(11); + } else { + mHourSpinner.setMinValue(1); + mHourSpinner.setMaxValue(12); + } + } + mHourSpinner.setFormatter(mHourWithTwoDigit ? NumberPicker.getTwoDigitFormatter() : null); + } + + private void updateMinuteControl() { + if (is24HourView()) { + mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_DONE); + } else { + mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT); + } + } + + private void setContentDescriptions() { + // Minute + trySetContentDescription(mMinuteSpinner, R.id.increment, + R.string.time_picker_increment_minute_button); + trySetContentDescription(mMinuteSpinner, R.id.decrement, + R.string.time_picker_decrement_minute_button); + // Hour + trySetContentDescription(mHourSpinner, R.id.increment, + R.string.time_picker_increment_hour_button); + trySetContentDescription(mHourSpinner, R.id.decrement, + R.string.time_picker_decrement_hour_button); + // AM/PM + if (mAmPmSpinner != null) { + trySetContentDescription(mAmPmSpinner, R.id.increment, + R.string.time_picker_increment_set_pm_button); + trySetContentDescription(mAmPmSpinner, R.id.decrement, + R.string.time_picker_decrement_set_am_button); + } + } + + private void trySetContentDescription(View root, int viewId, int contDescResId) { + View target = root.findViewById(viewId); + if (target != null) { + target.setContentDescription(mContext.getString(contDescResId)); + } + } + + /** + * Used to save / restore state of time picker + */ + private static class SavedState extends View.BaseSavedState { + + private final int mHour; + + private final int mMinute; + + private SavedState(Parcelable superState, int hour, int minute) { + super(superState); + mHour = hour; + mMinute = minute; + } + + private SavedState(Parcel in) { + super(in); + mHour = in.readInt(); + mMinute = in.readInt(); + } + + public int getHour() { + return mHour; + } + + public int getMinute() { + return mMinute; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(mHour); + dest.writeInt(mMinute); + } + + @SuppressWarnings({"unused", "hiding"}) + public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() { + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } +} + diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java index ad60a95..82e624d 100644 --- a/core/java/android/widget/LinearLayout.java +++ b/core/java/android/widget/LinearLayout.java @@ -18,6 +18,7 @@ package android.widget; import com.android.internal.R; +import android.annotation.IntDef; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; @@ -31,6 +32,9 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.RemoteViews.RemoteView; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * A Layout that arranges its children in a single column or a single row. The direction of @@ -57,9 +61,25 @@ import android.widget.RemoteViews.RemoteView; */ @RemoteView public class LinearLayout extends ViewGroup { + /** @hide */ + @IntDef({HORIZONTAL, VERTICAL}) + @Retention(RetentionPolicy.SOURCE) + public @interface OrientationMode {} + public static final int HORIZONTAL = 0; public static final int VERTICAL = 1; + /** @hide */ + @IntDef(flag = true, + value = { + SHOW_DIVIDER_NONE, + SHOW_DIVIDER_BEGINNING, + SHOW_DIVIDER_MIDDLE, + SHOW_DIVIDER_END + }) + @Retention(RetentionPolicy.SOURCE) + public @interface DividerMode {} + /** * Don't show any dividers. */ @@ -165,18 +185,22 @@ public class LinearLayout extends ViewGroup { private int mDividerPadding; public LinearLayout(Context context) { - super(context); + this(context, null); } public LinearLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } - public LinearLayout(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public LinearLayout(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public LinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); - TypedArray a = context.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.LinearLayout, defStyle, 0); + final TypedArray a = context.obtainStyledAttributes( + attrs, com.android.internal.R.styleable.LinearLayout, defStyleAttr, defStyleRes); int index = a.getInt(com.android.internal.R.styleable.LinearLayout_orientation, -1); if (index >= 0) { @@ -214,7 +238,7 @@ public class LinearLayout extends ViewGroup { * {@link #SHOW_DIVIDER_MIDDLE}, or {@link #SHOW_DIVIDER_END}, * or {@link #SHOW_DIVIDER_NONE} to show no dividers. */ - public void setShowDividers(int showDividers) { + public void setShowDividers(@DividerMode int showDividers) { if (showDividers != mShowDividers) { requestLayout(); } @@ -230,6 +254,7 @@ public class LinearLayout extends ViewGroup { * @return A flag set indicating how dividers should be shown around items. * @see #setShowDividers(int) */ + @DividerMode public int getShowDividers() { return mShowDividers; } @@ -642,6 +667,7 @@ public class LinearLayout extends ViewGroup { final int heightMode = MeasureSpec.getMode(heightMeasureSpec); boolean matchWidth = false; + boolean skippedMeasure = false; final int baselineChildIndex = mBaselineAlignedChildIndex; final boolean useLargestChild = mUseLargestChild; @@ -676,6 +702,7 @@ public class LinearLayout extends ViewGroup { // there is any leftover space. final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin); + skippedMeasure = true; } else { int oldHeight = Integer.MIN_VALUE; @@ -802,9 +829,10 @@ public class LinearLayout extends ViewGroup { heightSize = heightSizeAndState & MEASURED_SIZE_MASK; // Either expand children with weight to take up available space or - // shrink them if they extend beyond our current bounds + // shrink them if they extend beyond our current bounds. If we skipped + // measurement on any children, we need to measure them now. int delta = heightSize - mTotalLength; - if (delta != 0 && totalWeight > 0.0f) { + if (skippedMeasure || delta != 0 && totalWeight > 0.0f) { float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight; mTotalLength = 0; @@ -970,6 +998,7 @@ public class LinearLayout extends ViewGroup { final int heightMode = MeasureSpec.getMode(heightMeasureSpec); boolean matchHeight = false; + boolean skippedMeasure = false; if (mMaxAscent == null || mMaxDescent == null) { mMaxAscent = new int[VERTICAL_GRAVITY_COUNT]; @@ -1032,6 +1061,8 @@ public class LinearLayout extends ViewGroup { if (baselineAligned) { final int freeSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); child.measure(freeSpec, freeSpec); + } else { + skippedMeasure = true; } } else { int oldWidth = Integer.MIN_VALUE; @@ -1180,9 +1211,10 @@ public class LinearLayout extends ViewGroup { widthSize = widthSizeAndState & MEASURED_SIZE_MASK; // Either expand children with weight to take up available space or - // shrink them if they extend beyond our current bounds + // shrink them if they extend beyond our current bounds. If we skipped + // measurement on any children, we need to measure them now. int delta = widthSize - mTotalLength; - if (delta != 0 && totalWeight > 0.0f) { + if (skippedMeasure || delta != 0 && totalWeight > 0.0f) { float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight; maxAscent[0] = maxAscent[1] = maxAscent[2] = maxAscent[3] = -1; @@ -1673,12 +1705,12 @@ public class LinearLayout extends ViewGroup { /** * Should the layout be a column or a row. - * @param orientation Pass HORIZONTAL or VERTICAL. Default - * value is HORIZONTAL. + * @param orientation Pass {@link #HORIZONTAL} or {@link #VERTICAL}. Default + * value is {@link #HORIZONTAL}. * * @attr ref android.R.styleable#LinearLayout_orientation */ - public void setOrientation(int orientation) { + public void setOrientation(@OrientationMode int orientation) { if (mOrientation != orientation) { mOrientation = orientation; requestLayout(); @@ -1690,6 +1722,7 @@ public class LinearLayout extends ViewGroup { * * @return either {@link #HORIZONTAL} or {@link #VERTICAL} */ + @OrientationMode public int getOrientation() { return mOrientation; } diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java index 66fe46f..64953f8 100644 --- a/core/java/android/widget/ListPopupWindow.java +++ b/core/java/android/widget/ListPopupWindow.java @@ -33,7 +33,6 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.View.MeasureSpec; -import android.view.View.OnAttachStateChangeListener; import android.view.View.OnTouchListener; import android.view.ViewConfiguration; import android.view.ViewGroup; diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java index 78237c3..5de67c8 100644 --- a/core/java/android/widget/ListView.java +++ b/core/java/android/widget/ListView.java @@ -44,6 +44,7 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo; import android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo; +import android.view.accessibility.AccessibilityNodeProvider; import android.widget.RemoteViews.RemoteView; import java.util.ArrayList; @@ -142,11 +143,15 @@ public class ListView extends AbsListView { this(context, attrs, com.android.internal.R.attr.listViewStyle); } - public ListView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public ListView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public ListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); - TypedArray a = context.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.ListView, defStyle, 0); + final TypedArray a = context.obtainStyledAttributes( + attrs, com.android.internal.R.styleable.ListView, defStyleAttr, defStyleRes); CharSequence[] entries = a.getTextArray( com.android.internal.R.styleable.ListView_entries); @@ -263,6 +268,7 @@ public class ListView extends AbsListView { info.data = data; info.isSelectable = isSelectable; mHeaderViewInfos.add(info); + mAreAllItemsSelectable &= isSelectable; // Wrap the adapter if it wasn't already wrapped. if (mAdapter != null) { @@ -356,6 +362,7 @@ public class ListView extends AbsListView { info.data = data; info.isSelectable = isSelectable; mFooterViewInfos.add(info); + mAreAllItemsSelectable &= isSelectable; // Wrap the adapter if it wasn't already wrapped. if (mAdapter != null) { @@ -1562,22 +1569,58 @@ public class ListView extends AbsListView { setSelectedPositionInt(mNextSelectedPosition); - // Remember which child, if any, had accessibility focus. - final int accessibilityFocusPosition; - final View accessFocusedChild = getAccessibilityFocusedChild(); - if (accessFocusedChild != null) { - accessibilityFocusPosition = getPositionForView(accessFocusedChild); - accessFocusedChild.setHasTransientState(true); - } else { - accessibilityFocusPosition = INVALID_POSITION; + AccessibilityNodeInfo accessibilityFocusLayoutRestoreNode = null; + View accessibilityFocusLayoutRestoreView = null; + int accessibilityFocusPosition = INVALID_POSITION; + + // Remember which child, if any, had accessibility focus. This must + // occur before recycling any views, since that will clear + // accessibility focus. + final ViewRootImpl viewRootImpl = getViewRootImpl(); + if (viewRootImpl != null) { + final View focusHost = viewRootImpl.getAccessibilityFocusedHost(); + if (focusHost != null) { + final View focusChild = getAccessibilityFocusedChild(focusHost); + if (focusChild != null) { + if (!dataChanged || isDirectChildHeaderOrFooter(focusChild) + || focusChild.hasTransientState() || mAdapterHasStableIds) { + // The views won't be changing, so try to maintain + // focus on the current host and virtual view. + accessibilityFocusLayoutRestoreView = focusHost; + accessibilityFocusLayoutRestoreNode = viewRootImpl + .getAccessibilityFocusedVirtualView(); + } + + // If all else fails, maintain focus at the same + // position. + accessibilityFocusPosition = getPositionForView(focusChild); + } + } } - // Ensure the child containing focus, if any, has transient state. - // If the list data hasn't changed, or if the adapter has stable - // IDs, this will maintain focus. + View focusLayoutRestoreDirectChild = null; + View focusLayoutRestoreView = null; + + // Take focus back to us temporarily to avoid the eventual call to + // clear focus when removing the focused child below from messing + // things up when ViewAncestor assigns focus back to someone else. final View focusedChild = getFocusedChild(); if (focusedChild != null) { - focusedChild.setHasTransientState(true); + // TODO: in some cases focusedChild.getParent() == null + + // We can remember the focused view to restore after re-layout + // if the data hasn't changed, or if the focused position is a + // header or footer. + if (!dataChanged || isDirectChildHeaderOrFooter(focusedChild)) { + focusLayoutRestoreDirectChild = focusedChild; + // Remember the specific view that had focus. + focusLayoutRestoreView = findFocus(); + if (focusLayoutRestoreView != null) { + // Tell it we are going to mess with it. + focusLayoutRestoreView.onStartTemporaryDetach(); + } + } + requestFocus(); } // Pull all children into the RecycleBin. @@ -1651,20 +1694,24 @@ public class ListView extends AbsListView { recycleBin.scrapActiveViews(); if (sel != null) { - final boolean shouldPlaceFocus = mItemsCanFocus && hasFocus(); - final boolean maintainedFocus = focusedChild != null && focusedChild.hasFocus(); - if (shouldPlaceFocus && !maintainedFocus && !sel.hasFocus()) { - if (sel.requestFocus()) { - // Successfully placed focus, clear selection. - sel.setSelected(false); - mSelectorRect.setEmpty(); - } else { - // Failed to place focus, clear current (invalid) focus. + // The current selected item should get focus if items are + // focusable. + if (mItemsCanFocus && hasFocus() && !sel.hasFocus()) { + final boolean focusWasTaken = (sel == focusLayoutRestoreDirectChild && + focusLayoutRestoreView != null && + focusLayoutRestoreView.requestFocus()) || sel.requestFocus(); + if (!focusWasTaken) { + // Selected item didn't take focus, but we still want to + // make sure something else outside of the selected view + // has focus. final View focused = getFocusedChild(); if (focused != null) { focused.clearFocus(); } positionSelector(INVALID_POSITION, sel); + } else { + sel.setSelected(false); + mSelectorRect.setEmpty(); } } else { positionSelector(INVALID_POSITION, sel); @@ -1682,27 +1729,48 @@ public class ListView extends AbsListView { mSelectedTop = 0; mSelectorRect.setEmpty(); } - } - - if (accessFocusedChild != null) { - accessFocusedChild.setHasTransientState(false); - // If we failed to maintain accessibility focus on the previous - // view, attempt to restore it to the previous position. - if (!accessFocusedChild.isAccessibilityFocused() - && accessibilityFocusPosition != INVALID_POSITION) { - // Bound the position within the visible children. - final int position = MathUtils.constrain( - accessibilityFocusPosition - mFirstPosition, 0, getChildCount() - 1); - final View restoreView = getChildAt(position); - if (restoreView != null) { - restoreView.requestAccessibilityFocus(); + // Even if there is not selected position, we may need to + // restore focus (i.e. something focusable in touch mode). + if (hasFocus() && focusLayoutRestoreView != null) { + focusLayoutRestoreView.requestFocus(); + } + } + + // Attempt to restore accessibility focus, if necessary. + if (viewRootImpl != null) { + final View newAccessibilityFocusedView = viewRootImpl.getAccessibilityFocusedHost(); + if (newAccessibilityFocusedView == null) { + if (accessibilityFocusLayoutRestoreView != null + && accessibilityFocusLayoutRestoreView.isAttachedToWindow()) { + final AccessibilityNodeProvider provider = + accessibilityFocusLayoutRestoreView.getAccessibilityNodeProvider(); + if (accessibilityFocusLayoutRestoreNode != null && provider != null) { + final int virtualViewId = AccessibilityNodeInfo.getVirtualDescendantId( + accessibilityFocusLayoutRestoreNode.getSourceNodeId()); + provider.performAction(virtualViewId, + AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null); + } else { + accessibilityFocusLayoutRestoreView.requestAccessibilityFocus(); + } + } else if (accessibilityFocusPosition != INVALID_POSITION) { + // Bound the position within the visible children. + final int position = MathUtils.constrain( + accessibilityFocusPosition - mFirstPosition, 0, + getChildCount() - 1); + final View restoreView = getChildAt(position); + if (restoreView != null) { + restoreView.requestAccessibilityFocus(); + } } } } - if (focusedChild != null) { - focusedChild.setHasTransientState(false); + // Tell focus view we are done mucking with it, if it is still in + // our view hierarchy. + if (focusLayoutRestoreView != null + && focusLayoutRestoreView.getWindowToken() != null) { + focusLayoutRestoreView.onFinishTemporaryDetach(); } mLayoutMode = LAYOUT_NORMAL; @@ -1729,31 +1797,27 @@ public class ListView extends AbsListView { } /** - * @return the direct child that contains accessibility focus, or null if no - * child contains accessibility focus + * @param child a direct child of this list. + * @return Whether child is a header or footer view. */ - private View getAccessibilityFocusedChild() { - final ViewRootImpl viewRootImpl = getViewRootImpl(); - if (viewRootImpl == null) { - return null; - } - - View focusedView = viewRootImpl.getAccessibilityFocusedHost(); - if (focusedView == null) { - return null; - } - - ViewParent viewParent = focusedView.getParent(); - while ((viewParent instanceof View) && (viewParent != this)) { - focusedView = (View) viewParent; - viewParent = viewParent.getParent(); + private boolean isDirectChildHeaderOrFooter(View child) { + final ArrayList<FixedViewInfo> headers = mHeaderViewInfos; + final int numHeaders = headers.size(); + for (int i = 0; i < numHeaders; i++) { + if (child == headers.get(i).view) { + return true; + } } - if (!(viewParent instanceof View)) { - return null; + final ArrayList<FixedViewInfo> footers = mFooterViewInfos; + final int numFooters = footers.size(); + for (int i = 0; i < numFooters; i++) { + if (child == footers.get(i).view) { + return true; + } } - return focusedView; + return false; } /** @@ -1915,45 +1979,6 @@ public class ListView extends AbsListView { } /** - * Sets the selected item and positions the selection y pixels from the top edge - * of the ListView. (If in touch mode, the item will not be selected but it will - * still be positioned appropriately.) - * - * @param position Index (starting at 0) of the data item to be selected. - * @param y The distance from the top edge of the ListView (plus padding) that the - * item will be positioned. - */ - public void setSelectionFromTop(int position, int y) { - if (mAdapter == null) { - return; - } - - if (!isInTouchMode()) { - position = lookForSelectablePosition(position, true); - if (position >= 0) { - setNextSelectedPositionInt(position); - } - } else { - mResurrectToPosition = position; - } - - if (position >= 0) { - mLayoutMode = LAYOUT_SPECIFIC; - mSpecificTop = mListPadding.top + y; - - if (mNeedSync) { - mSyncPosition = position; - mSyncRowId = mAdapter.getItemId(position); - } - - if (mPositionScroller != null) { - mPositionScroller.stop(); - } - requestLayout(); - } - } - - /** * Makes the item at the supplied position selected. * * @param position the position of the item to select @@ -3266,14 +3291,13 @@ public class ListView extends AbsListView { if (drawDividers && (bottom < listBottom) && !(drawOverscrollFooter && isLastItem)) { final int nextIndex = (itemIndex + 1); - // Draw dividers between enabled items, headers and/or - // footers when enabled, and the end of the list. - if (areAllItemsSelectable || ((adapter.isEnabled(itemIndex) - || (headerDividers && isHeader) - || (footerDividers && isFooter)) && (isLastItem - || adapter.isEnabled(nextIndex) - || (headerDividers && (nextIndex < headerCount)) - || (footerDividers && (nextIndex >= footerLimit))))) { + // Draw dividers between enabled items, headers + // and/or footers when enabled and requested, and + // after the last enabled item. + if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader + && (nextIndex >= headerCount)) && (isLastItem + || adapter.isEnabled(nextIndex) && (footerDividers || !isFooter + && (nextIndex < footerLimit)))) { bounds.top = bottom; bounds.bottom = bottom + dividerHeight; drawDivider(canvas, bounds, i); @@ -3315,14 +3339,13 @@ public class ListView extends AbsListView { if (drawDividers && (top > effectivePaddingTop)) { final boolean isFirstItem = (i == start); final int previousIndex = (itemIndex - 1); - // Draw dividers between enabled items, headers and/or - // footers when enabled, and the end of the list. - if (areAllItemsSelectable || ((adapter.isEnabled(itemIndex) - || (headerDividers && isHeader) - || (footerDividers && isFooter)) && (isFirstItem - || adapter.isEnabled(previousIndex) - || (headerDividers && (previousIndex < headerCount)) - || (footerDividers && (previousIndex >= footerLimit))))) { + // Draw dividers between enabled items, headers + // and/or footers when enabled and requested, and + // before the first enabled item. + if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader + && (previousIndex >= headerCount)) && (isFirstItem || + adapter.isEnabled(previousIndex) && (footerDividers || !isFooter + && (previousIndex < footerLimit)))) { bounds.top = top - dividerHeight; bounds.bottom = top; // Give the method the child ABOVE the divider, @@ -3771,6 +3794,79 @@ public class ListView extends AbsListView { } @Override + int getHeightForPosition(int position) { + final int height = super.getHeightForPosition(position); + if (shouldAdjustHeightForDivider(position)) { + return height + mDividerHeight; + } + return height; + } + + private boolean shouldAdjustHeightForDivider(int itemIndex) { + final int dividerHeight = mDividerHeight; + final Drawable overscrollHeader = mOverScrollHeader; + final Drawable overscrollFooter = mOverScrollFooter; + final boolean drawOverscrollHeader = overscrollHeader != null; + final boolean drawOverscrollFooter = overscrollFooter != null; + final boolean drawDividers = dividerHeight > 0 && mDivider != null; + + if (drawDividers) { + final boolean fillForMissingDividers = isOpaque() && !super.isOpaque(); + final int itemCount = mItemCount; + final int headerCount = mHeaderViewInfos.size(); + final int footerLimit = (itemCount - mFooterViewInfos.size()); + final boolean isHeader = (itemIndex < headerCount); + final boolean isFooter = (itemIndex >= footerLimit); + final boolean headerDividers = mHeaderDividersEnabled; + final boolean footerDividers = mFooterDividersEnabled; + if ((headerDividers || !isHeader) && (footerDividers || !isFooter)) { + final ListAdapter adapter = mAdapter; + if (!mStackFromBottom) { + final boolean isLastItem = (itemIndex == (itemCount - 1)); + if (!drawOverscrollFooter || !isLastItem) { + final int nextIndex = itemIndex + 1; + // Draw dividers between enabled items, headers + // and/or footers when enabled and requested, and + // after the last enabled item. + if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader + && (nextIndex >= headerCount)) && (isLastItem + || adapter.isEnabled(nextIndex) && (footerDividers || !isFooter + && (nextIndex < footerLimit)))) { + return true; + } else if (fillForMissingDividers) { + return true; + } + } + } else { + final int start = drawOverscrollHeader ? 1 : 0; + final boolean isFirstItem = (itemIndex == start); + if (!isFirstItem) { + final int previousIndex = (itemIndex - 1); + // Draw dividers between enabled items, headers + // and/or footers when enabled and requested, and + // before the first enabled item. + if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader + && (previousIndex >= headerCount)) && (isFirstItem || + adapter.isEnabled(previousIndex) && (footerDividers || !isFooter + && (previousIndex < footerLimit)))) { + return true; + } else if (fillForMissingDividers) { + return true; + } + } + } + } + } + + return false; + } + + @Override + AbsPositionScroller createPositionScroller() { + return new ListViewPositionScroller(); + } + + @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); event.setClassName(ListView.class.getName()); @@ -3782,7 +3878,8 @@ public class ListView extends AbsListView { info.setClassName(ListView.class.getName()); final int count = getCount(); - final CollectionInfo collectionInfo = CollectionInfo.obtain(1, count, false); + final int selectionMode = getSelectionModeForAccessibility(); + final CollectionInfo collectionInfo = CollectionInfo.obtain(1, count, false, selectionMode); info.setCollectionInfo(collectionInfo); } @@ -3793,7 +3890,29 @@ public class ListView extends AbsListView { final LayoutParams lp = (LayoutParams) view.getLayoutParams(); final boolean isHeading = lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER; - final CollectionItemInfo itemInfo = CollectionItemInfo.obtain(0, 1, position, 1, isHeading); + final boolean isSelected = isItemChecked(position); + final CollectionItemInfo itemInfo = CollectionItemInfo.obtain( + 0, 1, position, 1, isHeading, isSelected); info.setCollectionItemInfo(itemInfo); } + + /** + * Sub-position scroller that understands the layout of a ListView. + */ + class ListViewPositionScroller extends AbsSubPositionScroller { + @Override + public int getRowForPosition(int position) { + return position; + } + + @Override + public int getFirstPositionForRow(int row) { + return row; + } + + @Override + public int getHeightForRow(int row) { + return getHeightForPosition(row); + } + } } diff --git a/core/java/android/widget/MediaController.java b/core/java/android/widget/MediaController.java index 9c61fd6..546cc5f 100644 --- a/core/java/android/widget/MediaController.java +++ b/core/java/android/widget/MediaController.java @@ -442,7 +442,19 @@ public class MediaController extends FrameLayout { @Override public boolean onTouchEvent(MotionEvent event) { - show(sDefaultTimeout); + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + show(0); // show until hide is called + break; + case MotionEvent.ACTION_UP: + show(sDefaultTimeout); // start timeout + break; + case MotionEvent.ACTION_CANCEL: + hide(); + break; + default: + break; + } return true; } diff --git a/core/java/android/widget/MultiAutoCompleteTextView.java b/core/java/android/widget/MultiAutoCompleteTextView.java index 0b30c84..cbd01b0 100644 --- a/core/java/android/widget/MultiAutoCompleteTextView.java +++ b/core/java/android/widget/MultiAutoCompleteTextView.java @@ -67,8 +67,13 @@ public class MultiAutoCompleteTextView extends AutoCompleteTextView { this(context, attrs, com.android.internal.R.attr.autoCompleteTextViewStyle); } - public MultiAutoCompleteTextView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public MultiAutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public MultiAutoCompleteTextView( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); } /* package */ void finishInit() { } diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java index 9c6a2e3..00b2c13 100644 --- a/core/java/android/widget/NumberPicker.java +++ b/core/java/android/widget/NumberPicker.java @@ -16,6 +16,7 @@ package android.widget; +import android.annotation.IntDef; import android.annotation.Widget; import android.content.Context; import android.content.res.ColorStateList; @@ -53,6 +54,8 @@ import android.view.inputmethod.InputMethodManager; import com.android.internal.R; import libcore.icu.LocaleData; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -493,6 +496,10 @@ public class NumberPicker extends LinearLayout { * Interface to listen for the picker scroll state. */ public interface OnScrollListener { + /** @hide */ + @IntDef({SCROLL_STATE_IDLE, SCROLL_STATE_TOUCH_SCROLL, SCROLL_STATE_FLING}) + @Retention(RetentionPolicy.SOURCE) + public @interface ScrollState {} /** * The view is not scrolling. @@ -518,7 +525,7 @@ public class NumberPicker extends LinearLayout { * {@link #SCROLL_STATE_TOUCH_SCROLL} or * {@link #SCROLL_STATE_IDLE}. */ - public void onScrollStateChange(NumberPicker view, int scrollState); + public void onScrollStateChange(NumberPicker view, @ScrollState int scrollState); } /** @@ -559,14 +566,33 @@ public class NumberPicker extends LinearLayout { * * @param context the application environment. * @param attrs a collection of attributes. - * @param defStyle The default style to apply to this view. + * @param defStyleAttr An attribute in the current theme that contains a + * reference to a style resource that supplies default values for + * the view. Can be 0 to not look for defaults. */ - public NumberPicker(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public NumberPicker(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + /** + * Create a new number picker + * + * @param context the application environment. + * @param attrs a collection of attributes. + * @param defStyleAttr An attribute in the current theme that contains a + * reference to a style resource that supplies default values for + * the view. Can be 0 to not look for defaults. + * @param defStyleRes A resource identifier of a style resource that + * supplies default values for the view, used only if + * defStyleAttr is 0 or can not be found in the theme. Can be 0 + * to not look for defaults. + */ + public NumberPicker(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); // process style attributes - TypedArray attributesArray = context.obtainStyledAttributes( - attrs, R.styleable.NumberPicker, defStyle, 0); + final TypedArray attributesArray = context.obtainStyledAttributes( + attrs, R.styleable.NumberPicker, defStyleAttr, defStyleRes); final int layoutResId = attributesArray.getResourceId( R.styleable.NumberPicker_internalLayout, DEFAULT_LAYOUT_RESOURCE_ID); diff --git a/core/java/android/widget/OverScroller.java b/core/java/android/widget/OverScroller.java index f218199..7b3dd31 100644 --- a/core/java/android/widget/OverScroller.java +++ b/core/java/android/widget/OverScroller.java @@ -70,7 +70,11 @@ public class OverScroller { * @hide */ public OverScroller(Context context, Interpolator interpolator, boolean flywheel) { - mInterpolator = interpolator; + if (interpolator == null) { + mInterpolator = new Scroller.ViscousFluidInterpolator(); + } else { + mInterpolator = interpolator; + } mFlywheel = flywheel; mScrollerX = new SplineOverScroller(context); mScrollerY = new SplineOverScroller(context); @@ -112,7 +116,11 @@ public class OverScroller { } void setInterpolator(Interpolator interpolator) { - mInterpolator = interpolator; + if (interpolator == null) { + mInterpolator = new Scroller.ViscousFluidInterpolator(); + } else { + mInterpolator = interpolator; + } } /** @@ -302,14 +310,7 @@ public class OverScroller { final int duration = mScrollerX.mDuration; if (elapsedTime < duration) { - float q = (float) (elapsedTime) / duration; - - if (mInterpolator == null) { - q = Scroller.viscousFluid(q); - } else { - q = mInterpolator.getInterpolation(q); - } - + final float q = mInterpolator.getInterpolation(elapsedTime / (float) duration); mScrollerX.updateScroll(q); mScrollerY.updateScroll(q); } else { diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index be20d2d..6e71a5c 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -170,8 +170,8 @@ public class PopupWindow { * * <p>The popup does provide a background.</p> */ - public PopupWindow(Context context, AttributeSet attrs, int defStyle) { - this(context, attrs, defStyle, 0); + public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); } /** @@ -183,8 +183,7 @@ public class PopupWindow { mContext = context; mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); - TypedArray a = - context.obtainStyledAttributes( + final TypedArray a = context.obtainStyledAttributes( attrs, com.android.internal.R.styleable.PopupWindow, defStyleAttr, defStyleRes); mBackground = a.getDrawable(R.styleable.PopupWindow_popupBackground); diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java index 6a369a6..f7e81b8 100644 --- a/core/java/android/widget/ProgressBar.java +++ b/core/java/android/widget/ProgressBar.java @@ -242,29 +242,26 @@ public class ProgressBar extends View { this(context, attrs, com.android.internal.R.attr.progressBarStyle); } - public ProgressBar(Context context, AttributeSet attrs, int defStyle) { - this(context, attrs, defStyle, 0); + public ProgressBar(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); } - /** - * @hide - */ - public ProgressBar(Context context, AttributeSet attrs, int defStyle, int styleRes) { - super(context, attrs, defStyle); + public ProgressBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + mUiThreadId = Thread.currentThread().getId(); initProgressBar(); - TypedArray a = - context.obtainStyledAttributes(attrs, R.styleable.ProgressBar, defStyle, styleRes); + final TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.ProgressBar, defStyleAttr, defStyleRes); mNoInvalidate = true; Drawable drawable = a.getDrawable(R.styleable.ProgressBar_progressDrawable); if (drawable != null) { - drawable = tileify(drawable, false); // Calling this method can set mMaxHeight, make sure the corresponding // XML attribute for mMaxHeight is read after calling this method - setProgressDrawable(drawable); + setProgressDrawableTiled(drawable); } @@ -293,8 +290,7 @@ public class ProgressBar extends View { drawable = a.getDrawable(R.styleable.ProgressBar_indeterminateDrawable); if (drawable != null) { - drawable = tileifyIndeterminate(drawable); - setIndeterminateDrawable(drawable); + setIndeterminateDrawableTiled(drawable); } mOnlyIndeterminate = a.getBoolean( @@ -350,19 +346,24 @@ public class ProgressBar extends View { return out; } else if (drawable instanceof BitmapDrawable) { - final Bitmap tileBitmap = ((BitmapDrawable) drawable).getBitmap(); + final BitmapDrawable bitmap = (BitmapDrawable) drawable; + final Bitmap tileBitmap = bitmap.getBitmap(); if (mSampleTile == null) { mSampleTile = tileBitmap; } - - final ShapeDrawable shapeDrawable = new ShapeDrawable(getDrawableShape()); + final ShapeDrawable shapeDrawable = new ShapeDrawable(getDrawableShape()); final BitmapShader bitmapShader = new BitmapShader(tileBitmap, Shader.TileMode.REPEAT, Shader.TileMode.CLAMP); shapeDrawable.getPaint().setShader(bitmapShader); - return (clip) ? new ClipDrawable(shapeDrawable, Gravity.LEFT, - ClipDrawable.HORIZONTAL) : shapeDrawable; + // Ensure the color filter and tint are propagated. + shapeDrawable.setTint(bitmap.getTint()); + shapeDrawable.setTintMode(bitmap.getTintMode()); + shapeDrawable.setColorFilter(bitmap.getColorFilter()); + + return clip ? new ClipDrawable( + shapeDrawable, Gravity.LEFT, ClipDrawable.HORIZONTAL) : shapeDrawable; } return drawable; @@ -472,11 +473,9 @@ public class ProgressBar extends View { } /** - * <p>Define the drawable used to draw the progress bar in - * indeterminate mode.</p> + * Define the drawable used to draw the progress bar in indeterminate mode. * * @param d the new drawable - * * @see #getIndeterminateDrawable() * @see #setIndeterminate(boolean) */ @@ -493,6 +492,25 @@ public class ProgressBar extends View { postInvalidate(); } } + + /** + * Define the tileable drawable used to draw the progress bar in + * indeterminate mode. + * <p> + * If the drawable is a BitmapDrawable or contains BitmapDrawables, a + * tiled copy will be generated for display as a progress bar. + * + * @param d the new drawable + * @see #getIndeterminateDrawable() + * @see #setIndeterminate(boolean) + */ + public void setIndeterminateDrawableTiled(Drawable d) { + if (d != null) { + d = tileifyIndeterminate(d); + } + + setIndeterminateDrawable(d); + } /** * <p>Get the drawable used to draw the progress bar in @@ -508,11 +526,9 @@ public class ProgressBar extends View { } /** - * <p>Define the drawable used to draw the progress bar in - * progress mode.</p> + * Define the drawable used to draw the progress bar in progress mode. * * @param d the new drawable - * * @see #getProgressDrawable() * @see #setIndeterminate(boolean) */ @@ -551,6 +567,25 @@ public class ProgressBar extends View { doRefreshProgress(R.id.secondaryProgress, mSecondaryProgress, false, false); } } + + /** + * Define the tileable drawable used to draw the progress bar in + * progress mode. + * <p> + * If the drawable is a BitmapDrawable or contains BitmapDrawables, a + * tiled copy will be generated for display as a progress bar. + * + * @param d the new drawable + * @see #getProgressDrawable() + * @see #setIndeterminate(boolean) + */ + public void setProgressDrawableTiled(Drawable d) { + if (d != null) { + d = tileify(d, false); + } + + setProgressDrawable(d); + } /** * @return The drawable currently used to draw the progress bar diff --git a/core/java/android/widget/QuickContactBadge.java b/core/java/android/widget/QuickContactBadge.java index fd2f754..74b41c9 100644 --- a/core/java/android/widget/QuickContactBadge.java +++ b/core/java/android/widget/QuickContactBadge.java @@ -84,8 +84,13 @@ public class QuickContactBadge extends ImageView implements OnClickListener { this(context, attrs, 0); } - public QuickContactBadge(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public QuickContactBadge(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public QuickContactBadge( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); TypedArray styledAttributes = mContext.obtainStyledAttributes(R.styleable.Theme); mOverlay = styledAttributes.getDrawable( @@ -150,7 +155,7 @@ public class QuickContactBadge extends ImageView implements OnClickListener { */ public void setImageToDefault() { if (mDefaultAvatar == null) { - mDefaultAvatar = getResources().getDrawable(R.drawable.ic_contact_picture); + mDefaultAvatar = mContext.getDrawable(R.drawable.ic_contact_picture); } setImageDrawable(mDefaultAvatar); } diff --git a/core/java/android/widget/RadialTimePickerView.java b/core/java/android/widget/RadialTimePickerView.java new file mode 100644 index 0000000..1c9ab61 --- /dev/null +++ b/core/java/android/widget/RadialTimePickerView.java @@ -0,0 +1,1396 @@ +/* + * 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.widget; + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.Keyframe; +import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; +import android.animation.ValueAnimator; +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Typeface; +import android.graphics.RectF; +import android.os.Bundle; +import android.text.format.DateUtils; +import android.text.format.Time; +import android.util.AttributeSet; +import android.util.Log; +import android.view.HapticFeedbackConstants; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; +import com.android.internal.R; + +import java.text.DateFormatSymbols; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Locale; + +/** + * View to show a clock circle picker (with one or two picking circles) + * + * @hide + */ +public class RadialTimePickerView extends View implements View.OnTouchListener { + private static final String TAG = "ClockView"; + + private static final boolean DEBUG = false; + + private static final int DEBUG_COLOR = 0x20FF0000; + private static final int DEBUG_TEXT_COLOR = 0x60FF0000; + private static final int DEBUG_STROKE_WIDTH = 2; + + private static final int HOURS = 0; + private static final int MINUTES = 1; + private static final int HOURS_INNER = 2; + private static final int AMPM = 3; + + private static final int SELECTOR_CIRCLE = 0; + private static final int SELECTOR_DOT = 1; + private static final int SELECTOR_LINE = 2; + + private static final int AM = 0; + private static final int PM = 1; + + // Opaque alpha level + private static final int ALPHA_OPAQUE = 255; + + // Transparent alpha level + private static final int ALPHA_TRANSPARENT = 0; + + // Alpha level of color for selector. + private static final int ALPHA_SELECTOR = 51; + + // Alpha level of color for selected circle. + private static final int ALPHA_AMPM_SELECTED = ALPHA_SELECTOR; + + // Alpha level of color for pressed circle. + private static final int ALPHA_AMPM_PRESSED = 175; + + private static final float COSINE_30_DEGREES = ((float) Math.sqrt(3)) * 0.5f; + private static final float SINE_30_DEGREES = 0.5f; + + private static final int DEGREES_FOR_ONE_HOUR = 30; + private static final int DEGREES_FOR_ONE_MINUTE = 6; + + private static final int[] HOURS_NUMBERS = {12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; + private static final int[] HOURS_NUMBERS_24 = {0, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] MINUTES_NUMBERS = {0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55}; + + private static final int CENTER_RADIUS = 2; + + private static int[] sSnapPrefer30sMap = new int[361]; + + private final String[] mHours12Texts = new String[12]; + private final String[] mOuterHours24Texts = new String[12]; + private final String[] mInnerHours24Texts = new String[12]; + private final String[] mMinutesTexts = new String[12]; + + private final String[] mAmPmText = new String[2]; + + private final Paint[] mPaint = new Paint[2]; + private final Paint mPaintCenter = new Paint(); + private final Paint[][] mPaintSelector = new Paint[2][3]; + private final Paint mPaintAmPmText = new Paint(); + private final Paint[] mPaintAmPmCircle = new Paint[2]; + + private final Paint mPaintBackground = new Paint(); + private final Paint mPaintDisabled = new Paint(); + private final Paint mPaintDebug = new Paint(); + + private Typeface mTypeface; + + private boolean mIs24HourMode; + private boolean mShowHours; + private boolean mIsOnInnerCircle; + + private int mXCenter; + private int mYCenter; + + private float[] mCircleRadius = new float[3]; + + private int mMinHypotenuseForInnerNumber; + private int mMaxHypotenuseForOuterNumber; + private int mHalfwayHypotenusePoint; + + private float[] mTextSize = new float[2]; + private float mInnerTextSize; + + private float[][] mTextGridHeights = new float[2][7]; + private float[][] mTextGridWidths = new float[2][7]; + + private float[] mInnerTextGridHeights = new float[7]; + private float[] mInnerTextGridWidths = new float[7]; + + private String[] mOuterTextHours; + private String[] mInnerTextHours; + private String[] mOuterTextMinutes; + + private float[] mCircleRadiusMultiplier = new float[2]; + private float[] mNumbersRadiusMultiplier = new float[3]; + + private float[] mTextSizeMultiplier = new float[3]; + + private float[] mAnimationRadiusMultiplier = new float[3]; + + private float mTransitionMidRadiusMultiplier; + private float mTransitionEndRadiusMultiplier; + + private AnimatorSet mTransition; + private InvalidateUpdateListener mInvalidateUpdateListener = new InvalidateUpdateListener(); + + private int[] mLineLength = new int[3]; + private int[] mSelectionRadius = new int[3]; + private float mSelectionRadiusMultiplier; + private int[] mSelectionDegrees = new int[3]; + + private int mAmPmCircleRadius; + private float mAmPmYCenter; + + private float mAmPmCircleRadiusMultiplier; + private int mAmPmTextColor; + + private float mLeftIndicatorXCenter; + private float mRightIndicatorXCenter; + + private int mAmPmUnselectedColor; + private int mAmPmSelectedColor; + + private int mAmOrPm; + private int mAmOrPmPressed; + + private RectF mRectF = new RectF(); + private boolean mInputEnabled = true; + private OnValueSelectedListener mListener; + + private final ArrayList<Animator> mHoursToMinutesAnims = new ArrayList<Animator>(); + private final ArrayList<Animator> mMinuteToHoursAnims = new ArrayList<Animator>(); + + public interface OnValueSelectedListener { + void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance); + } + + static { + // Prepare mapping to snap touchable degrees to selectable degrees. + preparePrefer30sMap(); + } + + /** + * Split up the 360 degrees of the circle among the 60 selectable values. Assigns a larger + * selectable area to each of the 12 visible values, such that the ratio of space apportioned + * to a visible value : space apportioned to a non-visible value will be 14 : 4. + * E.g. the output of 30 degrees should have a higher range of input associated with it than + * the output of 24 degrees, because 30 degrees corresponds to a visible number on the clock + * circle (5 on the minutes, 1 or 13 on the hours). + */ + private static void preparePrefer30sMap() { + // We'll split up the visible output and the non-visible output such that each visible + // output will correspond to a range of 14 associated input degrees, and each non-visible + // output will correspond to a range of 4 associate input degrees, so visible numbers + // are more than 3 times easier to get than non-visible numbers: + // {354-359,0-7}:0, {8-11}:6, {12-15}:12, {16-19}:18, {20-23}:24, {24-37}:30, etc. + // + // If an output of 30 degrees should correspond to a range of 14 associated degrees, then + // we'll need any input between 24 - 37 to snap to 30. Working out from there, 20-23 should + // snap to 24, while 38-41 should snap to 36. This is somewhat counter-intuitive, that you + // can be touching 36 degrees but have the selection snapped to 30 degrees; however, this + // inconsistency isn't noticeable at such fine-grained degrees, and it affords us the + // ability to aggressively prefer the visible values by a factor of more than 3:1, which + // greatly contributes to the selectability of these values. + + // The first output is 0, and each following output will increment by 6 {0, 6, 12, ...}. + int snappedOutputDegrees = 0; + // Count of how many inputs we've designated to the specified output. + int count = 1; + // How many input we expect for a specified output. This will be 14 for output divisible + // by 30, and 4 for the remaining output. We'll special case the outputs of 0 and 360, so + // the caller can decide which they need. + int expectedCount = 8; + // Iterate through the input. + for (int degrees = 0; degrees < 361; degrees++) { + // Save the input-output mapping. + sSnapPrefer30sMap[degrees] = snappedOutputDegrees; + // If this is the last input for the specified output, calculate the next output and + // the next expected count. + if (count == expectedCount) { + snappedOutputDegrees += 6; + if (snappedOutputDegrees == 360) { + expectedCount = 7; + } else if (snappedOutputDegrees % 30 == 0) { + expectedCount = 14; + } else { + expectedCount = 4; + } + count = 1; + } else { + count++; + } + } + } + + /** + * Returns mapping of any input degrees (0 to 360) to one of 60 selectable output degrees, + * where the degrees corresponding to visible numbers (i.e. those divisible by 30) will be + * weighted heavier than the degrees corresponding to non-visible numbers. + * See {@link #preparePrefer30sMap()} documentation for the rationale and generation of the + * mapping. + */ + private static int snapPrefer30s(int degrees) { + if (sSnapPrefer30sMap == null) { + return -1; + } + return sSnapPrefer30sMap[degrees]; + } + + /** + * Returns mapping of any input degrees (0 to 360) to one of 12 visible output degrees (all + * multiples of 30), where the input will be "snapped" to the closest visible degrees. + * @param degrees The input degrees + * @param forceHigherOrLower The output may be forced to either the higher or lower step, or may + * be allowed to snap to whichever is closer. Use 1 to force strictly higher, -1 to force + * strictly lower, and 0 to snap to the closer one. + * @return output degrees, will be a multiple of 30 + */ + private static int snapOnly30s(int degrees, int forceHigherOrLower) { + final int stepSize = DEGREES_FOR_ONE_HOUR; + int floor = (degrees / stepSize) * stepSize; + final int ceiling = floor + stepSize; + if (forceHigherOrLower == 1) { + degrees = ceiling; + } else if (forceHigherOrLower == -1) { + if (degrees == floor) { + floor -= stepSize; + } + degrees = floor; + } else { + if ((degrees - floor) < (ceiling - degrees)) { + degrees = floor; + } else { + degrees = ceiling; + } + } + return degrees; + } + + public RadialTimePickerView(Context context, AttributeSet attrs) { + this(context, attrs, R.attr.timePickerStyle); + } + + public RadialTimePickerView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs); + + // process style attributes + final TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.TimePicker, + defStyle, 0); + + final Resources res = getResources(); + + mAmPmUnselectedColor = a.getColor(R.styleable.TimePicker_amPmUnselectedBackgroundColor, + res.getColor( + R.color.timepicker_default_ampm_unselected_background_color_holo_light)); + + mAmPmSelectedColor = a.getColor(R.styleable.TimePicker_amPmSelectedBackgroundColor, + res.getColor(R.color.timepicker_default_ampm_selected_background_color_holo_light)); + + mAmPmTextColor = a.getColor(R.styleable.TimePicker_amPmTextColor, + res.getColor(R.color.timepicker_default_text_color_holo_light)); + + final int numbersTextColor = a.getColor(R.styleable.TimePicker_numbersTextColor, + res.getColor(R.color.timepicker_default_text_color_holo_light)); + + mTypeface = Typeface.create("sans-serif", Typeface.NORMAL); + + mPaint[HOURS] = new Paint(); + mPaint[HOURS].setColor(numbersTextColor); + mPaint[HOURS].setAntiAlias(true); + mPaint[HOURS].setTextAlign(Paint.Align.CENTER); + + mPaint[MINUTES] = new Paint(); + mPaint[MINUTES].setColor(numbersTextColor); + mPaint[MINUTES].setAntiAlias(true); + mPaint[MINUTES].setTextAlign(Paint.Align.CENTER); + + mPaintCenter.setColor(numbersTextColor); + mPaintCenter.setAntiAlias(true); + mPaintCenter.setTextAlign(Paint.Align.CENTER); + + mPaintSelector[HOURS][SELECTOR_CIRCLE] = new Paint(); + mPaintSelector[HOURS][SELECTOR_CIRCLE].setColor( + a.getColor(R.styleable.TimePicker_numbersSelectorColor, R.color.holo_blue_light)); + mPaintSelector[HOURS][SELECTOR_CIRCLE].setAntiAlias(true); + + mPaintSelector[HOURS][SELECTOR_DOT] = new Paint(); + mPaintSelector[HOURS][SELECTOR_DOT].setColor( + a.getColor(R.styleable.TimePicker_numbersSelectorColor, R.color.holo_blue_light)); + mPaintSelector[HOURS][SELECTOR_DOT].setAntiAlias(true); + + mPaintSelector[HOURS][SELECTOR_LINE] = new Paint(); + mPaintSelector[HOURS][SELECTOR_LINE].setColor( + a.getColor(R.styleable.TimePicker_numbersSelectorColor, R.color.holo_blue_light)); + mPaintSelector[HOURS][SELECTOR_LINE].setAntiAlias(true); + mPaintSelector[HOURS][SELECTOR_LINE].setStrokeWidth(2); + + mPaintSelector[MINUTES][SELECTOR_CIRCLE] = new Paint(); + mPaintSelector[MINUTES][SELECTOR_CIRCLE].setColor( + a.getColor(R.styleable.TimePicker_numbersSelectorColor, R.color.holo_blue_light)); + mPaintSelector[MINUTES][SELECTOR_CIRCLE].setAntiAlias(true); + + mPaintSelector[MINUTES][SELECTOR_DOT] = new Paint(); + mPaintSelector[MINUTES][SELECTOR_DOT].setColor( + a.getColor(R.styleable.TimePicker_numbersSelectorColor, R.color.holo_blue_light)); + mPaintSelector[MINUTES][SELECTOR_DOT].setAntiAlias(true); + + mPaintSelector[MINUTES][SELECTOR_LINE] = new Paint(); + mPaintSelector[MINUTES][SELECTOR_LINE].setColor( + a.getColor(R.styleable.TimePicker_numbersSelectorColor, R.color.holo_blue_light)); + mPaintSelector[MINUTES][SELECTOR_LINE].setAntiAlias(true); + mPaintSelector[MINUTES][SELECTOR_LINE].setStrokeWidth(2); + + mPaintAmPmText.setColor(mAmPmTextColor); + mPaintAmPmText.setTypeface(mTypeface); + mPaintAmPmText.setAntiAlias(true); + mPaintAmPmText.setTextAlign(Paint.Align.CENTER); + + mPaintAmPmCircle[AM] = new Paint(); + mPaintAmPmCircle[AM].setAntiAlias(true); + mPaintAmPmCircle[PM] = new Paint(); + mPaintAmPmCircle[PM].setAntiAlias(true); + + mPaintBackground.setColor( + a.getColor(R.styleable.TimePicker_numbersBackgroundColor, Color.WHITE)); + mPaintBackground.setAntiAlias(true); + + final int disabledColor = a.getColor(R.styleable.TimePicker_disabledColor, + res.getColor(R.color.timepicker_default_disabled_color_holo_light)); + mPaintDisabled.setColor(disabledColor); + mPaintDisabled.setAntiAlias(true); + + if (DEBUG) { + mPaintDebug.setColor(DEBUG_COLOR); + mPaintDebug.setAntiAlias(true); + mPaintDebug.setStrokeWidth(DEBUG_STROKE_WIDTH); + mPaintDebug.setStyle(Paint.Style.STROKE); + mPaintDebug.setTextAlign(Paint.Align.CENTER); + } + + mShowHours = true; + mIs24HourMode = false; + mAmOrPm = AM; + mAmOrPmPressed = -1; + + initHoursAndMinutesText(); + initData(); + + mTransitionMidRadiusMultiplier = Float.parseFloat( + res.getString(R.string.timepicker_transition_mid_radius_multiplier)); + mTransitionEndRadiusMultiplier = Float.parseFloat( + res.getString(R.string.timepicker_transition_end_radius_multiplier)); + + mTextGridHeights[HOURS] = new float[7]; + mTextGridHeights[MINUTES] = new float[7]; + + mSelectionRadiusMultiplier = Float.parseFloat( + res.getString(R.string.timepicker_selection_radius_multiplier)); + + setOnTouchListener(this); + + // Initial values + final Calendar calendar = Calendar.getInstance(Locale.getDefault()); + final int currentHour = calendar.get(Calendar.HOUR_OF_DAY); + final int currentMinute = calendar.get(Calendar.MINUTE); + + setCurrentHour(currentHour); + setCurrentMinute(currentMinute); + + setHapticFeedbackEnabled(true); + } + + /** + * Measure the view to end up as a square, based on the minimum of the height and width. + */ + @Override + public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int measuredWidth = MeasureSpec.getSize(widthMeasureSpec); + int widthMode = MeasureSpec.getMode(widthMeasureSpec); + int measuredHeight = MeasureSpec.getSize(heightMeasureSpec); + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + int minDimension = Math.min(measuredWidth, measuredHeight); + + super.onMeasure(MeasureSpec.makeMeasureSpec(minDimension, widthMode), + MeasureSpec.makeMeasureSpec(minDimension, heightMode)); + } + + public void initialize(int hour, int minute, boolean is24HourMode) { + mIs24HourMode = is24HourMode; + setCurrentHour(hour); + setCurrentMinute(minute); + } + + public void setCurrentItemShowing(int item, boolean animate) { + switch (item){ + case HOURS: + showHours(animate); + break; + case MINUTES: + showMinutes(animate); + break; + default: + Log.e(TAG, "ClockView does not support showing item " + item); + } + } + + public int getCurrentItemShowing() { + return mShowHours ? HOURS : MINUTES; + } + + public void setOnValueSelectedListener(OnValueSelectedListener listener) { + mListener = listener; + } + + public void setCurrentHour(int hour) { + final int degrees = (hour % 12) * DEGREES_FOR_ONE_HOUR; + mSelectionDegrees[HOURS] = degrees; + mSelectionDegrees[HOURS_INNER] = degrees; + mAmOrPm = ((hour % 24) < 12) ? AM : PM; + if (mIs24HourMode) { + mIsOnInnerCircle = (mAmOrPm == AM); + } else { + mIsOnInnerCircle = false; + } + initData(); + updateLayoutData(); + invalidate(); + } + + // Return hours in 0-23 range + public int getCurrentHour() { + int hours = + mSelectionDegrees[mIsOnInnerCircle ? HOURS_INNER : HOURS] / DEGREES_FOR_ONE_HOUR; + if (mIs24HourMode) { + if (mIsOnInnerCircle) { + hours = hours % 12; + } else { + if (hours != 0) { + hours += 12; + } + } + } else { + hours = hours % 12; + if (hours == 0) { + if (mAmOrPm == PM) { + hours = 12; + } + } else { + if (mAmOrPm == PM) { + hours += 12; + } + } + } + return hours; + } + + public void setCurrentMinute(int minute) { + mSelectionDegrees[MINUTES] = (minute % 60) * DEGREES_FOR_ONE_MINUTE; + invalidate(); + } + + // Returns minutes in 0-59 range + public int getCurrentMinute() { + return (mSelectionDegrees[MINUTES] / DEGREES_FOR_ONE_MINUTE); + } + + public void setAmOrPm(int val) { + mAmOrPm = (val % 2); + invalidate(); + } + + public int getAmOrPm() { + return mAmOrPm; + } + + public void swapAmPm() { + mAmOrPm = (mAmOrPm == AM) ? PM : AM; + invalidate(); + } + + public void showHours(boolean animate) { + if (mShowHours) return; + mShowHours = true; + if (animate) { + startMinutesToHoursAnimation(); + } + initData(); + updateLayoutData(); + invalidate(); + } + + public void showMinutes(boolean animate) { + if (!mShowHours) return; + mShowHours = false; + if (animate) { + startHoursToMinutesAnimation(); + } + initData(); + updateLayoutData(); + invalidate(); + } + + private void initHoursAndMinutesText() { + // Initialize the hours and minutes numbers. + for (int i = 0; i < 12; i++) { + mHours12Texts[i] = String.format("%d", HOURS_NUMBERS[i]); + mOuterHours24Texts[i] = String.format("%02d", HOURS_NUMBERS_24[i]); + mInnerHours24Texts[i] = String.format("%d", HOURS_NUMBERS[i]); + mMinutesTexts[i] = String.format("%02d", MINUTES_NUMBERS[i]); + } + + String[] amPmTexts = new DateFormatSymbols().getAmPmStrings(); + mAmPmText[AM] = amPmTexts[0]; + mAmPmText[PM] = amPmTexts[1]; + } + + private void initData() { + if (mIs24HourMode) { + mOuterTextHours = mOuterHours24Texts; + mInnerTextHours = mInnerHours24Texts; + } else { + mOuterTextHours = mHours12Texts; + mInnerTextHours = null; + } + + mOuterTextMinutes = mMinutesTexts; + + final Resources res = getResources(); + + if (mShowHours) { + if (mIs24HourMode) { + mCircleRadiusMultiplier[HOURS] = Float.parseFloat( + res.getString(R.string.timepicker_circle_radius_multiplier_24HourMode)); + mNumbersRadiusMultiplier[HOURS] = Float.parseFloat( + res.getString(R.string.timepicker_numbers_radius_multiplier_outer)); + mTextSizeMultiplier[HOURS] = Float.parseFloat( + res.getString(R.string.timepicker_text_size_multiplier_outer)); + + mNumbersRadiusMultiplier[HOURS_INNER] = Float.parseFloat( + res.getString(R.string.timepicker_numbers_radius_multiplier_inner)); + mTextSizeMultiplier[HOURS_INNER] = Float.parseFloat( + res.getString(R.string.timepicker_text_size_multiplier_inner)); + } else { + mCircleRadiusMultiplier[HOURS] = Float.parseFloat( + res.getString(R.string.timepicker_circle_radius_multiplier)); + mNumbersRadiusMultiplier[HOURS] = Float.parseFloat( + res.getString(R.string.timepicker_numbers_radius_multiplier_normal)); + mTextSizeMultiplier[HOURS] = Float.parseFloat( + res.getString(R.string.timepicker_text_size_multiplier_normal)); + } + } else { + mCircleRadiusMultiplier[MINUTES] = Float.parseFloat( + res.getString(R.string.timepicker_circle_radius_multiplier)); + mNumbersRadiusMultiplier[MINUTES] = Float.parseFloat( + res.getString(R.string.timepicker_numbers_radius_multiplier_normal)); + mTextSizeMultiplier[MINUTES] = Float.parseFloat( + res.getString(R.string.timepicker_text_size_multiplier_normal)); + } + + mAnimationRadiusMultiplier[HOURS] = 1; + mAnimationRadiusMultiplier[HOURS_INNER] = 1; + mAnimationRadiusMultiplier[MINUTES] = 1; + + mAmPmCircleRadiusMultiplier = Float.parseFloat( + res.getString(R.string.timepicker_ampm_circle_radius_multiplier)); + + mPaint[HOURS].setAlpha(mShowHours ? ALPHA_OPAQUE : ALPHA_TRANSPARENT); + mPaint[MINUTES].setAlpha(mShowHours ? ALPHA_TRANSPARENT : ALPHA_OPAQUE); + + mPaintSelector[HOURS][SELECTOR_CIRCLE].setAlpha( + mShowHours ?ALPHA_SELECTOR : ALPHA_TRANSPARENT); + mPaintSelector[HOURS][SELECTOR_DOT].setAlpha( + mShowHours ? ALPHA_OPAQUE : ALPHA_TRANSPARENT); + mPaintSelector[HOURS][SELECTOR_LINE].setAlpha( + mShowHours ? ALPHA_SELECTOR : ALPHA_TRANSPARENT); + + mPaintSelector[MINUTES][SELECTOR_CIRCLE].setAlpha( + mShowHours ? ALPHA_TRANSPARENT : ALPHA_SELECTOR); + mPaintSelector[MINUTES][SELECTOR_DOT].setAlpha( + mShowHours ? ALPHA_TRANSPARENT : ALPHA_OPAQUE); + mPaintSelector[MINUTES][SELECTOR_LINE].setAlpha( + mShowHours ? ALPHA_TRANSPARENT : ALPHA_SELECTOR); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + updateLayoutData(); + } + + private void updateLayoutData() { + mXCenter = getWidth() / 2; + mYCenter = getHeight() / 2; + + final int min = Math.min(mXCenter, mYCenter); + + mCircleRadius[HOURS] = min * mCircleRadiusMultiplier[HOURS]; + mCircleRadius[HOURS_INNER] = min * mCircleRadiusMultiplier[HOURS]; + mCircleRadius[MINUTES] = min * mCircleRadiusMultiplier[MINUTES]; + + if (!mIs24HourMode) { + // We'll need to draw the AM/PM circles, so the main circle will need to have + // a slightly higher center. To keep the entire view centered vertically, we'll + // have to push it up by half the radius of the AM/PM circles. + int amPmCircleRadius = (int) (mCircleRadius[HOURS] * mAmPmCircleRadiusMultiplier); + mYCenter -= amPmCircleRadius / 2; + } + + mMinHypotenuseForInnerNumber = (int) (mCircleRadius[HOURS] + * mNumbersRadiusMultiplier[HOURS_INNER]) - mSelectionRadius[HOURS]; + mMaxHypotenuseForOuterNumber = (int) (mCircleRadius[HOURS] + * mNumbersRadiusMultiplier[HOURS]) + mSelectionRadius[HOURS]; + mHalfwayHypotenusePoint = (int) (mCircleRadius[HOURS] + * ((mNumbersRadiusMultiplier[HOURS] + mNumbersRadiusMultiplier[HOURS_INNER]) / 2)); + + mTextSize[HOURS] = mCircleRadius[HOURS] * mTextSizeMultiplier[HOURS]; + mTextSize[MINUTES] = mCircleRadius[MINUTES] * mTextSizeMultiplier[MINUTES]; + + if (mIs24HourMode) { + mInnerTextSize = mCircleRadius[HOURS] * mTextSizeMultiplier[HOURS_INNER]; + } + + calculateGridSizesHours(); + calculateGridSizesMinutes(); + + mSelectionRadius[HOURS] = (int) (mCircleRadius[HOURS] * mSelectionRadiusMultiplier); + mSelectionRadius[HOURS_INNER] = mSelectionRadius[HOURS]; + mSelectionRadius[MINUTES] = (int) (mCircleRadius[MINUTES] * mSelectionRadiusMultiplier); + + mAmPmCircleRadius = (int) (mCircleRadius[HOURS] * mAmPmCircleRadiusMultiplier); + mPaintAmPmText.setTextSize(mAmPmCircleRadius * 3 / 4); + + // Line up the vertical center of the AM/PM circles with the bottom of the main circle. + mAmPmYCenter = mYCenter + mCircleRadius[HOURS]; + + // Line up the horizontal edges of the AM/PM circles with the horizontal edges + // of the main circle + mLeftIndicatorXCenter = mXCenter - mCircleRadius[HOURS] + mAmPmCircleRadius; + mRightIndicatorXCenter = mXCenter + mCircleRadius[HOURS] - mAmPmCircleRadius; + } + + @Override + public void onDraw(Canvas canvas) { + canvas.save(); + + calculateGridSizesHours(); + calculateGridSizesMinutes(); + + drawCircleBackground(canvas); + + drawTextElements(canvas, mTextSize[HOURS], mTypeface, mOuterTextHours, + mTextGridWidths[HOURS], mTextGridHeights[HOURS], mPaint[HOURS]); + + if (mIs24HourMode && mInnerTextHours != null) { + drawTextElements(canvas, mInnerTextSize, mTypeface, mInnerTextHours, + mInnerTextGridWidths, mInnerTextGridHeights, mPaint[HOURS]); + } + + drawTextElements(canvas, mTextSize[MINUTES], mTypeface, mOuterTextMinutes, + mTextGridWidths[MINUTES], mTextGridHeights[MINUTES], mPaint[MINUTES]); + + drawCenter(canvas); + drawSelector(canvas); + if (!mIs24HourMode) { + drawAmPm(canvas); + } + + if(!mInputEnabled) { + // Draw outer view rectangle + mRectF.set(0, 0, getWidth(), getHeight()); + canvas.drawRect(mRectF, mPaintDisabled); + } + + if (DEBUG) { + drawDebug(canvas); + } + + canvas.restore(); + } + + private void drawCircleBackground(Canvas canvas) { + canvas.drawCircle(mXCenter, mYCenter, mCircleRadius[HOURS], mPaintBackground); + } + + private void drawCenter(Canvas canvas) { + canvas.drawCircle(mXCenter, mYCenter, CENTER_RADIUS, mPaintCenter); + } + + private void drawSelector(Canvas canvas) { + drawSelector(canvas, mIsOnInnerCircle ? HOURS_INNER : HOURS); + drawSelector(canvas, MINUTES); + } + + private void drawAmPm(Canvas canvas) { + final boolean isLayoutRtl = isLayoutRtl(); + + int amColor = mAmPmUnselectedColor; + int amAlpha = ALPHA_OPAQUE; + int pmColor = mAmPmUnselectedColor; + int pmAlpha = ALPHA_OPAQUE; + if (mAmOrPm == AM) { + amColor = mAmPmSelectedColor; + amAlpha = ALPHA_AMPM_SELECTED; + } else if (mAmOrPm == PM) { + pmColor = mAmPmSelectedColor; + pmAlpha = ALPHA_AMPM_SELECTED; + } + if (mAmOrPmPressed == AM) { + amColor = mAmPmSelectedColor; + amAlpha = ALPHA_AMPM_PRESSED; + } else if (mAmOrPmPressed == PM) { + pmColor = mAmPmSelectedColor; + pmAlpha = ALPHA_AMPM_PRESSED; + } + + // Draw the two circles + mPaintAmPmCircle[AM].setColor(amColor); + mPaintAmPmCircle[AM].setAlpha(amAlpha); + canvas.drawCircle(isLayoutRtl ? mRightIndicatorXCenter : mLeftIndicatorXCenter, + mAmPmYCenter, mAmPmCircleRadius, mPaintAmPmCircle[AM]); + + mPaintAmPmCircle[PM].setColor(pmColor); + mPaintAmPmCircle[PM].setAlpha(pmAlpha); + canvas.drawCircle(isLayoutRtl ? mLeftIndicatorXCenter : mRightIndicatorXCenter, + mAmPmYCenter, mAmPmCircleRadius, mPaintAmPmCircle[PM]); + + // Draw the AM/PM texts on top + mPaintAmPmText.setColor(mAmPmTextColor); + float textYCenter = mAmPmYCenter - + (int) (mPaintAmPmText.descent() + mPaintAmPmText.ascent()) / 2; + + canvas.drawText(isLayoutRtl ? mAmPmText[PM] : mAmPmText[AM], mLeftIndicatorXCenter, + textYCenter, mPaintAmPmText); + canvas.drawText(isLayoutRtl ? mAmPmText[AM] : mAmPmText[PM], mRightIndicatorXCenter, + textYCenter, mPaintAmPmText); + } + + private void drawSelector(Canvas canvas, int index) { + // Calculate the current radius at which to place the selection circle. + mLineLength[index] = (int) (mCircleRadius[index] + * mNumbersRadiusMultiplier[index] * mAnimationRadiusMultiplier[index]); + + double selectionRadians = Math.toRadians(mSelectionDegrees[index]); + + int pointX = mXCenter + (int) (mLineLength[index] * Math.sin(selectionRadians)); + int pointY = mYCenter - (int) (mLineLength[index] * Math.cos(selectionRadians)); + + // Draw the selection circle + canvas.drawCircle(pointX, pointY, mSelectionRadius[index], + mPaintSelector[index % 2][SELECTOR_CIRCLE]); + + // Draw the dot if needed + if (mSelectionDegrees[index] % 30 != 0) { + // We're not on a direct tick + canvas.drawCircle(pointX, pointY, (mSelectionRadius[index] * 2 / 7), + mPaintSelector[index % 2][SELECTOR_DOT]); + } else { + // We're not drawing the dot, so shorten the line to only go as far as the edge of the + // selection circle + int lineLength = mLineLength[index] - mSelectionRadius[index]; + pointX = mXCenter + (int) (lineLength * Math.sin(selectionRadians)); + pointY = mYCenter - (int) (lineLength * Math.cos(selectionRadians)); + } + + // Draw the line + canvas.drawLine(mXCenter, mYCenter, pointX, pointY, + mPaintSelector[index % 2][SELECTOR_LINE]); + } + + private void drawDebug(Canvas canvas) { + // Draw outer numbers circle + final float outerRadius = mCircleRadius[HOURS] * mNumbersRadiusMultiplier[HOURS]; + canvas.drawCircle(mXCenter, mYCenter, outerRadius, mPaintDebug); + + // Draw inner numbers circle + final float innerRadius = mCircleRadius[HOURS] * mNumbersRadiusMultiplier[HOURS_INNER]; + canvas.drawCircle(mXCenter, mYCenter, innerRadius, mPaintDebug); + + // Draw outer background circle + canvas.drawCircle(mXCenter, mYCenter, mCircleRadius[HOURS], mPaintDebug); + + // Draw outer rectangle for circles + float left = mXCenter - outerRadius; + float top = mYCenter - outerRadius; + float right = mXCenter + outerRadius; + float bottom = mYCenter + outerRadius; + mRectF = new RectF(left, top, right, bottom); + canvas.drawRect(mRectF, mPaintDebug); + + // Draw outer rectangle for background + left = mXCenter - mCircleRadius[HOURS]; + top = mYCenter - mCircleRadius[HOURS]; + right = mXCenter + mCircleRadius[HOURS]; + bottom = mYCenter + mCircleRadius[HOURS]; + mRectF.set(left, top, right, bottom); + canvas.drawRect(mRectF, mPaintDebug); + + // Draw outer view rectangle + mRectF.set(0, 0, getWidth(), getHeight()); + canvas.drawRect(mRectF, mPaintDebug); + + // Draw selected time + final String selected = String.format("%02d:%02d", getCurrentHour(), getCurrentMinute()); + + ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + TextView tv = new TextView(getContext()); + tv.setLayoutParams(lp); + tv.setText(selected); + tv.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); + Paint paint = tv.getPaint(); + paint.setColor(DEBUG_TEXT_COLOR); + + final int width = tv.getMeasuredWidth(); + + float height = paint.descent() - paint.ascent(); + float x = mXCenter - width / 2; + float y = mYCenter + 1.5f * height; + + canvas.drawText(selected.toString(), x, y, paint); + } + + private void calculateGridSizesHours() { + // Calculate the text positions + float numbersRadius = mCircleRadius[HOURS] + * mNumbersRadiusMultiplier[HOURS] * mAnimationRadiusMultiplier[HOURS]; + + // Calculate the positions for the 12 numbers in the main circle. + calculateGridSizes(mPaint[HOURS], numbersRadius, mXCenter, mYCenter, + mTextSize[HOURS], mTextGridHeights[HOURS], mTextGridWidths[HOURS]); + + // If we have an inner circle, calculate those positions too. + if (mIs24HourMode) { + float innerNumbersRadius = mCircleRadius[HOURS_INNER] + * mNumbersRadiusMultiplier[HOURS_INNER] + * mAnimationRadiusMultiplier[HOURS_INNER]; + + calculateGridSizes(mPaint[HOURS], innerNumbersRadius, mXCenter, mYCenter, + mInnerTextSize, mInnerTextGridHeights, mInnerTextGridWidths); + } + } + + private void calculateGridSizesMinutes() { + // Calculate the text positions + float numbersRadius = mCircleRadius[MINUTES] + * mNumbersRadiusMultiplier[MINUTES] * mAnimationRadiusMultiplier[MINUTES]; + + // Calculate the positions for the 12 numbers in the main circle. + calculateGridSizes(mPaint[MINUTES], numbersRadius, mXCenter, mYCenter, + mTextSize[MINUTES], mTextGridHeights[MINUTES], mTextGridWidths[MINUTES]); + } + + + /** + * Using the trigonometric Unit Circle, calculate the positions that the text will need to be + * drawn at based on the specified circle radius. Place the values in the textGridHeights and + * textGridWidths parameters. + */ + private static void calculateGridSizes(Paint paint, float numbersRadius, float xCenter, + float yCenter, float textSize, float[] textGridHeights, float[] textGridWidths) { + /* + * The numbers need to be drawn in a 7x7 grid, representing the points on the Unit Circle. + */ + final float offset1 = numbersRadius; + // cos(30) = a / r => r * cos(30) + final float offset2 = numbersRadius * COSINE_30_DEGREES; + // sin(30) = o / r => r * sin(30) + final float offset3 = numbersRadius * SINE_30_DEGREES; + + paint.setTextSize(textSize); + // We'll need yTextBase to be slightly lower to account for the text's baseline. + yCenter -= (paint.descent() + paint.ascent()) / 2; + + textGridHeights[0] = yCenter - offset1; + textGridWidths[0] = xCenter - offset1; + textGridHeights[1] = yCenter - offset2; + textGridWidths[1] = xCenter - offset2; + textGridHeights[2] = yCenter - offset3; + textGridWidths[2] = xCenter - offset3; + textGridHeights[3] = yCenter; + textGridWidths[3] = xCenter; + textGridHeights[4] = yCenter + offset3; + textGridWidths[4] = xCenter + offset3; + textGridHeights[5] = yCenter + offset2; + textGridWidths[5] = xCenter + offset2; + textGridHeights[6] = yCenter + offset1; + textGridWidths[6] = xCenter + offset1; + } + + /** + * Draw the 12 text values at the positions specified by the textGrid parameters. + */ + private void drawTextElements(Canvas canvas, float textSize, Typeface typeface, String[] texts, + float[] textGridWidths, float[] textGridHeights, Paint paint) { + paint.setTextSize(textSize); + paint.setTypeface(typeface); + canvas.drawText(texts[0], textGridWidths[3], textGridHeights[0], paint); + canvas.drawText(texts[1], textGridWidths[4], textGridHeights[1], paint); + canvas.drawText(texts[2], textGridWidths[5], textGridHeights[2], paint); + canvas.drawText(texts[3], textGridWidths[6], textGridHeights[3], paint); + canvas.drawText(texts[4], textGridWidths[5], textGridHeights[4], paint); + canvas.drawText(texts[5], textGridWidths[4], textGridHeights[5], paint); + canvas.drawText(texts[6], textGridWidths[3], textGridHeights[6], paint); + canvas.drawText(texts[7], textGridWidths[2], textGridHeights[5], paint); + canvas.drawText(texts[8], textGridWidths[1], textGridHeights[4], paint); + canvas.drawText(texts[9], textGridWidths[0], textGridHeights[3], paint); + canvas.drawText(texts[10], textGridWidths[1], textGridHeights[2], paint); + canvas.drawText(texts[11], textGridWidths[2], textGridHeights[1], paint); + } + + // Used for animating the hours by changing their radius + private void setAnimationRadiusMultiplierHours(float animationRadiusMultiplier) { + mAnimationRadiusMultiplier[HOURS] = animationRadiusMultiplier; + mAnimationRadiusMultiplier[HOURS_INNER] = animationRadiusMultiplier; + } + + // Used for animating the minutes by changing their radius + private void setAnimationRadiusMultiplierMinutes(float animationRadiusMultiplier) { + mAnimationRadiusMultiplier[MINUTES] = animationRadiusMultiplier; + } + + private static ObjectAnimator getRadiusDisappearAnimator(Object target, + String radiusPropertyName, InvalidateUpdateListener updateListener, + float midRadiusMultiplier, float endRadiusMultiplier) { + Keyframe kf0, kf1, kf2; + float midwayPoint = 0.2f; + int duration = 500; + + kf0 = Keyframe.ofFloat(0f, 1); + kf1 = Keyframe.ofFloat(midwayPoint, midRadiusMultiplier); + kf2 = Keyframe.ofFloat(1f, endRadiusMultiplier); + PropertyValuesHolder radiusDisappear = PropertyValuesHolder.ofKeyframe( + radiusPropertyName, kf0, kf1, kf2); + + ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder( + target, radiusDisappear).setDuration(duration); + animator.addUpdateListener(updateListener); + return animator; + } + + private static ObjectAnimator getRadiusReappearAnimator(Object target, + String radiusPropertyName, InvalidateUpdateListener updateListener, + float midRadiusMultiplier, float endRadiusMultiplier) { + Keyframe kf0, kf1, kf2, kf3; + float midwayPoint = 0.2f; + int duration = 500; + + // Set up animator for reappearing. + float delayMultiplier = 0.25f; + float transitionDurationMultiplier = 1f; + float totalDurationMultiplier = transitionDurationMultiplier + delayMultiplier; + int totalDuration = (int) (duration * totalDurationMultiplier); + float delayPoint = (delayMultiplier * duration) / totalDuration; + midwayPoint = 1 - (midwayPoint * (1 - delayPoint)); + + kf0 = Keyframe.ofFloat(0f, endRadiusMultiplier); + kf1 = Keyframe.ofFloat(delayPoint, endRadiusMultiplier); + kf2 = Keyframe.ofFloat(midwayPoint, midRadiusMultiplier); + kf3 = Keyframe.ofFloat(1f, 1); + PropertyValuesHolder radiusReappear = PropertyValuesHolder.ofKeyframe( + radiusPropertyName, kf0, kf1, kf2, kf3); + + ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder( + target, radiusReappear).setDuration(totalDuration); + animator.addUpdateListener(updateListener); + return animator; + } + + private static ObjectAnimator getFadeOutAnimator(Object target, int startAlpha, int endAlpha, + InvalidateUpdateListener updateListener) { + int duration = 500; + ObjectAnimator animator = ObjectAnimator.ofInt(target, "alpha", startAlpha, endAlpha); + animator.setDuration(duration); + animator.addUpdateListener(updateListener); + + return animator; + } + + private static ObjectAnimator getFadeInAnimator(Object target, int startAlpha, int endAlpha, + InvalidateUpdateListener updateListener) { + Keyframe kf0, kf1, kf2; + int duration = 500; + + // Set up animator for reappearing. + float delayMultiplier = 0.25f; + float transitionDurationMultiplier = 1f; + float totalDurationMultiplier = transitionDurationMultiplier + delayMultiplier; + int totalDuration = (int) (duration * totalDurationMultiplier); + float delayPoint = (delayMultiplier * duration) / totalDuration; + + kf0 = Keyframe.ofInt(0f, startAlpha); + kf1 = Keyframe.ofInt(delayPoint, startAlpha); + kf2 = Keyframe.ofInt(1f, endAlpha); + PropertyValuesHolder fadeIn = PropertyValuesHolder.ofKeyframe("alpha", kf0, kf1, kf2); + + ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder( + target, fadeIn).setDuration(totalDuration); + animator.addUpdateListener(updateListener); + return animator; + } + + private class InvalidateUpdateListener implements ValueAnimator.AnimatorUpdateListener { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + RadialTimePickerView.this.invalidate(); + } + } + + private void startHoursToMinutesAnimation() { + if (mHoursToMinutesAnims.size() == 0) { + mHoursToMinutesAnims.add(getRadiusDisappearAnimator(this, + "animationRadiusMultiplierHours", mInvalidateUpdateListener, + mTransitionMidRadiusMultiplier, mTransitionEndRadiusMultiplier)); + mHoursToMinutesAnims.add(getFadeOutAnimator(mPaint[HOURS], + ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener)); + mHoursToMinutesAnims.add(getFadeOutAnimator(mPaintSelector[HOURS][SELECTOR_CIRCLE], + ALPHA_SELECTOR, ALPHA_TRANSPARENT, mInvalidateUpdateListener)); + mHoursToMinutesAnims.add(getFadeOutAnimator(mPaintSelector[HOURS][SELECTOR_DOT], + ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener)); + mHoursToMinutesAnims.add(getFadeOutAnimator(mPaintSelector[HOURS][SELECTOR_LINE], + ALPHA_SELECTOR, ALPHA_TRANSPARENT, mInvalidateUpdateListener)); + + mHoursToMinutesAnims.add(getRadiusReappearAnimator(this, + "animationRadiusMultiplierMinutes", mInvalidateUpdateListener, + mTransitionMidRadiusMultiplier, mTransitionEndRadiusMultiplier)); + mHoursToMinutesAnims.add(getFadeInAnimator(mPaint[MINUTES], + ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener)); + mHoursToMinutesAnims.add(getFadeInAnimator(mPaintSelector[MINUTES][SELECTOR_CIRCLE], + ALPHA_TRANSPARENT, ALPHA_SELECTOR, mInvalidateUpdateListener)); + mHoursToMinutesAnims.add(getFadeInAnimator(mPaintSelector[MINUTES][SELECTOR_DOT], + ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener)); + mHoursToMinutesAnims.add(getFadeInAnimator(mPaintSelector[MINUTES][SELECTOR_LINE], + ALPHA_TRANSPARENT, ALPHA_SELECTOR, mInvalidateUpdateListener)); + } + + if (mTransition != null && mTransition.isRunning()) { + mTransition.end(); + } + mTransition = new AnimatorSet(); + mTransition.playTogether(mHoursToMinutesAnims); + mTransition.start(); + } + + private void startMinutesToHoursAnimation() { + if (mMinuteToHoursAnims.size() == 0) { + mMinuteToHoursAnims.add(getRadiusDisappearAnimator(this, + "animationRadiusMultiplierMinutes", mInvalidateUpdateListener, + mTransitionMidRadiusMultiplier, mTransitionEndRadiusMultiplier)); + mMinuteToHoursAnims.add(getFadeOutAnimator(mPaint[MINUTES], + ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener)); + mMinuteToHoursAnims.add(getFadeOutAnimator(mPaintSelector[MINUTES][SELECTOR_CIRCLE], + ALPHA_SELECTOR, ALPHA_TRANSPARENT, mInvalidateUpdateListener)); + mMinuteToHoursAnims.add(getFadeOutAnimator(mPaintSelector[MINUTES][SELECTOR_DOT], + ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener)); + mMinuteToHoursAnims.add(getFadeOutAnimator(mPaintSelector[MINUTES][SELECTOR_LINE], + ALPHA_SELECTOR, ALPHA_TRANSPARENT, mInvalidateUpdateListener)); + + mMinuteToHoursAnims.add(getRadiusReappearAnimator(this, + "animationRadiusMultiplierHours", mInvalidateUpdateListener, + mTransitionMidRadiusMultiplier, mTransitionEndRadiusMultiplier)); + mMinuteToHoursAnims.add(getFadeInAnimator(mPaint[HOURS], + ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener)); + mMinuteToHoursAnims.add(getFadeInAnimator(mPaintSelector[HOURS][SELECTOR_CIRCLE], + ALPHA_TRANSPARENT, ALPHA_SELECTOR, mInvalidateUpdateListener)); + mMinuteToHoursAnims.add(getFadeInAnimator(mPaintSelector[HOURS][SELECTOR_DOT], + ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener)); + mMinuteToHoursAnims.add(getFadeInAnimator(mPaintSelector[HOURS][SELECTOR_LINE], + ALPHA_TRANSPARENT, ALPHA_SELECTOR, mInvalidateUpdateListener)); + } + + if (mTransition != null && mTransition.isRunning()) { + mTransition.end(); + } + mTransition = new AnimatorSet(); + mTransition.playTogether(mMinuteToHoursAnims); + mTransition.start(); + } + + private int getDegreesFromXY(float x, float y) { + final double hypotenuse = Math.sqrt( + (y - mYCenter) * (y - mYCenter) + (x - mXCenter) * (x - mXCenter)); + + // Basic check if we're outside the range of the disk + if (hypotenuse > mCircleRadius[HOURS]) { + return -1; + } + // Check + if (mIs24HourMode && mShowHours) { + if (hypotenuse >= mMinHypotenuseForInnerNumber + && hypotenuse <= mHalfwayHypotenusePoint) { + mIsOnInnerCircle = true; + } else if (hypotenuse <= mMaxHypotenuseForOuterNumber + && hypotenuse >= mHalfwayHypotenusePoint) { + mIsOnInnerCircle = false; + } else { + return -1; + } + } else { + final int index = (mShowHours) ? HOURS : MINUTES; + final float length = (mCircleRadius[index] * mNumbersRadiusMultiplier[index]); + final int distanceToNumber = (int) Math.abs(hypotenuse - length); + final int maxAllowedDistance = + (int) (mCircleRadius[index] * (1 - mNumbersRadiusMultiplier[index])); + if (distanceToNumber > maxAllowedDistance) { + return -1; + } + } + + final float opposite = Math.abs(y - mYCenter); + double degrees = Math.toDegrees(Math.asin(opposite / hypotenuse)); + + // Now we have to translate to the correct quadrant. + boolean rightSide = (x > mXCenter); + boolean topSide = (y < mYCenter); + if (rightSide && topSide) { + degrees = 90 - degrees; + } else if (rightSide && !topSide) { + degrees = 90 + degrees; + } else if (!rightSide && !topSide) { + degrees = 270 - degrees; + } else if (!rightSide && topSide) { + degrees = 270 + degrees; + } + return (int) degrees; + } + + private int getIsTouchingAmOrPm(float x, float y) { + final boolean isLayoutRtl = isLayoutRtl(); + int squaredYDistance = (int) ((y - mAmPmYCenter) * (y - mAmPmYCenter)); + + int distanceToAmCenter = (int) Math.sqrt( + (x - mLeftIndicatorXCenter) * (x - mLeftIndicatorXCenter) + squaredYDistance); + if (distanceToAmCenter <= mAmPmCircleRadius) { + return (isLayoutRtl ? PM : AM); + } + + int distanceToPmCenter = (int) Math.sqrt( + (x - mRightIndicatorXCenter) * (x - mRightIndicatorXCenter) + squaredYDistance); + if (distanceToPmCenter <= mAmPmCircleRadius) { + return (isLayoutRtl ? AM : PM); + } + + // Neither was close enough. + return -1; + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + if(!mInputEnabled) { + return true; + } + + final float eventX = event.getX(); + final float eventY = event.getY(); + + int degrees; + int snapDegrees; + boolean result = false; + + switch(event.getAction()) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_MOVE: + mAmOrPmPressed = getIsTouchingAmOrPm(eventX, eventY); + if (mAmOrPmPressed != -1) { + result = true; + } else { + degrees = getDegreesFromXY(eventX, eventY); + if (degrees != -1) { + snapDegrees = (mShowHours ? + snapOnly30s(degrees, 0) : snapPrefer30s(degrees)) % 360; + if (mShowHours) { + mSelectionDegrees[HOURS] = snapDegrees; + mSelectionDegrees[HOURS_INNER] = snapDegrees; + } else { + mSelectionDegrees[MINUTES] = snapDegrees; + } + performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK); + if (mListener != null) { + if (mShowHours) { + mListener.onValueSelected(HOURS, getCurrentHour(), false); + } else { + mListener.onValueSelected(MINUTES, getCurrentMinute(), false); + } + } + result = true; + } + } + invalidate(); + return result; + + case MotionEvent.ACTION_UP: + mAmOrPmPressed = getIsTouchingAmOrPm(eventX, eventY); + if (mAmOrPmPressed != -1) { + if (mAmOrPm != mAmOrPmPressed) { + swapAmPm(); + } + mAmOrPmPressed = -1; + if (mListener != null) { + mListener.onValueSelected(AMPM, getCurrentHour(), true); + } + result = true; + } else { + degrees = getDegreesFromXY(eventX, eventY); + if (degrees != -1) { + snapDegrees = (mShowHours ? + snapOnly30s(degrees, 0) : snapPrefer30s(degrees)) % 360; + if (mShowHours) { + mSelectionDegrees[HOURS] = snapDegrees; + mSelectionDegrees[HOURS_INNER] = snapDegrees; + } else { + mSelectionDegrees[MINUTES] = snapDegrees; + } + if (mListener != null) { + if (mShowHours) { + mListener.onValueSelected(HOURS, getCurrentHour(), true); + } else { + mListener.onValueSelected(MINUTES, getCurrentMinute(), true); + } + } + result = true; + } + } + if (result) { + invalidate(); + } + return result; + + default: + break; + } + return false; + } + + /** + * Necessary for accessibility, to ensure we support "scrolling" forward and backward + * in the circle. + */ + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); + } + + /** + * Announce the currently-selected time when launched. + */ + @Override + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { + // Clear the event's current text so that only the current time will be spoken. + event.getText().clear(); + Time time = new Time(); + time.hour = getCurrentHour(); + time.minute = getCurrentMinute(); + long millis = time.normalize(true); + int flags = DateUtils.FORMAT_SHOW_TIME; + if (mIs24HourMode) { + flags |= DateUtils.FORMAT_24HOUR; + } + String timeString = DateUtils.formatDateTime(getContext(), millis, flags); + event.getText().add(timeString); + return true; + } + return super.dispatchPopulateAccessibilityEvent(event); + } + + /** + * When scroll forward/backward events are received, jump the time to the higher/lower + * discrete, visible value on the circle. + */ + @SuppressLint("NewApi") + @Override + public boolean performAccessibilityAction(int action, Bundle arguments) { + if (super.performAccessibilityAction(action, arguments)) { + return true; + } + + int changeMultiplier = 0; + if (action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD) { + changeMultiplier = 1; + } else if (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) { + changeMultiplier = -1; + } + if (changeMultiplier != 0) { + int value = 0; + int stepSize = 0; + if (mShowHours) { + stepSize = DEGREES_FOR_ONE_HOUR; + value = getCurrentHour() % 12; + } else { + stepSize = DEGREES_FOR_ONE_MINUTE; + value = getCurrentMinute(); + } + + int degrees = value * stepSize; + degrees = snapOnly30s(degrees, changeMultiplier); + value = degrees / stepSize; + int maxValue = 0; + int minValue = 0; + if (mShowHours) { + if (mIs24HourMode) { + maxValue = 23; + } else { + maxValue = 12; + minValue = 1; + } + } else { + maxValue = 55; + } + if (value > maxValue) { + // If we scrolled forward past the highest number, wrap around to the lowest. + value = minValue; + } else if (value < minValue) { + // If we scrolled backward past the lowest number, wrap around to the highest. + value = maxValue; + } + if (mShowHours) { + setCurrentHour(value); + if (mListener != null) { + mListener.onValueSelected(HOURS, value, false); + } + } else { + setCurrentMinute(value); + if (mListener != null) { + mListener.onValueSelected(MINUTES, value, false); + } + } + return true; + } + + return false; + } + + public void setInputEnabled(boolean inputEnabled) { + mInputEnabled = inputEnabled; + invalidate(); + } +} diff --git a/core/java/android/widget/RadioButton.java b/core/java/android/widget/RadioButton.java index a0fef7d..afc4830 100644 --- a/core/java/android/widget/RadioButton.java +++ b/core/java/android/widget/RadioButton.java @@ -21,8 +21,6 @@ import android.util.AttributeSet; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; -import com.android.internal.R; - /** * <p> @@ -59,8 +57,12 @@ public class RadioButton extends CompoundButton { this(context, attrs, com.android.internal.R.attr.radioButtonStyle); } - public RadioButton(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public RadioButton(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public RadioButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); } /** diff --git a/core/java/android/widget/RatingBar.java b/core/java/android/widget/RatingBar.java index 4d3c56c..82b490e 100644 --- a/core/java/android/widget/RatingBar.java +++ b/core/java/android/widget/RatingBar.java @@ -82,11 +82,15 @@ public class RatingBar extends AbsSeekBar { private OnRatingBarChangeListener mOnRatingBarChangeListener; - public RatingBar(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RatingBar, - defStyle, 0); + public RatingBar(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public RatingBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + final TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.RatingBar, defStyleAttr, defStyleRes); final int numStars = a.getInt(R.styleable.RatingBar_numStars, mNumStars); setIsIndicator(a.getBoolean(R.styleable.RatingBar_isIndicator, !mIsUserSeekable)); final float rating = a.getFloat(R.styleable.RatingBar_rating, -1); diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java index e03e83d..90e80d3 100644 --- a/core/java/android/widget/RelativeLayout.java +++ b/core/java/android/widget/RelativeLayout.java @@ -228,24 +228,27 @@ public class RelativeLayout extends ViewGroup { private static final int DEFAULT_WIDTH = 0x00010000; public RelativeLayout(Context context) { - super(context); - queryCompatibilityModes(context); + this(context, null); } public RelativeLayout(Context context, AttributeSet attrs) { - super(context, attrs); - initFromAttributes(context, attrs); - queryCompatibilityModes(context); + this(context, attrs, 0); + } + + public RelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); } - public RelativeLayout(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - initFromAttributes(context, attrs); + public RelativeLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + initFromAttributes(context, attrs, defStyleAttr, defStyleRes); queryCompatibilityModes(context); } - private void initFromAttributes(Context context, AttributeSet attrs) { - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RelativeLayout); + private void initFromAttributes( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + final TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.RelativeLayout, defStyleAttr, defStyleRes); mIgnoreGravity = a.getResourceId(R.styleable.RelativeLayout_ignoreGravity, View.NO_ID); mGravity = a.getInt(R.styleable.RelativeLayout_gravity, mGravity); a.recycle(); @@ -738,18 +741,29 @@ public class RelativeLayout extends ViewGroup { private int getChildMeasureSpec(int childStart, int childEnd, int childSize, int startMargin, int endMargin, int startPadding, int endPadding, int mySize) { + int childSpecMode = 0; + int childSpecSize = 0; + + // Negative values in a mySize/myWidth/myWidth value in RelativeLayout + // measurement is code for, "we got an unspecified mode in the + // RelativeLayout's measure spec." if (mySize < 0 && !mAllowBrokenMeasureSpecs) { - if (childSize >= 0) { - return MeasureSpec.makeMeasureSpec(childSize, MeasureSpec.EXACTLY); + if (childStart >= 0 && childEnd >= 0) { + // Constraints fixed both edges, so child has an exact size. + childSpecSize = Math.max(0, childEnd - childStart); + childSpecMode = MeasureSpec.EXACTLY; + } else if (childSize >= 0) { + // The child specified an exact size. + childSpecSize = childSize; + childSpecMode = MeasureSpec.EXACTLY; + } else { + // Allow the child to be whatever size it wants. + childSpecSize = 0; + childSpecMode = MeasureSpec.UNSPECIFIED; } - // Negative values in a mySize/myWidth/myWidth value in RelativeLayout measurement - // is code for, "we got an unspecified mode in the RelativeLayout's measurespec." - // Carry it forward. - return MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); - } - int childSpecMode = 0; - int childSpecSize = 0; + return MeasureSpec.makeMeasureSpec(childSpecSize, childSpecMode); + } // Figure out start and end bounds. int tempStart = childStart; diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java index 3ff0cee..bbe6f9e 100644 --- a/core/java/android/widget/RemoteViewsAdapter.java +++ b/core/java/android/widget/RemoteViewsAdapter.java @@ -32,7 +32,6 @@ import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; -import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.util.Log; @@ -45,7 +44,6 @@ import android.widget.RemoteViews.OnClickHandler; import com.android.internal.widget.IRemoteViewsAdapterConnection; import com.android.internal.widget.IRemoteViewsFactory; -import com.android.internal.widget.LockPatternUtils; /** * An adapter to a RemoteViewsService which fetches and caches RemoteViews diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index 6680393..082d728 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -162,12 +162,16 @@ public class ScrollView extends FrameLayout { this(context, attrs, com.android.internal.R.attr.scrollViewStyle); } - public ScrollView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public ScrollView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public ScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); initScrollView(); - TypedArray a = - context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ScrollView, defStyle, 0); + final TypedArray a = context.obtainStyledAttributes( + attrs, com.android.internal.R.styleable.ScrollView, defStyleAttr, defStyleRes); setFillViewport(a.getBoolean(R.styleable.ScrollView_fillViewport, false)); diff --git a/core/java/android/widget/Scroller.java b/core/java/android/widget/Scroller.java index 3bfd39d..1a0ce9c 100644 --- a/core/java/android/widget/Scroller.java +++ b/core/java/android/widget/Scroller.java @@ -61,6 +61,8 @@ import android.view.animation.Interpolator; * }</pre> */ public class Scroller { + private final Interpolator mInterpolator; + private int mMode; private int mStartX; @@ -81,7 +83,6 @@ public class Scroller { private float mDeltaX; private float mDeltaY; private boolean mFinished; - private Interpolator mInterpolator; private boolean mFlywheel; private float mVelocity; @@ -142,18 +143,8 @@ public class Scroller { SPLINE_TIME[i] = coef * ((1.0f - y) * P1 + y * P2) + y * y * y; } SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f; - - // This controls the viscous fluid effect (how much of it) - sViscousFluidScale = 8.0f; - // must be set to 1.0 (used in viscousFluid()) - sViscousFluidNormalize = 1.0f; - sViscousFluidNormalize = 1.0f / viscousFluid(1.0f); - } - private static float sViscousFluidScale; - private static float sViscousFluidNormalize; - /** * Create a Scroller with the default duration and interpolator. */ @@ -178,7 +169,11 @@ public class Scroller { */ public Scroller(Context context, Interpolator interpolator, boolean flywheel) { mFinished = true; - mInterpolator = interpolator; + if (interpolator == null) { + mInterpolator = new ViscousFluidInterpolator(); + } else { + mInterpolator = interpolator; + } mPpi = context.getResources().getDisplayMetrics().density * 160.0f; mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction()); mFlywheel = flywheel; @@ -312,13 +307,7 @@ public class Scroller { if (timePassed < mDuration) { switch (mMode) { case SCROLL_MODE: - float x = timePassed * mDurationReciprocal; - - if (mInterpolator == null) - x = viscousFluid(x); - else - x = mInterpolator.getInterpolation(x); - + final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal); mCurrX = mStartX + Math.round(x * mDeltaX); mCurrY = mStartY + Math.round(x * mDeltaY); break; @@ -499,20 +488,6 @@ public class Scroller { return mFlingFriction * mPhysicalCoeff * Math.exp(DECELERATION_RATE / decelMinusOne * l); } - static float viscousFluid(float x) - { - x *= sViscousFluidScale; - if (x < 1.0f) { - x -= (1.0f - (float)Math.exp(-x)); - } else { - float start = 0.36787944117f; // 1/e == exp(-1) - x = 1.0f - (float)Math.exp(1.0f - x); - x = start + x * (1.0f - start); - } - x *= sViscousFluidNormalize; - return x; - } - /** * Stops the animation. Contrary to {@link #forceFinished(boolean)}, * aborting the animating cause the scroller to move to the final x and y @@ -583,4 +558,41 @@ public class Scroller { return !mFinished && Math.signum(xvel) == Math.signum(mFinalX - mStartX) && Math.signum(yvel) == Math.signum(mFinalY - mStartY); } + + static class ViscousFluidInterpolator implements Interpolator { + /** Controls the viscous fluid effect (how much of it). */ + private static final float VISCOUS_FLUID_SCALE = 8.0f; + + private static final float VISCOUS_FLUID_NORMALIZE; + private static final float VISCOUS_FLUID_OFFSET; + + static { + + // must be set to 1.0 (used in viscousFluid()) + VISCOUS_FLUID_NORMALIZE = 1.0f / viscousFluid(1.0f); + // account for very small floating-point error + VISCOUS_FLUID_OFFSET = 1.0f - VISCOUS_FLUID_NORMALIZE * viscousFluid(1.0f); + } + + private static float viscousFluid(float x) { + x *= VISCOUS_FLUID_SCALE; + if (x < 1.0f) { + x -= (1.0f - (float)Math.exp(-x)); + } else { + float start = 0.36787944117f; // 1/e == exp(-1) + x = 1.0f - (float)Math.exp(1.0f - x); + x = start + x * (1.0f - start); + } + return x; + } + + @Override + public float getInterpolation(float input) { + final float interpolated = VISCOUS_FLUID_NORMALIZE * viscousFluid(input); + if (input > 0) { + return input + VISCOUS_FLUID_OFFSET; + } + return input; + } + } } diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java index 0281602..1eedc5d 100644 --- a/core/java/android/widget/SearchView.java +++ b/core/java/android/widget/SearchView.java @@ -242,7 +242,15 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { } public SearchView(Context context, AttributeSet attrs) { - super(context, attrs); + this(context, attrs, 0); + } + + public SearchView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public SearchView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); LayoutInflater inflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE); @@ -281,7 +289,8 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { } }); - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SearchView, 0, 0); + TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.SearchView, defStyleAttr, defStyleRes); setIconifiedByDefault(a.getBoolean(R.styleable.SearchView_iconifiedByDefault, true)); int maxWidth = a.getDimensionPixelSize(R.styleable.SearchView_maxWidth, -1); if (maxWidth != -1) { @@ -304,7 +313,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { boolean focusable = true; - a = context.obtainStyledAttributes(attrs, R.styleable.View, 0, 0); + a = context.obtainStyledAttributes(attrs, R.styleable.View, defStyleAttr, defStyleRes); focusable = a.getBoolean(R.styleable.View_focusable, focusable); a.recycle(); setFocusable(focusable); @@ -1050,7 +1059,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { SpannableStringBuilder ssb = new SpannableStringBuilder(" "); // for the icon ssb.append(hintText); - Drawable searchIcon = getContext().getResources().getDrawable(getSearchIconId()); + Drawable searchIcon = getContext().getDrawable(getSearchIconId()); int textSize = (int) (mQueryTextView.getTextSize() * 1.25); searchIcon.setBounds(0, 0, textSize, textSize); ssb.setSpan(new ImageSpan(searchIcon), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); @@ -1661,8 +1670,14 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { mThreshold = getThreshold(); } - public SearchAutoComplete(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public SearchAutoComplete(Context context, AttributeSet attrs, int defStyleAttrs) { + super(context, attrs, defStyleAttrs); + mThreshold = getThreshold(); + } + + public SearchAutoComplete( + Context context, AttributeSet attrs, int defStyleAttrs, int defStyleRes) { + super(context, attrs, defStyleAttrs, defStyleRes); mThreshold = getThreshold(); } diff --git a/core/java/android/widget/SeekBar.java b/core/java/android/widget/SeekBar.java index 2737f94..dc7c04c 100644 --- a/core/java/android/widget/SeekBar.java +++ b/core/java/android/widget/SeekBar.java @@ -79,8 +79,12 @@ public class SeekBar extends AbsSeekBar { this(context, attrs, com.android.internal.R.attr.seekBarStyle); } - public SeekBar(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public SeekBar(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public SeekBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); } @Override diff --git a/core/java/android/widget/ShareActionProvider.java b/core/java/android/widget/ShareActionProvider.java index bdaaa01..64a1574 100644 --- a/core/java/android/widget/ShareActionProvider.java +++ b/core/java/android/widget/ShareActionProvider.java @@ -168,7 +168,7 @@ public class ShareActionProvider extends ActionProvider { // Lookup and set the expand action icon. TypedValue outTypedValue = new TypedValue(); mContext.getTheme().resolveAttribute(R.attr.actionModeShareDrawable, outTypedValue, true); - Drawable drawable = mContext.getResources().getDrawable(outTypedValue.resourceId); + Drawable drawable = mContext.getDrawable(outTypedValue.resourceId); activityChooserView.setExpandActivityOverflowButtonDrawable(drawable); activityChooserView.setProvider(this); diff --git a/core/java/android/widget/SlidingDrawer.java b/core/java/android/widget/SlidingDrawer.java index 517246b..ec06c02 100644 --- a/core/java/android/widget/SlidingDrawer.java +++ b/core/java/android/widget/SlidingDrawer.java @@ -192,11 +192,32 @@ public class SlidingDrawer extends ViewGroup { * * @param context The application's environment. * @param attrs The attributes defined in XML. - * @param defStyle The style to apply to this widget. + * @param defStyleAttr An attribute in the current theme that contains a + * reference to a style resource that supplies default values for + * the view. Can be 0 to not look for defaults. */ - public SlidingDrawer(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SlidingDrawer, defStyle, 0); + public SlidingDrawer(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + /** + * Creates a new SlidingDrawer from a specified set of attributes defined in XML. + * + * @param context The application's environment. + * @param attrs The attributes defined in XML. + * @param defStyleAttr An attribute in the current theme that contains a + * reference to a style resource that supplies default values for + * the view. Can be 0 to not look for defaults. + * @param defStyleRes A resource identifier of a style resource that + * supplies default values for the view, used only if + * defStyleAttr is 0 or can not be found in the theme. Can be 0 + * to not look for defaults. + */ + public SlidingDrawer(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + final TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.SlidingDrawer, defStyleAttr, defStyleRes); int orientation = a.getInt(R.styleable.SlidingDrawer_orientation, ORIENTATION_VERTICAL); mVertical = orientation == ORIENTATION_VERTICAL; diff --git a/core/java/android/widget/Space.java b/core/java/android/widget/Space.java index bb53a77..c4eaeb7 100644 --- a/core/java/android/widget/Space.java +++ b/core/java/android/widget/Space.java @@ -20,7 +20,6 @@ import android.content.Context; import android.graphics.Canvas; import android.util.AttributeSet; import android.view.View; -import android.view.ViewGroup; /** * Space is a lightweight View subclass that may be used to create gaps between components @@ -30,8 +29,8 @@ public final class Space extends View { /** * {@inheritDoc} */ - public Space(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public Space(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); if (getVisibility() == VISIBLE) { setVisibility(INVISIBLE); } @@ -40,6 +39,13 @@ public final class Space extends View { /** * {@inheritDoc} */ + public Space(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + /** + * {@inheritDoc} + */ public Space(Context context, AttributeSet attrs) { this(context, attrs, 0); } diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java index b204dfd..1cda631 100644 --- a/core/java/android/widget/SpellChecker.java +++ b/core/java/android/widget/SpellChecker.java @@ -731,10 +731,14 @@ public class SpellChecker implements SpellCheckerSessionListener { } } - if (scheduleOtherSpellCheck) { + if (scheduleOtherSpellCheck && wordStart <= end) { // Update range span: start new spell check from last wordStart setRangeSpan(editable, wordStart, end); } else { + if (DBG && scheduleOtherSpellCheck) { + Log.w(TAG, "Trying to schedule spellcheck for invalid region, from " + + wordStart + " to " + end); + } removeRangeSpan(editable); } diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java index 5cbabef..9601d4a 100644 --- a/core/java/android/widget/Spinner.java +++ b/core/java/android/widget/Spinner.java @@ -130,18 +130,17 @@ public class Spinner extends AbsSpinner implements OnClickListener { /** * Construct a new spinner with the given context's theme, the supplied attribute set, - * and default style. + * and default style attribute. * * @param context The Context the view is running in, through which it can * access the current theme, resources, etc. * @param attrs The attributes of the XML tag that is inflating the view. - * @param defStyle The default style to apply to this view. If 0, no style - * will be applied (beyond what is included in the theme). This may - * either be an attribute resource, whose value will be retrieved - * from the current theme, or an explicit style resource. + * @param defStyleAttr An attribute in the current theme that contains a + * reference to a style resource that supplies default values for + * the view. Can be 0 to not look for defaults. */ - public Spinner(Context context, AttributeSet attrs, int defStyle) { - this(context, attrs, defStyle, MODE_THEME); + public Spinner(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0, MODE_THEME); } /** @@ -152,20 +151,44 @@ public class Spinner extends AbsSpinner implements OnClickListener { * @param context The Context the view is running in, through which it can * access the current theme, resources, etc. * @param attrs The attributes of the XML tag that is inflating the view. - * @param defStyle The default style to apply to this view. If 0, no style - * will be applied (beyond what is included in the theme). This may - * either be an attribute resource, whose value will be retrieved - * from the current theme, or an explicit style resource. + * @param defStyleAttr An attribute in the current theme that contains a + * reference to a style resource that supplies default values for + * the view. Can be 0 to not look for defaults. * @param mode Constant describing how the user will select choices from the spinner. - * + * + * @see #MODE_DIALOG + * @see #MODE_DROPDOWN + */ + public Spinner(Context context, AttributeSet attrs, int defStyleAttr, int mode) { + this(context, attrs, defStyleAttr, 0, mode); + } + + /** + * Construct a new spinner with the given context's theme, the supplied attribute set, + * and default style. <code>mode</code> may be one of {@link #MODE_DIALOG} or + * {@link #MODE_DROPDOWN} and determines how the user will select choices from the spinner. + * + * @param context The Context the view is running in, through which it can + * access the current theme, resources, etc. + * @param attrs The attributes of the XML tag that is inflating the view. + * @param defStyleAttr An attribute in the current theme that contains a + * reference to a style resource that supplies default values for + * the view. Can be 0 to not look for defaults. + * @param defStyleRes A resource identifier of a style resource that + * supplies default values for the view, used only if + * defStyleAttr is 0 or can not be found in the theme. Can be 0 + * to not look for defaults. + * @param mode Constant describing how the user will select choices from the spinner. + * * @see #MODE_DIALOG * @see #MODE_DROPDOWN */ - public Spinner(Context context, AttributeSet attrs, int defStyle, int mode) { - super(context, attrs, defStyle); + public Spinner( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes, int mode) { + super(context, attrs, defStyleAttr, defStyleRes); - TypedArray a = context.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.Spinner, defStyle, 0); + final TypedArray a = context.obtainStyledAttributes( + attrs, com.android.internal.R.styleable.Spinner, defStyleAttr, defStyleRes); if (mode == MODE_THEME) { mode = a.getInt(com.android.internal.R.styleable.Spinner_spinnerMode, MODE_DIALOG); @@ -178,7 +201,7 @@ public class Spinner extends AbsSpinner implements OnClickListener { } case MODE_DROPDOWN: { - final DropdownPopup popup = new DropdownPopup(context, attrs, defStyle); + final DropdownPopup popup = new DropdownPopup(context, attrs, defStyleAttr, defStyleRes); mDropDownWidth = a.getLayoutDimension( com.android.internal.R.styleable.Spinner_dropDownWidth, @@ -258,7 +281,7 @@ public class Spinner extends AbsSpinner implements OnClickListener { * @attr ref android.R.styleable#Spinner_popupBackground */ public void setPopupBackgroundResource(int resId) { - setPopupBackgroundDrawable(getContext().getResources().getDrawable(resId)); + setPopupBackgroundDrawable(getContext().getDrawable(resId)); } /** @@ -1033,8 +1056,9 @@ public class Spinner extends AbsSpinner implements OnClickListener { private CharSequence mHintText; private ListAdapter mAdapter; - public DropdownPopup(Context context, AttributeSet attrs, int defStyleRes) { - super(context, attrs, 0, defStyleRes); + public DropdownPopup( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); setAnchorView(Spinner.this); setModal(true); diff --git a/core/java/android/widget/StackView.java b/core/java/android/widget/StackView.java index 6853660..d2e718c 100644 --- a/core/java/android/widget/StackView.java +++ b/core/java/android/widget/StackView.java @@ -168,9 +168,16 @@ public class StackView extends AdapterViewAnimator { * {@inheritDoc} */ public StackView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - TypedArray a = context.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.StackView, defStyleAttr, 0); + this(context, attrs, defStyleAttr, 0); + } + + /** + * {@inheritDoc} + */ + public StackView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + final TypedArray a = context.obtainStyledAttributes( + attrs, com.android.internal.R.styleable.StackView, defStyleAttr, defStyleRes); mResOutColor = a.getColor( com.android.internal.R.styleable.StackView_resOutColor, 0); diff --git a/core/java/android/widget/SuggestionsAdapter.java b/core/java/android/widget/SuggestionsAdapter.java index c44d431..c8917e0 100644 --- a/core/java/android/widget/SuggestionsAdapter.java +++ b/core/java/android/widget/SuggestionsAdapter.java @@ -529,7 +529,7 @@ class SuggestionsAdapter extends ResourceCursorAdapter implements OnClickListene return drawable; } // Not cached, find it by resource ID - drawable = mProviderContext.getResources().getDrawable(resourceId); + drawable = mProviderContext.getDrawable(resourceId); // Stick it in the cache, using the URI as key storeInIconCache(drawableUri, drawable); return drawable; @@ -563,7 +563,7 @@ class SuggestionsAdapter extends ResourceCursorAdapter implements OnClickListene OpenResourceIdResult r = mProviderContext.getContentResolver().getResourceId(uri); try { - return r.r.getDrawable(r.id); + return r.r.getDrawable(r.id, mContext.getTheme()); } catch (Resources.NotFoundException ex) { throw new FileNotFoundException("Resource does not exist: " + uri); } diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java index e754c17..3d23e4d 100644 --- a/core/java/android/widget/Switch.java +++ b/core/java/android/widget/Switch.java @@ -16,6 +16,7 @@ package android.widget; +import android.animation.ObjectAnimator; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Resources; @@ -32,6 +33,8 @@ import android.text.TextUtils; import android.text.method.AllCapsTransformationMethod; import android.text.method.TransformationMethod2; import android.util.AttributeSet; +import android.util.FloatProperty; +import android.util.MathUtils; import android.view.Gravity; import android.view.MotionEvent; import android.view.VelocityTracker; @@ -66,6 +69,8 @@ import com.android.internal.R; * @attr ref android.R.styleable#Switch_track */ public class Switch extends CompoundButton { + private static final int THUMB_ANIMATION_DURATION = 250; + private static final int TOUCH_MODE_IDLE = 0; private static final int TOUCH_MODE_DOWN = 1; private static final int TOUCH_MODE_DRAGGING = 2; @@ -105,6 +110,7 @@ public class Switch extends CompoundButton { private Layout mOnLayout; private Layout mOffLayout; private TransformationMethod2 mSwitchTransformationMethod; + private ObjectAnimator mPositionAnimator; @SuppressWarnings("hiding") private final Rect mTempRect = new Rect(); @@ -139,19 +145,41 @@ public class Switch extends CompoundButton { * * @param context The Context that will determine this widget's theming. * @param attrs Specification of attributes that should deviate from the default styling. - * @param defStyle An attribute ID within the active theme containing a reference to the - * default style for this widget. e.g. android.R.attr.switchStyle. + * @param defStyleAttr An attribute in the current theme that contains a + * reference to a style resource that supplies default values for + * the view. Can be 0 to not look for defaults. + */ + public Switch(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + + /** + * Construct a new Switch with a default style determined by the given theme + * attribute or style resource, overriding specific style attributes as + * requested. + * + * @param context The Context that will determine this widget's theming. + * @param attrs Specification of attributes that should deviate from the + * default styling. + * @param defStyleAttr An attribute in the current theme that contains a + * reference to a style resource that supplies default values for + * the view. Can be 0 to not look for defaults. + * @param defStyleRes A resource identifier of a style resource that + * supplies default values for the view, used only if + * defStyleAttr is 0 or can not be found in the theme. Can be 0 + * to not look for defaults. */ - public Switch(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public Switch(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); Resources res = getResources(); mTextPaint.density = res.getDisplayMetrics().density; mTextPaint.setCompatibilityScaling(res.getCompatibilityInfo().applicationScale); - TypedArray a = context.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.Switch, defStyle, 0); + final TypedArray a = context.obtainStyledAttributes( + attrs, com.android.internal.R.styleable.Switch, defStyleAttr, defStyleRes); mThumbDrawable = a.getDrawable(com.android.internal.R.styleable.Switch_thumb); mTrackDrawable = a.getDrawable(com.android.internal.R.styleable.Switch_track); @@ -389,7 +417,7 @@ public class Switch extends CompoundButton { * @attr ref android.R.styleable#Switch_track */ public void setTrackResource(int resId) { - setTrackDrawable(getContext().getResources().getDrawable(resId)); + setTrackDrawable(getContext().getDrawable(resId)); } /** @@ -425,7 +453,7 @@ public class Switch extends CompoundButton { * @attr ref android.R.styleable#Switch_thumb */ public void setThumbResource(int resId) { - setThumbDrawable(getContext().getResources().getDrawable(resId)); + setThumbDrawable(getContext().getDrawable(resId)); } /** @@ -483,15 +511,18 @@ public class Switch extends CompoundButton { if (mOnLayout == null) { mOnLayout = makeLayout(mTextOn); } + if (mOffLayout == null) { mOffLayout = makeLayout(mTextOff); } mTrackDrawable.getPadding(mTempRect); + final int maxTextWidth = Math.max(mOnLayout.getWidth(), mOffLayout.getWidth()); final int switchWidth = Math.max(mSwitchMinWidth, maxTextWidth * 2 + mThumbTextPadding * 4 + mTempRect.left + mTempRect.right); - final int switchHeight = mTrackDrawable.getIntrinsicHeight(); + final int switchHeight = Math.max(mTrackDrawable.getIntrinsicHeight(), + mThumbDrawable.getIntrinsicHeight()); mThumbWidth = maxTextWidth + mThumbTextPadding * 2; @@ -528,9 +559,12 @@ public class Switch extends CompoundButton { * @return true if (x, y) is within the target area of the switch thumb */ private boolean hitThumb(float x, float y) { + // Relies on mTempRect, MUST be called first! + final int thumbOffset = getThumbOffset(); + mThumbDrawable.getPadding(mTempRect); final int thumbTop = mSwitchTop - mTouchSlop; - final int thumbLeft = mSwitchLeft + (int) (mThumbPosition + 0.5f) - mTouchSlop; + final int thumbLeft = mSwitchLeft + thumbOffset - mTouchSlop; final int thumbRight = thumbLeft + mThumbWidth + mTempRect.left + mTempRect.right + mTouchSlop; final int thumbBottom = mSwitchBottom + mTouchSlop; @@ -575,13 +609,23 @@ public class Switch extends CompoundButton { case TOUCH_MODE_DRAGGING: { final float x = ev.getX(); - final float dx = x - mTouchX; - float newPos = Math.max(0, - Math.min(mThumbPosition + dx, getThumbScrollRange())); + final int thumbScrollRange = getThumbScrollRange(); + final float thumbScrollOffset = x - mTouchX; + float dPos; + if (thumbScrollRange != 0) { + dPos = thumbScrollOffset / thumbScrollRange; + } else { + // If the thumb scroll range is empty, just use the + // movement direction to snap on or off. + dPos = thumbScrollOffset > 0 ? 1 : -1; + } + if (isLayoutRtl()) { + dPos = -dPos; + } + final float newPos = MathUtils.constrain(mThumbPosition + dPos, 0, 1); if (newPos != mThumbPosition) { - mThumbPosition = newPos; mTouchX = x; - invalidate(); + setThumbPosition(newPos); } return true; } @@ -618,62 +662,77 @@ public class Switch extends CompoundButton { */ private void stopDrag(MotionEvent ev) { mTouchMode = TOUCH_MODE_IDLE; - // Up and not canceled, also checks the switch has not been disabled during the drag - boolean commitChange = ev.getAction() == MotionEvent.ACTION_UP && isEnabled(); - - cancelSuperTouch(ev); + // Commit the change if the event is up and not canceled and the switch + // has not been disabled during the drag. + final boolean commitChange = ev.getAction() == MotionEvent.ACTION_UP && isEnabled(); + final boolean newState; if (commitChange) { - boolean newState; mVelocityTracker.computeCurrentVelocity(1000); - float xvel = mVelocityTracker.getXVelocity(); + final float xvel = mVelocityTracker.getXVelocity(); if (Math.abs(xvel) > mMinFlingVelocity) { newState = isLayoutRtl() ? (xvel < 0) : (xvel > 0); } else { newState = getTargetCheckedState(); } - animateThumbToCheckedState(newState); } else { - animateThumbToCheckedState(isChecked()); + newState = isChecked(); } + + setChecked(newState); + cancelSuperTouch(ev); } private void animateThumbToCheckedState(boolean newCheckedState) { - // TODO animate! - //float targetPos = newCheckedState ? 0 : getThumbScrollRange(); - //mThumbPosition = targetPos; - setChecked(newCheckedState); + final float targetPosition = newCheckedState ? 1 : 0; + mPositionAnimator = ObjectAnimator.ofFloat(this, THUMB_POS, targetPosition); + mPositionAnimator.setDuration(THUMB_ANIMATION_DURATION); + mPositionAnimator.setAutoCancel(true); + mPositionAnimator.start(); } - private boolean getTargetCheckedState() { - if (isLayoutRtl()) { - return mThumbPosition <= getThumbScrollRange() / 2; - } else { - return mThumbPosition >= getThumbScrollRange() / 2; + private void cancelPositionAnimator() { + if (mPositionAnimator != null) { + mPositionAnimator.cancel(); } } - private void setThumbPosition(boolean checked) { - if (isLayoutRtl()) { - mThumbPosition = checked ? 0 : getThumbScrollRange(); - } else { - mThumbPosition = checked ? getThumbScrollRange() : 0; - } + private boolean getTargetCheckedState() { + return mThumbPosition > 0.5f; + } + + /** + * Sets the thumb position as a decimal value between 0 (off) and 1 (on). + * + * @param position new position between [0,1] + */ + private void setThumbPosition(float position) { + mThumbPosition = position; + invalidate(); + } + + @Override + public void toggle() { + setChecked(!isChecked()); } @Override public void setChecked(boolean checked) { super.setChecked(checked); - setThumbPosition(isChecked()); - invalidate(); + + if (isAttachedToWindow() && isLaidOut()) { + animateThumbToCheckedState(checked); + } else { + // Immediately move the thumb to the new position. + cancelPositionAnimator(); + setThumbPosition(checked ? 1 : 0); + } } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); - setThumbPosition(isChecked()); - int switchRight; int switchLeft; @@ -716,47 +775,51 @@ public class Switch extends CompoundButton { protected void onDraw(Canvas canvas) { super.onDraw(canvas); - // Draw the switch - int switchLeft = mSwitchLeft; - int switchTop = mSwitchTop; - int switchRight = mSwitchRight; - int switchBottom = mSwitchBottom; - - mTrackDrawable.setBounds(switchLeft, switchTop, switchRight, switchBottom); - mTrackDrawable.draw(canvas); + final Rect tempRect = mTempRect; + final Drawable trackDrawable = mTrackDrawable; + final Drawable thumbDrawable = mThumbDrawable; - canvas.save(); - - mTrackDrawable.getPadding(mTempRect); - int switchInnerLeft = switchLeft + mTempRect.left; - int switchInnerTop = switchTop + mTempRect.top; - int switchInnerRight = switchRight - mTempRect.right; - int switchInnerBottom = switchBottom - mTempRect.bottom; + // Draw the switch + final int switchLeft = mSwitchLeft; + final int switchTop = mSwitchTop; + final int switchRight = mSwitchRight; + final int switchBottom = mSwitchBottom; + trackDrawable.setBounds(switchLeft, switchTop, switchRight, switchBottom); + trackDrawable.draw(canvas); + + final int saveCount = canvas.save(); + + trackDrawable.getPadding(tempRect); + final int switchInnerLeft = switchLeft + tempRect.left; + final int switchInnerTop = switchTop + tempRect.top; + final int switchInnerRight = switchRight - tempRect.right; + final int switchInnerBottom = switchBottom - tempRect.bottom; canvas.clipRect(switchInnerLeft, switchTop, switchInnerRight, switchBottom); - mThumbDrawable.getPadding(mTempRect); - final int thumbPos = (int) (mThumbPosition + 0.5f); - int thumbLeft = switchInnerLeft - mTempRect.left + thumbPos; - int thumbRight = switchInnerLeft + thumbPos + mThumbWidth + mTempRect.right; + // Relies on mTempRect, MUST be called first! + final int thumbPos = getThumbOffset(); - mThumbDrawable.setBounds(thumbLeft, switchTop, thumbRight, switchBottom); - mThumbDrawable.draw(canvas); + thumbDrawable.getPadding(tempRect); + int thumbLeft = switchInnerLeft - tempRect.left + thumbPos; + int thumbRight = switchInnerLeft + thumbPos + mThumbWidth + tempRect.right; + thumbDrawable.setBounds(thumbLeft, switchTop, thumbRight, switchBottom); + thumbDrawable.draw(canvas); - // mTextColors should not be null, but just in case + final int drawableState[] = getDrawableState(); if (mTextColors != null) { - mTextPaint.setColor(mTextColors.getColorForState(getDrawableState(), - mTextColors.getDefaultColor())); + mTextPaint.setColor(mTextColors.getColorForState(drawableState, 0)); } - mTextPaint.drawableState = getDrawableState(); + mTextPaint.drawableState = drawableState; - Layout switchText = getTargetCheckedState() ? mOnLayout : mOffLayout; + final Layout switchText = getTargetCheckedState() ? mOnLayout : mOffLayout; if (switchText != null) { - canvas.translate((thumbLeft + thumbRight) / 2 - switchText.getWidth() / 2, - (switchInnerTop + switchInnerBottom) / 2 - switchText.getHeight() / 2); + final int left = (thumbLeft + thumbRight) / 2 - switchText.getWidth() / 2; + final int top = (switchInnerTop + switchInnerBottom) / 2 - switchText.getHeight() / 2; + canvas.translate(left, top); switchText.draw(canvas); } - canvas.restore(); + canvas.restoreToCount(saveCount); } @Override @@ -783,6 +846,22 @@ public class Switch extends CompoundButton { return padding; } + /** + * Translates thumb position to offset according to current RTL setting and + * thumb scroll range. + * + * @return thumb offset + */ + private int getThumbOffset() { + final float thumbPosition; + if (isLayoutRtl()) { + thumbPosition = 1 - mThumbPosition; + } else { + thumbPosition = mThumbPosition; + } + return (int) (thumbPosition * getThumbScrollRange() + 0.5f); + } + private int getThumbScrollRange() { if (mTrackDrawable == null) { return 0; @@ -848,4 +927,16 @@ public class Switch extends CompoundButton { } } } + + private static final FloatProperty<Switch> THUMB_POS = new FloatProperty<Switch>("thumbPos") { + @Override + public Float get(Switch object) { + return object.mThumbPosition; + } + + @Override + public void setValue(Switch object, float value) { + object.setThumbPosition(value); + } + }; } diff --git a/core/java/android/widget/TabHost.java b/core/java/android/widget/TabHost.java index 238dc55..89df51a 100644 --- a/core/java/android/widget/TabHost.java +++ b/core/java/android/widget/TabHost.java @@ -77,11 +77,18 @@ public class TabHost extends FrameLayout implements ViewTreeObserver.OnTouchMode } public TabHost(Context context, AttributeSet attrs) { + this(context, attrs, com.android.internal.R.attr.tabWidgetStyle); + } + + public TabHost(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public TabHost(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs); - TypedArray a = context.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.TabWidget, - com.android.internal.R.attr.tabWidgetStyle, 0); + final TypedArray a = context.obtainStyledAttributes( + attrs, com.android.internal.R.styleable.TabWidget, defStyleAttr, defStyleRes); mTabLayoutId = a.getResourceId(R.styleable.TabWidget_tabLayout, 0); a.recycle(); diff --git a/core/java/android/widget/TabWidget.java b/core/java/android/widget/TabWidget.java index 6bced1c..47a5449 100644 --- a/core/java/android/widget/TabWidget.java +++ b/core/java/android/widget/TabWidget.java @@ -74,11 +74,15 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener { this(context, attrs, com.android.internal.R.attr.tabWidgetStyle); } - public TabWidget(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public TabWidget(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public TabWidget(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); final TypedArray a = context.obtainStyledAttributes( - attrs, com.android.internal.R.styleable.TabWidget, defStyle, 0); + attrs, com.android.internal.R.styleable.TabWidget, defStyleAttr, defStyleRes); setStripEnabled(a.getBoolean(R.styleable.TabWidget_tabStripEnabled, true)); setLeftStripDrawable(a.getDrawable(R.styleable.TabWidget_tabStripLeft)); @@ -116,28 +120,27 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener { setChildrenDrawingOrderEnabled(true); final Context context = mContext; - final Resources resources = context.getResources(); // Tests the target Sdk version, as set in the Manifest. Could not be set using styles.xml // in a values-v? directory which targets the current platform Sdk version instead. if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.DONUT) { // Donut apps get old color scheme if (mLeftStrip == null) { - mLeftStrip = resources.getDrawable( + mLeftStrip = context.getDrawable( com.android.internal.R.drawable.tab_bottom_left_v4); } if (mRightStrip == null) { - mRightStrip = resources.getDrawable( + mRightStrip = context.getDrawable( com.android.internal.R.drawable.tab_bottom_right_v4); } } else { // Use modern color scheme for Eclair and beyond if (mLeftStrip == null) { - mLeftStrip = resources.getDrawable( + mLeftStrip = context.getDrawable( com.android.internal.R.drawable.tab_bottom_left); } if (mRightStrip == null) { - mRightStrip = resources.getDrawable( + mRightStrip = context.getDrawable( com.android.internal.R.drawable.tab_bottom_right); } } @@ -242,7 +245,7 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener { * divider. */ public void setDividerDrawable(int resId) { - setDividerDrawable(getResources().getDrawable(resId)); + setDividerDrawable(mContext.getDrawable(resId)); } /** @@ -263,7 +266,7 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener { * left strip drawable */ public void setLeftStripDrawable(int resId) { - setLeftStripDrawable(getResources().getDrawable(resId)); + setLeftStripDrawable(mContext.getDrawable(resId)); } /** @@ -284,7 +287,7 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener { * right strip drawable */ public void setRightStripDrawable(int resId) { - setRightStripDrawable(getResources().getDrawable(resId)); + setRightStripDrawable(mContext.getDrawable(resId)); } /** diff --git a/core/java/android/widget/TextClock.java b/core/java/android/widget/TextClock.java index b3b95d9..4c5c71d 100644 --- a/core/java/android/widget/TextClock.java +++ b/core/java/android/widget/TextClock.java @@ -198,15 +198,19 @@ public class TextClock extends TextView { * @param context The Context the view is running in, through which it can * access the current theme, resources, etc. * @param attrs The attributes of the XML tag that is inflating the view - * @param defStyle The default style to apply to this view. If 0, no style - * will be applied (beyond what is included in the theme). This may - * either be an attribute resource, whose value will be retrieved - * from the current theme, or an explicit style resource + * @param defStyleAttr An attribute in the current theme that contains a + * reference to a style resource that supplies default values for + * the view. Can be 0 to not look for defaults. */ - public TextClock(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public TextClock(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public TextClock(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TextClock, defStyle, 0); + final TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.TextClock, defStyleAttr, defStyleRes); try { mFormat12 = a.getText(R.styleable.TextClock_format12Hour); mFormat24 = a.getText(R.styleable.TextClock_format24Hour); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 37121e2..687036c 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -136,7 +136,6 @@ import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Locale; -import java.util.concurrent.locks.ReentrantLock; import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; @@ -618,9 +617,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener this(context, attrs, com.android.internal.R.attr.textViewStyle); } + public TextView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + @SuppressWarnings("deprecation") - public TextView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public TextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + mText = ""; final Resources res = getResources(); @@ -657,8 +661,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * to be able to parse the appearance first and then let specific tags * for this View override it. */ - TypedArray a = theme.obtainStyledAttributes( - attrs, com.android.internal.R.styleable.TextViewAppearance, defStyle, 0); + TypedArray a = theme.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes); TypedArray appearance = null; int ap = a.getResourceId( com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1); @@ -751,7 +755,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener int inputType = EditorInfo.TYPE_NULL; a = theme.obtainStyledAttributes( - attrs, com.android.internal.R.styleable.TextView, defStyle, 0); + attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes); int n = a.getIndexCount(); for (int i = 0; i < n; i++) { @@ -1275,9 +1279,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * However, TextViews that have input or movement methods *are* * focusable by default. */ - a = context.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.View, - defStyle, 0); + a = context.obtainStyledAttributes( + attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes); boolean focusable = mMovement != null || getKeyListener() != null; boolean clickable = focusable; @@ -2070,11 +2073,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ @android.view.RemotableViewMethod public void setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom) { - final Resources resources = getContext().getResources(); - setCompoundDrawablesWithIntrinsicBounds(left != 0 ? resources.getDrawable(left) : null, - top != 0 ? resources.getDrawable(top) : null, - right != 0 ? resources.getDrawable(right) : null, - bottom != 0 ? resources.getDrawable(bottom) : null); + final Context context = getContext(); + setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null, + top != 0 ? context.getDrawable(top) : null, + right != 0 ? context.getDrawable(right) : null, + bottom != 0 ? context.getDrawable(bottom) : null); } /** @@ -2244,12 +2247,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @android.view.RemotableViewMethod public void setCompoundDrawablesRelativeWithIntrinsicBounds(int start, int top, int end, int bottom) { - final Resources resources = getContext().getResources(); + final Context context = getContext(); setCompoundDrawablesRelativeWithIntrinsicBounds( - start != 0 ? resources.getDrawable(start) : null, - top != 0 ? resources.getDrawable(top) : null, - end != 0 ? resources.getDrawable(end) : null, - bottom != 0 ? resources.getDrawable(bottom) : null); + start != 0 ? context.getDrawable(start) : null, + top != 0 ? context.getDrawable(top) : null, + end != 0 ? context.getDrawable(end) : null, + bottom != 0 ? context.getDrawable(bottom) : null); } /** @@ -4382,8 +4385,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (error == null) { setError(null, null); } else { - Drawable dr = getContext().getResources(). - getDrawable(com.android.internal.R.drawable.indicator_input_error); + Drawable dr = getContext().getDrawable( + com.android.internal.R.drawable.indicator_input_error); dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight()); setError(error, dr); @@ -4726,10 +4729,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mEditor != null) mEditor.onAttachedToWindow(); } + /** @hide */ @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - + protected void onDetachedFromWindowInternal() { if (mPreDrawRegistered) { getViewTreeObserver().removeOnPreDrawListener(this); mPreDrawRegistered = false; @@ -4738,6 +4740,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener resetResolvedDrawables(); if (mEditor != null) mEditor.onDetachedFromWindow(); + + super.onDetachedFromWindowInternal(); } @Override @@ -4811,6 +4815,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @Override public void invalidateDrawable(Drawable drawable) { + boolean handled = false; + if (verifyDrawable(drawable)) { final Rect dirty = drawable.getBounds(); int scrollX = mScrollX; @@ -4828,6 +4834,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener scrollX += mPaddingLeft; scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2; + handled = true; } else if (drawable == drawables.mDrawableRight) { final int compoundPaddingTop = getCompoundPaddingTop(); final int compoundPaddingBottom = getCompoundPaddingBottom(); @@ -4835,6 +4842,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight); scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2; + handled = true; } else if (drawable == drawables.mDrawableTop) { final int compoundPaddingLeft = getCompoundPaddingLeft(); final int compoundPaddingRight = getCompoundPaddingRight(); @@ -4842,6 +4850,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2; scrollY += mPaddingTop; + handled = true; } else if (drawable == drawables.mDrawableBottom) { final int compoundPaddingLeft = getCompoundPaddingLeft(); final int compoundPaddingRight = getCompoundPaddingRight(); @@ -4849,11 +4858,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2; scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom); + handled = true; } } - invalidate(dirty.left + scrollX, dirty.top + scrollY, - dirty.right + scrollX, dirty.bottom + scrollY); + if (handled) { + invalidate(dirty.left + scrollX, dirty.top + scrollY, + dirty.right + scrollX, dirty.bottom + scrollY); + } + } + + if (!handled) { + super.invalidateDrawable(drawable); } } @@ -5794,6 +5810,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener int end = text.partialEndOffset; if (end > N) end = N; removeParcelableSpans(content, start, end); + // If start > end, content.replace will swap them before using them. content.replace(start, end, text.text); } } @@ -8478,7 +8495,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return false; } - if (mText.length() > 0 && hasSelection()) { + if (mText.length() > 0 && hasSelection() && mEditor != null) { return true; } diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java index c26cb24..8e4ba0d 100644 --- a/core/java/android/widget/TimePicker.java +++ b/core/java/android/widget/TimePicker.java @@ -20,26 +20,17 @@ import android.annotation.Widget; import android.content.Context; import android.content.res.Configuration; import android.content.res.TypedArray; -import android.os.Parcel; import android.os.Parcelable; -import android.text.format.DateFormat; -import android.text.format.DateUtils; import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodManager; -import android.widget.NumberPicker.OnValueChangeListener; import com.android.internal.R; -import java.text.DateFormatSymbols; -import java.util.Calendar; import java.util.Locale; +import static android.os.Build.VERSION_CODES.KITKAT; + /** * A view for selecting the time of day, in either 24 hour or AM/PM mode. The * hour, each minute digit, and AM/PM (if applicable) can be conrolled by @@ -57,58 +48,12 @@ import java.util.Locale; @Widget public class TimePicker extends FrameLayout { - private static final boolean DEFAULT_ENABLED_STATE = true; + private TimePickerDelegate mDelegate; - private static final int HOURS_IN_HALF_DAY = 12; - - /** - * A no-op callback used in the constructor to avoid null checks later in - * the code. - */ - private static final OnTimeChangedListener NO_OP_CHANGE_LISTENER = new OnTimeChangedListener() { - public void onTimeChanged(TimePicker view, int hourOfDay, int minute) { - } - }; - - // state - private boolean mIs24HourView; - - private boolean mIsAm; - - // ui components - private final NumberPicker mHourSpinner; - - private final NumberPicker mMinuteSpinner; - - private final NumberPicker mAmPmSpinner; - - private final EditText mHourSpinnerInput; - - private final EditText mMinuteSpinnerInput; - - private final EditText mAmPmSpinnerInput; - - private final TextView mDivider; - - // Note that the legacy implementation of the TimePicker is - // using a button for toggling between AM/PM while the new - // version uses a NumberPicker spinner. Therefore the code - // accommodates these two cases to be backwards compatible. - private final Button mAmPmButton; - - private final String[] mAmPmStrings; - - private boolean mIsEnabled = DEFAULT_ENABLED_STATE; - - // callbacks - private OnTimeChangedListener mOnTimeChangedListener; - - private Calendar mTempCalendar; - - private Locale mCurrentLocale; - - private boolean mHourWithTwoDigit; - private char mHourFormat; + private AttributeSet mAttrs; + private int mDefStyleAttr; + private int mDefStyleRes; + private Context mContext; /** * The callback interface used to indicate the time has been adjusted. @@ -131,345 +76,79 @@ public class TimePicker extends FrameLayout { this(context, attrs, R.attr.timePickerStyle); } - public TimePicker(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - // initialization based on locale - setCurrentLocale(Locale.getDefault()); - - // process style attributes - TypedArray attributesArray = context.obtainStyledAttributes( - attrs, R.styleable.TimePicker, defStyle, 0); - int layoutResourceId = attributesArray.getResourceId( - R.styleable.TimePicker_internalLayout, R.layout.time_picker); - attributesArray.recycle(); - - LayoutInflater inflater = (LayoutInflater) context.getSystemService( - Context.LAYOUT_INFLATER_SERVICE); - inflater.inflate(layoutResourceId, this, true); - - // hour - mHourSpinner = (NumberPicker) findViewById(R.id.hour); - mHourSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { - public void onValueChange(NumberPicker spinner, int oldVal, int newVal) { - updateInputState(); - if (!is24HourView()) { - if ((oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) - || (oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1)) { - mIsAm = !mIsAm; - updateAmPmControl(); - } - } - onTimeChanged(); - } - }); - mHourSpinnerInput = (EditText) mHourSpinner.findViewById(R.id.numberpicker_input); - mHourSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT); - - // divider (only for the new widget style) - mDivider = (TextView) findViewById(R.id.divider); - if (mDivider != null) { - setDividerText(); - } - - // minute - mMinuteSpinner = (NumberPicker) findViewById(R.id.minute); - mMinuteSpinner.setMinValue(0); - mMinuteSpinner.setMaxValue(59); - mMinuteSpinner.setOnLongPressUpdateInterval(100); - mMinuteSpinner.setFormatter(NumberPicker.getTwoDigitFormatter()); - mMinuteSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { - public void onValueChange(NumberPicker spinner, int oldVal, int newVal) { - updateInputState(); - int minValue = mMinuteSpinner.getMinValue(); - int maxValue = mMinuteSpinner.getMaxValue(); - if (oldVal == maxValue && newVal == minValue) { - int newHour = mHourSpinner.getValue() + 1; - if (!is24HourView() && newHour == HOURS_IN_HALF_DAY) { - mIsAm = !mIsAm; - updateAmPmControl(); - } - mHourSpinner.setValue(newHour); - } else if (oldVal == minValue && newVal == maxValue) { - int newHour = mHourSpinner.getValue() - 1; - if (!is24HourView() && newHour == HOURS_IN_HALF_DAY - 1) { - mIsAm = !mIsAm; - updateAmPmControl(); - } - mHourSpinner.setValue(newHour); - } - onTimeChanged(); - } - }); - mMinuteSpinnerInput = (EditText) mMinuteSpinner.findViewById(R.id.numberpicker_input); - mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT); - - /* Get the localized am/pm strings and use them in the spinner */ - mAmPmStrings = new DateFormatSymbols().getAmPmStrings(); - - // am/pm - View amPmView = findViewById(R.id.amPm); - if (amPmView instanceof Button) { - mAmPmSpinner = null; - mAmPmSpinnerInput = null; - mAmPmButton = (Button) amPmView; - mAmPmButton.setOnClickListener(new OnClickListener() { - public void onClick(View button) { - button.requestFocus(); - mIsAm = !mIsAm; - updateAmPmControl(); - onTimeChanged(); - } - }); - } else { - mAmPmButton = null; - mAmPmSpinner = (NumberPicker) amPmView; - mAmPmSpinner.setMinValue(0); - mAmPmSpinner.setMaxValue(1); - mAmPmSpinner.setDisplayedValues(mAmPmStrings); - mAmPmSpinner.setOnValueChangedListener(new OnValueChangeListener() { - public void onValueChange(NumberPicker picker, int oldVal, int newVal) { - updateInputState(); - picker.requestFocus(); - mIsAm = !mIsAm; - updateAmPmControl(); - onTimeChanged(); - } - }); - mAmPmSpinnerInput = (EditText) mAmPmSpinner.findViewById(R.id.numberpicker_input); - mAmPmSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_DONE); - } - - if (isAmPmAtStart()) { - // Move the am/pm view to the beginning - ViewGroup amPmParent = (ViewGroup) findViewById(R.id.timePickerLayout); - amPmParent.removeView(amPmView); - amPmParent.addView(amPmView, 0); - // Swap layout margins if needed. They may be not symmetrical (Old Standard Theme for - // example and not for Holo Theme) - ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) amPmView.getLayoutParams(); - final int startMargin = lp.getMarginStart(); - final int endMargin = lp.getMarginEnd(); - if (startMargin != endMargin) { - lp.setMarginStart(endMargin); - lp.setMarginEnd(startMargin); - } - } - - getHourFormatData(); - - // update controls to initial state - updateHourControl(); - updateMinuteControl(); - updateAmPmControl(); - - setOnTimeChangedListener(NO_OP_CHANGE_LISTENER); - - // set to current time - setCurrentHour(mTempCalendar.get(Calendar.HOUR_OF_DAY)); - setCurrentMinute(mTempCalendar.get(Calendar.MINUTE)); - - if (!isEnabled()) { - setEnabled(false); - } - - // set the content descriptions - setContentDescriptions(); - - // If not explicitly specified this view is important for accessibility. - if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { - setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); - } + public TimePicker(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); } - private void getHourFormatData() { - final Locale defaultLocale = Locale.getDefault(); - final String bestDateTimePattern = DateFormat.getBestDateTimePattern(defaultLocale, - (mIs24HourView) ? "Hm" : "hm"); - final int lengthPattern = bestDateTimePattern.length(); - mHourWithTwoDigit = false; - char hourFormat = '\0'; - // Check if the returned pattern is single or double 'H', 'h', 'K', 'k'. We also save - // the hour format that we found. - for (int i = 0; i < lengthPattern; i++) { - final char c = bestDateTimePattern.charAt(i); - if (c == 'H' || c == 'h' || c == 'K' || c == 'k') { - mHourFormat = c; - if (i + 1 < lengthPattern && c == bestDateTimePattern.charAt(i + 1)) { - mHourWithTwoDigit = true; - } - break; - } - } - } + public TimePicker(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); - private boolean isAmPmAtStart() { - final Locale defaultLocale = Locale.getDefault(); - final String bestDateTimePattern = DateFormat.getBestDateTimePattern(defaultLocale, - "hm" /* skeleton */); + mContext = context; + mAttrs = attrs; + mDefStyleAttr = defStyleAttr; + mDefStyleRes = defStyleRes; - return bestDateTimePattern.startsWith("a"); - } + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TimePicker, + mDefStyleAttr, mDefStyleRes); - @Override - public void setEnabled(boolean enabled) { - if (mIsEnabled == enabled) { - return; - } - super.setEnabled(enabled); - mMinuteSpinner.setEnabled(enabled); - if (mDivider != null) { - mDivider.setEnabled(enabled); - } - mHourSpinner.setEnabled(enabled); - if (mAmPmSpinner != null) { - mAmPmSpinner.setEnabled(enabled); - } else { - mAmPmButton.setEnabled(enabled); - } - mIsEnabled = enabled; + // Create the correct UI delegate. Default is the legacy one. + final boolean isLegacyMode = shouldForceLegacyMode() ? + true : a.getBoolean(R.styleable.TimePicker_legacyMode, true); + setLegacyMode(isLegacyMode); } - @Override - public boolean isEnabled() { - return mIsEnabled; + private boolean shouldForceLegacyMode() { + final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion; + return targetSdkVersion < KITKAT; } - @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - setCurrentLocale(newConfig.locale); + private TimePickerDelegate createLegacyUIDelegate(Context context, AttributeSet attrs, + int defStyleAttr, int defStyleRes) { + return new LegacyTimePickerDelegate(this, context, attrs, defStyleAttr, defStyleRes); } - /** - * Sets the current locale. - * - * @param locale The current locale. - */ - private void setCurrentLocale(Locale locale) { - if (locale.equals(mCurrentLocale)) { - return; - } - mCurrentLocale = locale; - mTempCalendar = Calendar.getInstance(locale); + private TimePickerDelegate createNewUIDelegate(Context context, AttributeSet attrs, + int defStyleAttr, int defStyleRes) { + return new android.widget.TimePickerDelegate(this, context, attrs, defStyleAttr, + defStyleRes); } /** - * Used to save / restore state of time picker + * @hide */ - private static class SavedState extends BaseSavedState { - - private final int mHour; - - private final int mMinute; - - private SavedState(Parcelable superState, int hour, int minute) { - super(superState); - mHour = hour; - mMinute = minute; - } - - private SavedState(Parcel in) { - super(in); - mHour = in.readInt(); - mMinute = in.readInt(); - } - - public int getHour() { - return mHour; - } - - public int getMinute() { - return mMinute; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - super.writeToParcel(dest, flags); - dest.writeInt(mHour); - dest.writeInt(mMinute); - } - - @SuppressWarnings({"unused", "hiding"}) - public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() { - public SavedState createFromParcel(Parcel in) { - return new SavedState(in); - } - - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - } - - @Override - protected Parcelable onSaveInstanceState() { - Parcelable superState = super.onSaveInstanceState(); - return new SavedState(superState, getCurrentHour(), getCurrentMinute()); - } - - @Override - protected void onRestoreInstanceState(Parcelable state) { - SavedState ss = (SavedState) state; - super.onRestoreInstanceState(ss.getSuperState()); - setCurrentHour(ss.getHour()); - setCurrentMinute(ss.getMinute()); + public void setLegacyMode(boolean isLegacyMode) { + removeAllViewsInLayout(); + mDelegate = isLegacyMode ? + createLegacyUIDelegate(mContext, mAttrs, mDefStyleAttr, mDefStyleRes) : + createNewUIDelegate(mContext, mAttrs, mDefStyleAttr, mDefStyleRes); } /** - * Set the callback that indicates the time has been adjusted by the user. - * - * @param onTimeChangedListener the callback, should not be null. + * Set the current hour. */ - public void setOnTimeChangedListener(OnTimeChangedListener onTimeChangedListener) { - mOnTimeChangedListener = onTimeChangedListener; + public void setCurrentHour(Integer currentHour) { + mDelegate.setCurrentHour(currentHour); } /** * @return The current hour in the range (0-23). */ public Integer getCurrentHour() { - int currentHour = mHourSpinner.getValue(); - if (is24HourView()) { - return currentHour; - } else if (mIsAm) { - return currentHour % HOURS_IN_HALF_DAY; - } else { - return (currentHour % HOURS_IN_HALF_DAY) + HOURS_IN_HALF_DAY; - } + return mDelegate.getCurrentHour(); } /** - * Set the current hour. + * Set the current minute (0-59). */ - public void setCurrentHour(Integer currentHour) { - setCurrentHour(currentHour, true); + public void setCurrentMinute(Integer currentMinute) { + mDelegate.setCurrentMinute(currentMinute); } - private void setCurrentHour(Integer currentHour, boolean notifyTimeChanged) { - // why was Integer used in the first place? - if (currentHour == null || currentHour == getCurrentHour()) { - return; - } - if (!is24HourView()) { - // convert [0,23] ordinal to wall clock display - if (currentHour >= HOURS_IN_HALF_DAY) { - mIsAm = false; - if (currentHour > HOURS_IN_HALF_DAY) { - currentHour = currentHour - HOURS_IN_HALF_DAY; - } - } else { - mIsAm = true; - if (currentHour == 0) { - currentHour = HOURS_IN_HALF_DAY; - } - } - updateAmPmControl(); - } - mHourSpinner.setValue(currentHour); - if (notifyTimeChanged) { - onTimeChanged(); - } + /** + * @return The current minute. + */ + public Integer getCurrentMinute() { + return mDelegate.getCurrentMinute(); } /** @@ -478,223 +157,174 @@ public class TimePicker extends FrameLayout { * @param is24HourView True = 24 hour mode. False = AM/PM. */ public void setIs24HourView(Boolean is24HourView) { - if (mIs24HourView == is24HourView) { - return; - } - // cache the current hour since spinner range changes and BEFORE changing mIs24HourView!! - int currentHour = getCurrentHour(); - // Order is important here. - mIs24HourView = is24HourView; - getHourFormatData(); - updateHourControl(); - // set value after spinner range is updated - be aware that because mIs24HourView has - // changed then getCurrentHour() is not equal to the currentHour we cached before so - // explicitly ask for *not* propagating any onTimeChanged() - setCurrentHour(currentHour, false /* no onTimeChanged() */); - updateMinuteControl(); - updateAmPmControl(); + mDelegate.setIs24HourView(is24HourView); } /** * @return true if this is in 24 hour view else false. */ public boolean is24HourView() { - return mIs24HourView; + return mDelegate.is24HourView(); } /** - * @return The current minute. + * Set the callback that indicates the time has been adjusted by the user. + * + * @param onTimeChangedListener the callback, should not be null. */ - public Integer getCurrentMinute() { - return mMinuteSpinner.getValue(); + public void setOnTimeChangedListener(OnTimeChangedListener onTimeChangedListener) { + mDelegate.setOnTimeChangedListener(onTimeChangedListener); } - /** - * Set the current minute (0-59). - */ - public void setCurrentMinute(Integer currentMinute) { - if (currentMinute == getCurrentMinute()) { + @Override + public void setEnabled(boolean enabled) { + if (mDelegate.isEnabled() == enabled) { return; } - mMinuteSpinner.setValue(currentMinute); - onTimeChanged(); + super.setEnabled(enabled); + mDelegate.setEnabled(enabled); + } + + @Override + public boolean isEnabled() { + return mDelegate.isEnabled(); } /** - * The time separator is defined in the Unicode CLDR and cannot be supposed to be ":". - * - * See http://unicode.org/cldr/trac/browser/trunk/common/main - * - * We pass the correct "skeleton" depending on 12 or 24 hours view and then extract the - * separator as the character which is just after the hour marker in the returned pattern. + * @hide */ - private void setDividerText() { - final Locale defaultLocale = Locale.getDefault(); - final String skeleton = (mIs24HourView) ? "Hm" : "hm"; - final String bestDateTimePattern = DateFormat.getBestDateTimePattern(defaultLocale, - skeleton); - final String separatorText; - int hourIndex = bestDateTimePattern.lastIndexOf('H'); - if (hourIndex == -1) { - hourIndex = bestDateTimePattern.lastIndexOf('h'); - } - if (hourIndex == -1) { - // Default case - separatorText = ":"; - } else { - int minuteIndex = bestDateTimePattern.indexOf('m', hourIndex + 1); - if (minuteIndex == -1) { - separatorText = Character.toString(bestDateTimePattern.charAt(hourIndex + 1)); - } else { - separatorText = bestDateTimePattern.substring(hourIndex + 1, minuteIndex); - } - } - mDivider.setText(separatorText); + public void setShowDoneButton(boolean showDoneButton) { + mDelegate.setShowDoneButton(showDoneButton); + } + + /** + * @hide + */ + public void setDismissCallback(TimePickerDismissCallback callback) { + mDelegate.setDismissCallback(callback); } @Override public int getBaseline() { - return mHourSpinner.getBaseline(); + return mDelegate.getBaseline(); + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + mDelegate.onConfigurationChanged(newConfig); + } + + @Override + protected Parcelable onSaveInstanceState() { + Parcelable superState = super.onSaveInstanceState(); + return mDelegate.onSaveInstanceState(superState); + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + BaseSavedState ss = (BaseSavedState) state; + super.onRestoreInstanceState(ss.getSuperState()); + mDelegate.onRestoreInstanceState(ss); } @Override public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { - onPopulateAccessibilityEvent(event); - return true; + return mDelegate.dispatchPopulateAccessibilityEvent(event); } @Override public void onPopulateAccessibilityEvent(AccessibilityEvent event) { super.onPopulateAccessibilityEvent(event); - - int flags = DateUtils.FORMAT_SHOW_TIME; - if (mIs24HourView) { - flags |= DateUtils.FORMAT_24HOUR; - } else { - flags |= DateUtils.FORMAT_12HOUR; - } - mTempCalendar.set(Calendar.HOUR_OF_DAY, getCurrentHour()); - mTempCalendar.set(Calendar.MINUTE, getCurrentMinute()); - String selectedDateUtterance = DateUtils.formatDateTime(mContext, - mTempCalendar.getTimeInMillis(), flags); - event.getText().add(selectedDateUtterance); + mDelegate.onPopulateAccessibilityEvent(event); } @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); - event.setClassName(TimePicker.class.getName()); + mDelegate.onInitializeAccessibilityEvent(event); } @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); - info.setClassName(TimePicker.class.getName()); + mDelegate.onInitializeAccessibilityNodeInfo(info); } - private void updateHourControl() { - if (is24HourView()) { - // 'k' means 1-24 hour - if (mHourFormat == 'k') { - mHourSpinner.setMinValue(1); - mHourSpinner.setMaxValue(24); - } else { - mHourSpinner.setMinValue(0); - mHourSpinner.setMaxValue(23); - } - } else { - // 'K' means 0-11 hour - if (mHourFormat == 'K') { - mHourSpinner.setMinValue(0); - mHourSpinner.setMaxValue(11); - } else { - mHourSpinner.setMinValue(1); - mHourSpinner.setMaxValue(12); - } - } - mHourSpinner.setFormatter(mHourWithTwoDigit ? NumberPicker.getTwoDigitFormatter() : null); - } + /** + * A delegate interface that defined the public API of the TimePicker. Allows different + * TimePicker implementations. This would need to be implemented by the TimePicker delegates + * for the real behavior. + */ + interface TimePickerDelegate { + void setCurrentHour(Integer currentHour); + Integer getCurrentHour(); - private void updateMinuteControl() { - if (is24HourView()) { - mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_DONE); - } else { - mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT); - } - } + void setCurrentMinute(Integer currentMinute); + Integer getCurrentMinute(); - private void updateAmPmControl() { - if (is24HourView()) { - if (mAmPmSpinner != null) { - mAmPmSpinner.setVisibility(View.GONE); - } else { - mAmPmButton.setVisibility(View.GONE); - } - } else { - int index = mIsAm ? Calendar.AM : Calendar.PM; - if (mAmPmSpinner != null) { - mAmPmSpinner.setValue(index); - mAmPmSpinner.setVisibility(View.VISIBLE); - } else { - mAmPmButton.setText(mAmPmStrings[index]); - mAmPmButton.setVisibility(View.VISIBLE); - } - } - sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); - } + void setIs24HourView(Boolean is24HourView); + boolean is24HourView(); - private void onTimeChanged() { - sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); - if (mOnTimeChangedListener != null) { - mOnTimeChangedListener.onTimeChanged(this, getCurrentHour(), getCurrentMinute()); - } + void setOnTimeChangedListener(OnTimeChangedListener onTimeChangedListener); + + void setEnabled(boolean enabled); + boolean isEnabled(); + + void setShowDoneButton(boolean showDoneButton); + void setDismissCallback(TimePickerDismissCallback callback); + + int getBaseline(); + + void onConfigurationChanged(Configuration newConfig); + + Parcelable onSaveInstanceState(Parcelable superState); + void onRestoreInstanceState(Parcelable state); + + boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event); + void onPopulateAccessibilityEvent(AccessibilityEvent event); + void onInitializeAccessibilityEvent(AccessibilityEvent event); + void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info); } - private void setContentDescriptions() { - // Minute - trySetContentDescription(mMinuteSpinner, R.id.increment, - R.string.time_picker_increment_minute_button); - trySetContentDescription(mMinuteSpinner, R.id.decrement, - R.string.time_picker_decrement_minute_button); - // Hour - trySetContentDescription(mHourSpinner, R.id.increment, - R.string.time_picker_increment_hour_button); - trySetContentDescription(mHourSpinner, R.id.decrement, - R.string.time_picker_decrement_hour_button); - // AM/PM - if (mAmPmSpinner != null) { - trySetContentDescription(mAmPmSpinner, R.id.increment, - R.string.time_picker_increment_set_pm_button); - trySetContentDescription(mAmPmSpinner, R.id.decrement, - R.string.time_picker_decrement_set_am_button); - } + /** + * A callback interface for dismissing the TimePicker when included into a Dialog + * + * @hide + */ + public static interface TimePickerDismissCallback { + void dismiss(TimePicker view, boolean isCancel, int hourOfDay, int minute); } - private void trySetContentDescription(View root, int viewId, int contDescResId) { - View target = root.findViewById(viewId); - if (target != null) { - target.setContentDescription(mContext.getString(contDescResId)); + /** + * An abstract class which can be used as a start for TimePicker implementations + */ + abstract static class AbstractTimePickerDelegate implements TimePickerDelegate { + // The delegator + protected TimePicker mDelegator; + + // The context + protected Context mContext; + + // The current locale + protected Locale mCurrentLocale; + + // Callbacks + protected OnTimeChangedListener mOnTimeChangedListener; + + public AbstractTimePickerDelegate(TimePicker delegator, Context context) { + mDelegator = delegator; + mContext = context; + + // initialization based on locale + setCurrentLocale(Locale.getDefault()); } - } - private void updateInputState() { - // Make sure that if the user changes the value and the IME is active - // for one of the inputs if this widget, the IME is closed. If the user - // changed the value via the IME and there is a next input the IME will - // be shown, otherwise the user chose another means of changing the - // value and having the IME up makes no sense. - InputMethodManager inputMethodManager = InputMethodManager.peekInstance(); - if (inputMethodManager != null) { - if (inputMethodManager.isActive(mHourSpinnerInput)) { - mHourSpinnerInput.clearFocus(); - inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); - } else if (inputMethodManager.isActive(mMinuteSpinnerInput)) { - mMinuteSpinnerInput.clearFocus(); - inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); - } else if (inputMethodManager.isActive(mAmPmSpinnerInput)) { - mAmPmSpinnerInput.clearFocus(); - inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); + public void setCurrentLocale(Locale locale) { + if (locale.equals(mCurrentLocale)) { + return; } + mCurrentLocale = locale; } } } diff --git a/core/java/android/widget/TimePickerDelegate.java b/core/java/android/widget/TimePickerDelegate.java new file mode 100644 index 0000000..c9a9894 --- /dev/null +++ b/core/java/android/widget/TimePickerDelegate.java @@ -0,0 +1,1401 @@ +/* + * 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.widget; + +import android.animation.Keyframe; +import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; +import android.text.format.DateFormat; +import android.text.format.DateUtils; +import android.util.AttributeSet; +import android.util.Log; +import android.util.TypedValue; +import android.view.HapticFeedbackConstants; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; + +import com.android.internal.R; + +import java.text.DateFormatSymbols; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Locale; + +/** + * A view for selecting the time of day, in either 24 hour or AM/PM mode. + */ +class TimePickerDelegate extends TimePicker.AbstractTimePickerDelegate implements + RadialTimePickerView.OnValueSelectedListener { + + private static final String TAG = "TimePickerDelegate"; + + // Index used by RadialPickerLayout + private static final int HOUR_INDEX = 0; + private static final int MINUTE_INDEX = 1; + + // NOT a real index for the purpose of what's showing. + private static final int AMPM_INDEX = 2; + + // Also NOT a real index, just used for keyboard mode. + private static final int ENABLE_PICKER_INDEX = 3; + + private static final int AM = 0; + private static final int PM = 1; + + private static final boolean DEFAULT_ENABLED_STATE = true; + private boolean mIsEnabled = DEFAULT_ENABLED_STATE; + + private static final int HOURS_IN_HALF_DAY = 12; + + // Delay in ms before starting the pulse animation + private static final int PULSE_ANIMATOR_DELAY = 300; + + // Duration in ms of the pulse animation + private static final int PULSE_ANIMATOR_DURATION = 544; + + private static int[] TEXT_APPEARANCE_TIME_LABEL_ATTR = + new int[] { R.attr.timePickerHeaderTimeLabelTextAppearance }; + + private final View mMainView; + private TextView mHourView; + private TextView mMinuteView; + private TextView mAmPmTextView; + private RadialTimePickerView mRadialTimePickerView; + private TextView mSeparatorView; + + private ViewGroup mLayoutButtons; + + private int mHeaderSelectedColor; + private int mHeaderUnSelectedColor; + private String mAmText; + private String mPmText; + + private boolean mAllowAutoAdvance; + private int mInitialHourOfDay; + private int mInitialMinute; + private boolean mIs24HourView; + + // For hardware IME input. + private char mPlaceholderText; + private String mDoublePlaceholderText; + private String mDeletedKeyFormat; + private boolean mInKbMode; + private ArrayList<Integer> mTypedTimes = new ArrayList<Integer>(); + private Node mLegalTimesTree; + private int mAmKeyCode; + private int mPmKeyCode; + + // For showing the done button when in a Dialog + private Button mDoneButton; + private boolean mShowDoneButton; + private TimePicker.TimePickerDismissCallback mDismissCallback; + + // Accessibility strings. + private String mHourPickerDescription; + private String mSelectHours; + private String mMinutePickerDescription; + private String mSelectMinutes; + + private Calendar mTempCalendar; + + public TimePickerDelegate(TimePicker delegator, Context context, AttributeSet attrs, + int defStyleAttr, int defStyleRes) { + super(delegator, context); + + // process style attributes + final TypedArray a = mContext.obtainStyledAttributes(attrs, + R.styleable.TimePicker, defStyleAttr, defStyleRes); + + final Resources res = mContext.getResources(); + + mHourPickerDescription = res.getString(R.string.hour_picker_description); + mSelectHours = res.getString(R.string.select_hours); + mMinutePickerDescription = res.getString(R.string.minute_picker_description); + mSelectMinutes = res.getString(R.string.select_minutes); + + mHeaderSelectedColor = a.getColor(R.styleable.TimePicker_headerSelectedTextColor, + android.R.color.holo_blue_light); + + mHeaderUnSelectedColor = getUnselectedColor( + R.color.timepicker_default_text_color_holo_light); + if (mHeaderUnSelectedColor == -1) { + mHeaderUnSelectedColor = a.getColor(R.styleable.TimePicker_headerUnselectedTextColor, + R.color.timepicker_default_text_color_holo_light); + } + + final int headerBackgroundColor = a.getColor( + R.styleable.TimePicker_headerBackgroundColor, 0); + + a.recycle(); + + final int layoutResourceId = a.getResourceId( + R.styleable.TimePicker_internalLayout, R.layout.time_picker_holo); + + final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + + mMainView = inflater.inflate(layoutResourceId, null); + mDelegator.addView(mMainView); + + if (headerBackgroundColor != 0) { + RelativeLayout header = (RelativeLayout) mMainView.findViewById(R.id.time_header); + header.setBackgroundColor(headerBackgroundColor); + } + + mHourView = (TextView) mMainView.findViewById(R.id.hours); + mMinuteView = (TextView) mMainView.findViewById(R.id.minutes); + mAmPmTextView = (TextView) mMainView.findViewById(R.id.ampm_label); + mSeparatorView = (TextView) mMainView.findViewById(R.id.separator); + mRadialTimePickerView = (RadialTimePickerView) mMainView.findViewById(R.id.radial_picker); + + mLayoutButtons = (ViewGroup) mMainView.findViewById(R.id.layout_buttons); + mDoneButton = (Button) mMainView.findViewById(R.id.done_button); + + String[] amPmTexts = new DateFormatSymbols().getAmPmStrings(); + mAmText = amPmTexts[0]; + mPmText = amPmTexts[1]; + + setupListeners(); + + mAllowAutoAdvance = true; + + // Set up for keyboard mode. + mDoublePlaceholderText = res.getString(R.string.time_placeholder); + mDeletedKeyFormat = res.getString(R.string.deleted_key); + mPlaceholderText = mDoublePlaceholderText.charAt(0); + mAmKeyCode = mPmKeyCode = -1; + generateLegalTimesTree(); + + // Initialize with current time + final Calendar calendar = Calendar.getInstance(mCurrentLocale); + final int currentHour = calendar.get(Calendar.HOUR_OF_DAY); + final int currentMinute = calendar.get(Calendar.MINUTE); + initialize(currentHour, currentMinute, false /* 12h */, HOUR_INDEX, false); + } + + private int getUnselectedColor(int defColor) { + int result = -1; + final Resources.Theme theme = mContext.getTheme(); + final TypedValue outValue = new TypedValue(); + theme.resolveAttribute(R.attr.timePickerHeaderTimeLabelTextAppearance, outValue, true); + final int appearanceResId = outValue.resourceId; + TypedArray appearance = null; + if (appearanceResId != -1) { + appearance = theme.obtainStyledAttributes(appearanceResId, + com.android.internal.R.styleable.TextAppearance); + } + if (appearance != null) { + result = appearance.getColor( + com.android.internal.R.styleable.TextAppearance_textColor, defColor); + appearance.recycle(); + } + return result; + } + + private void initialize(int hourOfDay, int minute, boolean is24HourView, int index, + boolean showDoneButton) { + mInitialHourOfDay = hourOfDay; + mInitialMinute = minute; + mIs24HourView = is24HourView; + mInKbMode = false; + mShowDoneButton = showDoneButton; + updateUI(index); + } + + private void setupListeners() { + KeyboardListener keyboardListener = new KeyboardListener(); + mDelegator.setOnKeyListener(keyboardListener); + + mHourView.setOnKeyListener(keyboardListener); + mMinuteView.setOnKeyListener(keyboardListener); + mAmPmTextView.setOnKeyListener(keyboardListener); + mRadialTimePickerView.setOnValueSelectedListener(this); + mRadialTimePickerView.setOnKeyListener(keyboardListener); + + mHourView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + setCurrentItemShowing(HOUR_INDEX, true, false, true); + tryVibrate(); + } + }); + mMinuteView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + setCurrentItemShowing(MINUTE_INDEX, true, false, true); + tryVibrate(); + } + }); + mDoneButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mInKbMode && isTypedTimeFullyLegal()) { + finishKbMode(false); + } else { + tryVibrate(); + } + if (mDismissCallback != null) { + mDismissCallback.dismiss(mDelegator, false, getCurrentHour(), + getCurrentMinute()); + } + } + }); + mDoneButton.setOnKeyListener(keyboardListener); + } + + private void updateUI(int index) { + // Update RadialPicker values + updateRadialPicker(index); + // Enable or disable the AM/PM view. + updateHeaderAmPm(); + // Show or hide Done button + updateDoneButton(); + // Update Hour and Minutes + updateHeaderHour(mInitialHourOfDay, true); + // Update time separator + updateHeaderSeparator(); + // Update Minutes + updateHeaderMinute(mInitialMinute); + // Invalidate everything + mDelegator.invalidate(); + } + + private void updateRadialPicker(int index) { + mRadialTimePickerView.initialize(mInitialHourOfDay, mInitialMinute, mIs24HourView); + setCurrentItemShowing(index, false, true, true); + } + + private int computeMaxWidthOfNumbers(int max) { + TextView tempView = new TextView(mContext); + TypedArray a = mContext.obtainStyledAttributes(TEXT_APPEARANCE_TIME_LABEL_ATTR); + final int textAppearanceResId = a.getResourceId(0, 0); + tempView.setTextAppearance(mContext, (textAppearanceResId != 0) ? + textAppearanceResId : R.style.TextAppearance_Holo_TimePicker_TimeLabel); + a.recycle(); + ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + tempView.setLayoutParams(lp); + int maxWidth = 0; + for (int minutes = 0; minutes < max; minutes++) { + final String text = String.format("%02d", minutes); + tempView.setText(text); + tempView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); + maxWidth = Math.max(maxWidth, tempView.getMeasuredWidth()); + } + return maxWidth; + } + + private void updateHeaderAmPm() { + if (mIs24HourView) { + mAmPmTextView.setVisibility(View.GONE); + } else { + mAmPmTextView.setVisibility(View.VISIBLE); + final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale, + "hm"); + + boolean amPmOnLeft = bestDateTimePattern.startsWith("a"); + if (TextUtils.getLayoutDirectionFromLocale(mCurrentLocale) == + View.LAYOUT_DIRECTION_RTL) { + amPmOnLeft = !amPmOnLeft; + } + + RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) + mAmPmTextView.getLayoutParams(); + + if (amPmOnLeft) { + layoutParams.rightMargin = computeMaxWidthOfNumbers(12 /* for hours */); + layoutParams.removeRule(RelativeLayout.RIGHT_OF); + layoutParams.addRule(RelativeLayout.LEFT_OF, R.id.separator); + } else { + layoutParams.leftMargin = computeMaxWidthOfNumbers(60 /* for minutes */); + layoutParams.removeRule(RelativeLayout.LEFT_OF); + layoutParams.addRule(RelativeLayout.RIGHT_OF, R.id.separator); + } + + updateAmPmDisplay(mInitialHourOfDay < 12 ? AM : PM); + mAmPmTextView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + tryVibrate(); + int amOrPm = mRadialTimePickerView.getAmOrPm(); + if (amOrPm == AM) { + amOrPm = PM; + } else if (amOrPm == PM){ + amOrPm = AM; + } + updateAmPmDisplay(amOrPm); + mRadialTimePickerView.setAmOrPm(amOrPm); + } + }); + } + } + + private void updateDoneButton() { + mLayoutButtons.setVisibility(mShowDoneButton ? View.VISIBLE : View.GONE); + } + + /** + * Set the current hour. + */ + @Override + public void setCurrentHour(Integer currentHour) { + if (mInitialHourOfDay == currentHour) { + return; + } + mInitialHourOfDay = currentHour; + updateHeaderHour(currentHour, true /* accessibility announce */); + updateHeaderAmPm(); + mRadialTimePickerView.setCurrentHour(currentHour); + mRadialTimePickerView.setAmOrPm(mInitialHourOfDay < 12 ? AM : PM); + mDelegator.invalidate(); + onTimeChanged(); + } + + /** + * @return The current hour in the range (0-23). + */ + @Override + public Integer getCurrentHour() { + int currentHour = mRadialTimePickerView.getCurrentHour(); + if (mIs24HourView) { + return currentHour; + } else { + switch(mRadialTimePickerView.getAmOrPm()) { + case PM: + return (currentHour % HOURS_IN_HALF_DAY) + HOURS_IN_HALF_DAY; + case AM: + default: + return currentHour % HOURS_IN_HALF_DAY; + } + } + } + + /** + * Set the current minute (0-59). + */ + @Override + public void setCurrentMinute(Integer currentMinute) { + if (mInitialMinute == currentMinute) { + return; + } + mInitialMinute = currentMinute; + updateHeaderMinute(currentMinute); + mRadialTimePickerView.setCurrentMinute(currentMinute); + mDelegator.invalidate(); + onTimeChanged(); + } + + /** + * @return The current minute. + */ + @Override + public Integer getCurrentMinute() { + return mRadialTimePickerView.getCurrentMinute(); + } + + /** + * Set whether in 24 hour or AM/PM mode. + * + * @param is24HourView True = 24 hour mode. False = AM/PM. + */ + @Override + public void setIs24HourView(Boolean is24HourView) { + if (is24HourView == mIs24HourView) { + return; + } + mIs24HourView = is24HourView; + generateLegalTimesTree(); + int hour = mRadialTimePickerView.getCurrentHour(); + mInitialHourOfDay = hour; + updateHeaderHour(hour, false /* no accessibility announce */); + updateHeaderAmPm(); + updateRadialPicker(mRadialTimePickerView.getCurrentItemShowing()); + mDelegator.invalidate(); + } + + /** + * @return true if this is in 24 hour view else false. + */ + @Override + public boolean is24HourView() { + return mIs24HourView; + } + + @Override + public void setOnTimeChangedListener(TimePicker.OnTimeChangedListener callback) { + mOnTimeChangedListener = callback; + } + + @Override + public void setEnabled(boolean enabled) { + mHourView.setEnabled(enabled); + mMinuteView.setEnabled(enabled); + mAmPmTextView.setEnabled(enabled); + mRadialTimePickerView.setEnabled(enabled); + mIsEnabled = enabled; + } + + @Override + public boolean isEnabled() { + return mIsEnabled; + } + + @Override + public void setShowDoneButton(boolean showDoneButton) { + mShowDoneButton = showDoneButton; + updateDoneButton(); + } + + @Override + public void setDismissCallback(TimePicker.TimePickerDismissCallback callback) { + mDismissCallback = callback; + } + + @Override + public int getBaseline() { + // does not support baseline alignment + return -1; + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + updateUI(mRadialTimePickerView.getCurrentItemShowing()); + } + + @Override + public Parcelable onSaveInstanceState(Parcelable superState) { + return new SavedState(superState, getCurrentHour(), getCurrentMinute(), + is24HourView(), inKbMode(), getTypedTimes(), getCurrentItemShowing(), + isShowDoneButton()); + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + SavedState ss = (SavedState) state; + setInKbMode(ss.inKbMode()); + setTypedTimes(ss.getTypesTimes()); + initialize(ss.getHour(), ss.getMinute(), ss.is24HourMode(), ss.getCurrentItemShowing(), + ss.isShowDoneButton()); + mRadialTimePickerView.invalidate(); + if (mInKbMode) { + tryStartingKbMode(-1); + mHourView.invalidate(); + } + } + + @Override + public void setCurrentLocale(Locale locale) { + super.setCurrentLocale(locale); + mTempCalendar = Calendar.getInstance(locale); + } + + @Override + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + onPopulateAccessibilityEvent(event); + return true; + } + + @Override + public void onPopulateAccessibilityEvent(AccessibilityEvent event) { + int flags = DateUtils.FORMAT_SHOW_TIME; + if (mIs24HourView) { + flags |= DateUtils.FORMAT_24HOUR; + } else { + flags |= DateUtils.FORMAT_12HOUR; + } + mTempCalendar.set(Calendar.HOUR_OF_DAY, getCurrentHour()); + mTempCalendar.set(Calendar.MINUTE, getCurrentMinute()); + String selectedDate = DateUtils.formatDateTime(mContext, + mTempCalendar.getTimeInMillis(), flags); + event.getText().add(selectedDate); + } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + event.setClassName(TimePicker.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + info.setClassName(TimePicker.class.getName()); + } + + /** + * Set whether in keyboard mode or not. + * + * @param inKbMode True means in keyboard mode. + */ + private void setInKbMode(boolean inKbMode) { + mInKbMode = inKbMode; + } + + /** + * @return true if in keyboard mode + */ + private boolean inKbMode() { + return mInKbMode; + } + + private void setTypedTimes(ArrayList<Integer> typeTimes) { + mTypedTimes = typeTimes; + } + + /** + * @return an array of typed times + */ + private ArrayList<Integer> getTypedTimes() { + return mTypedTimes; + } + + /** + * @return the index of the current item showing + */ + private int getCurrentItemShowing() { + return mRadialTimePickerView.getCurrentItemShowing(); + } + + private boolean isShowDoneButton() { + return mShowDoneButton; + } + + /** + * Propagate the time change + */ + private void onTimeChanged() { + mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); + if (mOnTimeChangedListener != null) { + mOnTimeChangedListener.onTimeChanged(mDelegator, + getCurrentHour(), getCurrentMinute()); + } + } + + /** + * Used to save / restore state of time picker + */ + private static class SavedState extends View.BaseSavedState { + + private final int mHour; + private final int mMinute; + private final boolean mIs24HourMode; + private final boolean mInKbMode; + private final ArrayList<Integer> mTypedTimes; + private final int mCurrentItemShowing; + private final boolean mShowDoneButton; + + private SavedState(Parcelable superState, int hour, int minute, boolean is24HourMode, + boolean isKbMode, ArrayList<Integer> typedTimes, + int currentItemShowing, boolean showDoneButton) { + super(superState); + mHour = hour; + mMinute = minute; + mIs24HourMode = is24HourMode; + mInKbMode = isKbMode; + mTypedTimes = typedTimes; + mCurrentItemShowing = currentItemShowing; + mShowDoneButton = showDoneButton; + } + + private SavedState(Parcel in) { + super(in); + mHour = in.readInt(); + mMinute = in.readInt(); + mIs24HourMode = (in.readInt() == 1); + mInKbMode = (in.readInt() == 1); + mTypedTimes = in.readArrayList(getClass().getClassLoader()); + mCurrentItemShowing = in.readInt(); + mShowDoneButton = (in.readInt() == 1); + } + + public int getHour() { + return mHour; + } + + public int getMinute() { + return mMinute; + } + + public boolean is24HourMode() { + return mIs24HourMode; + } + + public boolean inKbMode() { + return mInKbMode; + } + + public ArrayList<Integer> getTypesTimes() { + return mTypedTimes; + } + + public int getCurrentItemShowing() { + return mCurrentItemShowing; + } + + public boolean isShowDoneButton() { + return mShowDoneButton; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(mHour); + dest.writeInt(mMinute); + dest.writeInt(mIs24HourMode ? 1 : 0); + dest.writeInt(mInKbMode ? 1 : 0); + dest.writeList(mTypedTimes); + dest.writeInt(mCurrentItemShowing); + dest.writeInt(mShowDoneButton ? 1 : 0); + } + + @SuppressWarnings({"unused", "hiding"}) + public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() { + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } + + private void tryVibrate() { + mDelegator.performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK); + } + + private void updateAmPmDisplay(int amOrPm) { + if (amOrPm == AM) { + mAmPmTextView.setText(mAmText); + mRadialTimePickerView.announceForAccessibility(mAmText); + } else if (amOrPm == PM){ + mAmPmTextView.setText(mPmText); + mRadialTimePickerView.announceForAccessibility(mPmText); + } else { + mAmPmTextView.setText(mDoublePlaceholderText); + } + } + + /** + * Called by the picker for updating the header display. + */ + @Override + public void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance) { + if (pickerIndex == HOUR_INDEX) { + updateHeaderHour(newValue, false); + String announcement = String.format("%d", newValue); + if (mAllowAutoAdvance && autoAdvance) { + setCurrentItemShowing(MINUTE_INDEX, true, true, false); + announcement += ". " + mSelectMinutes; + } else { + mRadialTimePickerView.setContentDescription( + mHourPickerDescription + ": " + newValue); + } + + mRadialTimePickerView.announceForAccessibility(announcement); + } else if (pickerIndex == MINUTE_INDEX){ + updateHeaderMinute(newValue); + mRadialTimePickerView.setContentDescription(mMinutePickerDescription + ": " + newValue); + } else if (pickerIndex == AMPM_INDEX) { + updateAmPmDisplay(newValue); + } else if (pickerIndex == ENABLE_PICKER_INDEX) { + if (!isTypedTimeFullyLegal()) { + mTypedTimes.clear(); + } + finishKbMode(true); + } + } + + private void updateHeaderHour(int value, boolean announce) { + final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale, + (mIs24HourView) ? "Hm" : "hm"); + final int lengthPattern = bestDateTimePattern.length(); + boolean hourWithTwoDigit = false; + char hourFormat = '\0'; + // Check if the returned pattern is single or double 'H', 'h', 'K', 'k'. We also save + // the hour format that we found. + for (int i = 0; i < lengthPattern; i++) { + final char c = bestDateTimePattern.charAt(i); + if (c == 'H' || c == 'h' || c == 'K' || c == 'k') { + hourFormat = c; + if (i + 1 < lengthPattern && c == bestDateTimePattern.charAt(i + 1)) { + hourWithTwoDigit = true; + } + break; + } + } + final String format; + if (hourWithTwoDigit) { + format = "%02d"; + } else { + format = "%d"; + } + if (mIs24HourView) { + // 'k' means 1-24 hour + if (hourFormat == 'k' && value == 0) { + value = 24; + } + } else { + // 'K' means 0-11 hour + value = modulo12(value, hourFormat == 'K'); + } + CharSequence text = String.format(format, value); + mHourView.setText(text); + if (announce) { + mRadialTimePickerView.announceForAccessibility(text); + } + } + + private static int modulo12(int n, boolean startWithZero) { + int value = n % 12; + if (value == 0 && !startWithZero) { + value = 12; + } + return value; + } + + /** + * The time separator is defined in the Unicode CLDR and cannot be supposed to be ":". + * + * See http://unicode.org/cldr/trac/browser/trunk/common/main + * + * We pass the correct "skeleton" depending on 12 or 24 hours view and then extract the + * separator as the character which is just after the hour marker in the returned pattern. + */ + private void updateHeaderSeparator() { + final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale, + (mIs24HourView) ? "Hm" : "hm"); + final String separatorText; + // See http://www.unicode.org/reports/tr35/tr35-dates.html for hour formats + final char[] hourFormats = {'H', 'h', 'K', 'k'}; + int hIndex = lastIndexOfAny(bestDateTimePattern, hourFormats); + if (hIndex == -1) { + // Default case + separatorText = ":"; + } else { + separatorText = Character.toString(bestDateTimePattern.charAt(hIndex + 1)); + } + mSeparatorView.setText(separatorText); + } + + static private int lastIndexOfAny(String str, char[] any) { + final int lengthAny = any.length; + if (lengthAny > 0) { + for (int i = str.length() - 1; i >= 0; i--) { + char c = str.charAt(i); + for (int j = 0; j < lengthAny; j++) { + if (c == any[j]) { + return i; + } + } + } + } + return -1; + } + + private void updateHeaderMinute(int value) { + if (value == 60) { + value = 0; + } + CharSequence text = String.format(mCurrentLocale, "%02d", value); + mRadialTimePickerView.announceForAccessibility(text); + mMinuteView.setText(text); + } + + /** + * Show either Hours or Minutes. + */ + private void setCurrentItemShowing(int index, boolean animateCircle, boolean delayLabelAnimate, + boolean announce) { + mRadialTimePickerView.setCurrentItemShowing(index, animateCircle); + + TextView labelToAnimate; + if (index == HOUR_INDEX) { + int hours = mRadialTimePickerView.getCurrentHour(); + if (!mIs24HourView) { + hours = hours % 12; + } + mRadialTimePickerView.setContentDescription(mHourPickerDescription + ": " + hours); + if (announce) { + mRadialTimePickerView.announceForAccessibility(mSelectHours); + } + labelToAnimate = mHourView; + } else { + int minutes = mRadialTimePickerView.getCurrentMinute(); + mRadialTimePickerView.setContentDescription(mMinutePickerDescription + ": " + minutes); + if (announce) { + mRadialTimePickerView.announceForAccessibility(mSelectMinutes); + } + labelToAnimate = mMinuteView; + } + + int hourColor = (index == HOUR_INDEX) ? mHeaderSelectedColor : mHeaderUnSelectedColor; + int minuteColor = (index == MINUTE_INDEX) ? mHeaderSelectedColor : mHeaderUnSelectedColor; + mHourView.setTextColor(hourColor); + mMinuteView.setTextColor(minuteColor); + + ObjectAnimator pulseAnimator = getPulseAnimator(labelToAnimate, 0.85f, 1.1f); + if (delayLabelAnimate) { + pulseAnimator.setStartDelay(PULSE_ANIMATOR_DELAY); + } + pulseAnimator.start(); + } + + /** + * For keyboard mode, processes key events. + * + * @param keyCode the pressed key. + * + * @return true if the key was successfully processed, false otherwise. + */ + private boolean processKeyUp(int keyCode) { + if (keyCode == KeyEvent.KEYCODE_ESCAPE || keyCode == KeyEvent.KEYCODE_BACK) { + if (mDismissCallback != null) { + mDismissCallback.dismiss(mDelegator, true, getCurrentHour(), getCurrentMinute()); + } + return true; + } else if (keyCode == KeyEvent.KEYCODE_TAB) { + if(mInKbMode) { + if (isTypedTimeFullyLegal()) { + finishKbMode(true); + } + return true; + } + } else if (keyCode == KeyEvent.KEYCODE_ENTER) { + if (mInKbMode) { + if (!isTypedTimeFullyLegal()) { + return true; + } + finishKbMode(false); + } + if (mOnTimeChangedListener != null) { + mOnTimeChangedListener.onTimeChanged(mDelegator, + mRadialTimePickerView.getCurrentHour(), + mRadialTimePickerView.getCurrentMinute()); + } + if (mDismissCallback != null) { + mDismissCallback.dismiss(mDelegator, false, getCurrentHour(), getCurrentMinute()); + } + return true; + } else if (keyCode == KeyEvent.KEYCODE_DEL) { + if (mInKbMode) { + if (!mTypedTimes.isEmpty()) { + int deleted = deleteLastTypedKey(); + String deletedKeyStr; + if (deleted == getAmOrPmKeyCode(AM)) { + deletedKeyStr = mAmText; + } else if (deleted == getAmOrPmKeyCode(PM)) { + deletedKeyStr = mPmText; + } else { + deletedKeyStr = String.format("%d", getValFromKeyCode(deleted)); + } + mRadialTimePickerView.announceForAccessibility( + String.format(mDeletedKeyFormat, deletedKeyStr)); + updateDisplay(true); + } + } + } else if (keyCode == KeyEvent.KEYCODE_0 || keyCode == KeyEvent.KEYCODE_1 + || keyCode == KeyEvent.KEYCODE_2 || keyCode == KeyEvent.KEYCODE_3 + || keyCode == KeyEvent.KEYCODE_4 || keyCode == KeyEvent.KEYCODE_5 + || keyCode == KeyEvent.KEYCODE_6 || keyCode == KeyEvent.KEYCODE_7 + || keyCode == KeyEvent.KEYCODE_8 || keyCode == KeyEvent.KEYCODE_9 + || (!mIs24HourView && + (keyCode == getAmOrPmKeyCode(AM) || keyCode == getAmOrPmKeyCode(PM)))) { + if (!mInKbMode) { + if (mRadialTimePickerView == null) { + // Something's wrong, because time picker should definitely not be null. + Log.e(TAG, "Unable to initiate keyboard mode, TimePicker was null."); + return true; + } + mTypedTimes.clear(); + tryStartingKbMode(keyCode); + return true; + } + // We're already in keyboard mode. + if (addKeyIfLegal(keyCode)) { + updateDisplay(false); + } + return true; + } + return false; + } + + /** + * Try to start keyboard mode with the specified key. + * + * @param keyCode The key to use as the first press. Keyboard mode will not be started if the + * key is not legal to start with. Or, pass in -1 to get into keyboard mode without a starting + * key. + */ + private void tryStartingKbMode(int keyCode) { + if (keyCode == -1 || addKeyIfLegal(keyCode)) { + mInKbMode = true; + mDoneButton.setEnabled(false); + updateDisplay(false); + mRadialTimePickerView.setInputEnabled(false); + } + } + + private boolean addKeyIfLegal(int keyCode) { + // If we're in 24hour mode, we'll need to check if the input is full. If in AM/PM mode, + // we'll need to see if AM/PM have been typed. + if ((mIs24HourView && mTypedTimes.size() == 4) || + (!mIs24HourView && isTypedTimeFullyLegal())) { + return false; + } + + mTypedTimes.add(keyCode); + if (!isTypedTimeLegalSoFar()) { + deleteLastTypedKey(); + return false; + } + + int val = getValFromKeyCode(keyCode); + mRadialTimePickerView.announceForAccessibility(String.format("%d", val)); + // Automatically fill in 0's if AM or PM was legally entered. + if (isTypedTimeFullyLegal()) { + if (!mIs24HourView && mTypedTimes.size() <= 3) { + mTypedTimes.add(mTypedTimes.size() - 1, KeyEvent.KEYCODE_0); + mTypedTimes.add(mTypedTimes.size() - 1, KeyEvent.KEYCODE_0); + } + mDoneButton.setEnabled(true); + } + + return true; + } + + /** + * Traverse the tree to see if the keys that have been typed so far are legal as is, + * or may become legal as more keys are typed (excluding backspace). + */ + private boolean isTypedTimeLegalSoFar() { + Node node = mLegalTimesTree; + for (int keyCode : mTypedTimes) { + node = node.canReach(keyCode); + if (node == null) { + return false; + } + } + return true; + } + + /** + * Check if the time that has been typed so far is completely legal, as is. + */ + private boolean isTypedTimeFullyLegal() { + if (mIs24HourView) { + // For 24-hour mode, the time is legal if the hours and minutes are each legal. Note: + // getEnteredTime() will ONLY call isTypedTimeFullyLegal() when NOT in 24hour mode. + int[] values = getEnteredTime(null); + return (values[0] >= 0 && values[1] >= 0 && values[1] < 60); + } else { + // For AM/PM mode, the time is legal if it contains an AM or PM, as those can only be + // legally added at specific times based on the tree's algorithm. + return (mTypedTimes.contains(getAmOrPmKeyCode(AM)) || + mTypedTimes.contains(getAmOrPmKeyCode(PM))); + } + } + + private int deleteLastTypedKey() { + int deleted = mTypedTimes.remove(mTypedTimes.size() - 1); + if (!isTypedTimeFullyLegal()) { + mDoneButton.setEnabled(false); + } + return deleted; + } + + /** + * Get out of keyboard mode. If there is nothing in typedTimes, revert to TimePicker's time. + * @param updateDisplays If true, update the displays with the relevant time. + */ + private void finishKbMode(boolean updateDisplays) { + mInKbMode = false; + if (!mTypedTimes.isEmpty()) { + int values[] = getEnteredTime(null); + mRadialTimePickerView.setCurrentHour(values[0]); + mRadialTimePickerView.setCurrentMinute(values[1]); + if (!mIs24HourView) { + mRadialTimePickerView.setAmOrPm(values[2]); + } + mTypedTimes.clear(); + } + if (updateDisplays) { + updateDisplay(false); + mRadialTimePickerView.setInputEnabled(true); + } + } + + /** + * Update the hours, minutes, and AM/PM displays with the typed times. If the typedTimes is + * empty, either show an empty display (filled with the placeholder text), or update from the + * timepicker's values. + * + * @param allowEmptyDisplay if true, then if the typedTimes is empty, use the placeholder text. + * Otherwise, revert to the timepicker's values. + */ + private void updateDisplay(boolean allowEmptyDisplay) { + if (!allowEmptyDisplay && mTypedTimes.isEmpty()) { + int hour = mRadialTimePickerView.getCurrentHour(); + int minute = mRadialTimePickerView.getCurrentMinute(); + updateHeaderHour(hour, true); + updateHeaderMinute(minute); + if (!mIs24HourView) { + updateAmPmDisplay(hour < 12 ? AM : PM); + } + setCurrentItemShowing(mRadialTimePickerView.getCurrentItemShowing(), true, true, true); + mDoneButton.setEnabled(true); + } else { + boolean[] enteredZeros = {false, false}; + int[] values = getEnteredTime(enteredZeros); + String hourFormat = enteredZeros[0] ? "%02d" : "%2d"; + String minuteFormat = (enteredZeros[1]) ? "%02d" : "%2d"; + String hourStr = (values[0] == -1) ? mDoublePlaceholderText : + String.format(hourFormat, values[0]).replace(' ', mPlaceholderText); + String minuteStr = (values[1] == -1) ? mDoublePlaceholderText : + String.format(minuteFormat, values[1]).replace(' ', mPlaceholderText); + mHourView.setText(hourStr); + mHourView.setTextColor(mHeaderUnSelectedColor); + mMinuteView.setText(minuteStr); + mMinuteView.setTextColor(mHeaderUnSelectedColor); + if (!mIs24HourView) { + updateAmPmDisplay(values[2]); + } + } + } + + private int getValFromKeyCode(int keyCode) { + switch (keyCode) { + case KeyEvent.KEYCODE_0: + return 0; + case KeyEvent.KEYCODE_1: + return 1; + case KeyEvent.KEYCODE_2: + return 2; + case KeyEvent.KEYCODE_3: + return 3; + case KeyEvent.KEYCODE_4: + return 4; + case KeyEvent.KEYCODE_5: + return 5; + case KeyEvent.KEYCODE_6: + return 6; + case KeyEvent.KEYCODE_7: + return 7; + case KeyEvent.KEYCODE_8: + return 8; + case KeyEvent.KEYCODE_9: + return 9; + default: + return -1; + } + } + + /** + * Get the currently-entered time, as integer values of the hours and minutes typed. + * + * @param enteredZeros A size-2 boolean array, which the caller should initialize, and which + * may then be used for the caller to know whether zeros had been explicitly entered as either + * hours of minutes. This is helpful for deciding whether to show the dashes, or actual 0's. + * + * @return A size-3 int array. The first value will be the hours, the second value will be the + * minutes, and the third will be either AM or PM. + */ + private int[] getEnteredTime(boolean[] enteredZeros) { + int amOrPm = -1; + int startIndex = 1; + if (!mIs24HourView && isTypedTimeFullyLegal()) { + int keyCode = mTypedTimes.get(mTypedTimes.size() - 1); + if (keyCode == getAmOrPmKeyCode(AM)) { + amOrPm = AM; + } else if (keyCode == getAmOrPmKeyCode(PM)){ + amOrPm = PM; + } + startIndex = 2; + } + int minute = -1; + int hour = -1; + for (int i = startIndex; i <= mTypedTimes.size(); i++) { + int val = getValFromKeyCode(mTypedTimes.get(mTypedTimes.size() - i)); + if (i == startIndex) { + minute = val; + } else if (i == startIndex+1) { + minute += 10 * val; + if (enteredZeros != null && val == 0) { + enteredZeros[1] = true; + } + } else if (i == startIndex+2) { + hour = val; + } else if (i == startIndex+3) { + hour += 10 * val; + if (enteredZeros != null && val == 0) { + enteredZeros[0] = true; + } + } + } + + int[] ret = {hour, minute, amOrPm}; + return ret; + } + + /** + * Get the keycode value for AM and PM in the current language. + */ + private int getAmOrPmKeyCode(int amOrPm) { + // Cache the codes. + if (mAmKeyCode == -1 || mPmKeyCode == -1) { + // Find the first character in the AM/PM text that is unique. + KeyCharacterMap kcm = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); + char amChar; + char pmChar; + for (int i = 0; i < Math.max(mAmText.length(), mPmText.length()); i++) { + amChar = mAmText.toLowerCase(mCurrentLocale).charAt(i); + pmChar = mPmText.toLowerCase(mCurrentLocale).charAt(i); + if (amChar != pmChar) { + KeyEvent[] events = kcm.getEvents(new char[]{amChar, pmChar}); + // There should be 4 events: a down and up for both AM and PM. + if (events != null && events.length == 4) { + mAmKeyCode = events[0].getKeyCode(); + mPmKeyCode = events[2].getKeyCode(); + } else { + Log.e(TAG, "Unable to find keycodes for AM and PM."); + } + break; + } + } + } + if (amOrPm == AM) { + return mAmKeyCode; + } else if (amOrPm == PM) { + return mPmKeyCode; + } + + return -1; + } + + /** + * Create a tree for deciding what keys can legally be typed. + */ + private void generateLegalTimesTree() { + // Create a quick cache of numbers to their keycodes. + final int k0 = KeyEvent.KEYCODE_0; + final int k1 = KeyEvent.KEYCODE_1; + final int k2 = KeyEvent.KEYCODE_2; + final int k3 = KeyEvent.KEYCODE_3; + final int k4 = KeyEvent.KEYCODE_4; + final int k5 = KeyEvent.KEYCODE_5; + final int k6 = KeyEvent.KEYCODE_6; + final int k7 = KeyEvent.KEYCODE_7; + final int k8 = KeyEvent.KEYCODE_8; + final int k9 = KeyEvent.KEYCODE_9; + + // The root of the tree doesn't contain any numbers. + mLegalTimesTree = new Node(); + if (mIs24HourView) { + // We'll be re-using these nodes, so we'll save them. + Node minuteFirstDigit = new Node(k0, k1, k2, k3, k4, k5); + Node minuteSecondDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9); + // The first digit must be followed by the second digit. + minuteFirstDigit.addChild(minuteSecondDigit); + + // The first digit may be 0-1. + Node firstDigit = new Node(k0, k1); + mLegalTimesTree.addChild(firstDigit); + + // When the first digit is 0-1, the second digit may be 0-5. + Node secondDigit = new Node(k0, k1, k2, k3, k4, k5); + firstDigit.addChild(secondDigit); + // We may now be followed by the first minute digit. E.g. 00:09, 15:58. + secondDigit.addChild(minuteFirstDigit); + + // When the first digit is 0-1, and the second digit is 0-5, the third digit may be 6-9. + Node thirdDigit = new Node(k6, k7, k8, k9); + // The time must now be finished. E.g. 0:55, 1:08. + secondDigit.addChild(thirdDigit); + + // When the first digit is 0-1, the second digit may be 6-9. + secondDigit = new Node(k6, k7, k8, k9); + firstDigit.addChild(secondDigit); + // We must now be followed by the first minute digit. E.g. 06:50, 18:20. + secondDigit.addChild(minuteFirstDigit); + + // The first digit may be 2. + firstDigit = new Node(k2); + mLegalTimesTree.addChild(firstDigit); + + // When the first digit is 2, the second digit may be 0-3. + secondDigit = new Node(k0, k1, k2, k3); + firstDigit.addChild(secondDigit); + // We must now be followed by the first minute digit. E.g. 20:50, 23:09. + secondDigit.addChild(minuteFirstDigit); + + // When the first digit is 2, the second digit may be 4-5. + secondDigit = new Node(k4, k5); + firstDigit.addChild(secondDigit); + // We must now be followd by the last minute digit. E.g. 2:40, 2:53. + secondDigit.addChild(minuteSecondDigit); + + // The first digit may be 3-9. + firstDigit = new Node(k3, k4, k5, k6, k7, k8, k9); + mLegalTimesTree.addChild(firstDigit); + // We must now be followed by the first minute digit. E.g. 3:57, 8:12. + firstDigit.addChild(minuteFirstDigit); + } else { + // We'll need to use the AM/PM node a lot. + // Set up AM and PM to respond to "a" and "p". + Node ampm = new Node(getAmOrPmKeyCode(AM), getAmOrPmKeyCode(PM)); + + // The first hour digit may be 1. + Node firstDigit = new Node(k1); + mLegalTimesTree.addChild(firstDigit); + // We'll allow quick input of on-the-hour times. E.g. 1pm. + firstDigit.addChild(ampm); + + // When the first digit is 1, the second digit may be 0-2. + Node secondDigit = new Node(k0, k1, k2); + firstDigit.addChild(secondDigit); + // Also for quick input of on-the-hour times. E.g. 10pm, 12am. + secondDigit.addChild(ampm); + + // When the first digit is 1, and the second digit is 0-2, the third digit may be 0-5. + Node thirdDigit = new Node(k0, k1, k2, k3, k4, k5); + secondDigit.addChild(thirdDigit); + // The time may be finished now. E.g. 1:02pm, 1:25am. + thirdDigit.addChild(ampm); + + // When the first digit is 1, the second digit is 0-2, and the third digit is 0-5, + // the fourth digit may be 0-9. + Node fourthDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9); + thirdDigit.addChild(fourthDigit); + // The time must be finished now. E.g. 10:49am, 12:40pm. + fourthDigit.addChild(ampm); + + // When the first digit is 1, and the second digit is 0-2, the third digit may be 6-9. + thirdDigit = new Node(k6, k7, k8, k9); + secondDigit.addChild(thirdDigit); + // The time must be finished now. E.g. 1:08am, 1:26pm. + thirdDigit.addChild(ampm); + + // When the first digit is 1, the second digit may be 3-5. + secondDigit = new Node(k3, k4, k5); + firstDigit.addChild(secondDigit); + + // When the first digit is 1, and the second digit is 3-5, the third digit may be 0-9. + thirdDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9); + secondDigit.addChild(thirdDigit); + // The time must be finished now. E.g. 1:39am, 1:50pm. + thirdDigit.addChild(ampm); + + // The hour digit may be 2-9. + firstDigit = new Node(k2, k3, k4, k5, k6, k7, k8, k9); + mLegalTimesTree.addChild(firstDigit); + // We'll allow quick input of on-the-hour-times. E.g. 2am, 5pm. + firstDigit.addChild(ampm); + + // When the first digit is 2-9, the second digit may be 0-5. + secondDigit = new Node(k0, k1, k2, k3, k4, k5); + firstDigit.addChild(secondDigit); + + // When the first digit is 2-9, and the second digit is 0-5, the third digit may be 0-9. + thirdDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9); + secondDigit.addChild(thirdDigit); + // The time must be finished now. E.g. 2:57am, 9:30pm. + thirdDigit.addChild(ampm); + } + } + + /** + * Simple node class to be used for traversal to check for legal times. + * mLegalKeys represents the keys that can be typed to get to the node. + * mChildren are the children that can be reached from this node. + */ + private class Node { + private int[] mLegalKeys; + private ArrayList<Node> mChildren; + + public Node(int... legalKeys) { + mLegalKeys = legalKeys; + mChildren = new ArrayList<Node>(); + } + + public void addChild(Node child) { + mChildren.add(child); + } + + public boolean containsKey(int key) { + for (int i = 0; i < mLegalKeys.length; i++) { + if (mLegalKeys[i] == key) { + return true; + } + } + return false; + } + + public Node canReach(int key) { + if (mChildren == null) { + return null; + } + for (Node child : mChildren) { + if (child.containsKey(key)) { + return child; + } + } + return null; + } + } + + private class KeyboardListener implements View.OnKeyListener { + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_UP) { + return processKeyUp(keyCode); + } + return false; + } + } + + /** + * Render an animator to pulsate a view in place. + * + * @param labelToAnimate the view to pulsate. + * @return The animator object. Use .start() to begin. + */ + private static ObjectAnimator getPulseAnimator(View labelToAnimate, float decreaseRatio, + float increaseRatio) { + final Keyframe k0 = Keyframe.ofFloat(0f, 1f); + final Keyframe k1 = Keyframe.ofFloat(0.275f, decreaseRatio); + final Keyframe k2 = Keyframe.ofFloat(0.69f, increaseRatio); + final Keyframe k3 = Keyframe.ofFloat(1f, 1f); + + PropertyValuesHolder scaleX = PropertyValuesHolder.ofKeyframe("scaleX", k0, k1, k2, k3); + PropertyValuesHolder scaleY = PropertyValuesHolder.ofKeyframe("scaleY", k0, k1, k2, k3); + ObjectAnimator pulseAnimator = + ObjectAnimator.ofPropertyValuesHolder(labelToAnimate, scaleX, scaleY); + pulseAnimator.setDuration(PULSE_ANIMATOR_DURATION); + + return pulseAnimator; + } +} diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java index e38dfa7..bf5e49b 100644 --- a/core/java/android/widget/Toast.java +++ b/core/java/android/widget/Toast.java @@ -16,6 +16,7 @@ package android.widget; +import android.annotation.IntDef; import android.app.INotificationManager; import android.app.ITransientNotification; import android.content.Context; @@ -29,11 +30,13 @@ import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; -import android.view.View.OnClickListener; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * A toast is a view containing a quick little message for the user. The toast class * helps you create and show those. @@ -61,6 +64,11 @@ public class Toast { static final String TAG = "Toast"; static final boolean localLOGV = false; + /** @hide */ + @IntDef({LENGTH_SHORT, LENGTH_LONG}) + @Retention(RetentionPolicy.SOURCE) + public @interface Duration {} + /** * Show the view or text notification for a short period of time. This time * could be user-definable. This is the default. @@ -152,7 +160,7 @@ public class Toast { * @see #LENGTH_SHORT * @see #LENGTH_LONG */ - public void setDuration(int duration) { + public void setDuration(@Duration int duration) { mDuration = duration; } @@ -160,6 +168,7 @@ public class Toast { * Return the duration. * @see #setDuration */ + @Duration public int getDuration() { return mDuration; } @@ -237,7 +246,7 @@ public class Toast { * {@link #LENGTH_LONG} * */ - public static Toast makeText(Context context, CharSequence text, int duration) { + public static Toast makeText(Context context, CharSequence text, @Duration int duration) { Toast result = new Toast(context); LayoutInflater inflate = (LayoutInflater) @@ -263,7 +272,7 @@ public class Toast { * * @throws Resources.NotFoundException if the resource can't be found. */ - public static Toast makeText(Context context, int resId, int duration) + public static Toast makeText(Context context, int resId, @Duration int duration) throws Resources.NotFoundException { return makeText(context, context.getResources().getText(resId), duration); } diff --git a/core/java/android/widget/ToggleButton.java b/core/java/android/widget/ToggleButton.java index cedc777..28519d1 100644 --- a/core/java/android/widget/ToggleButton.java +++ b/core/java/android/widget/ToggleButton.java @@ -16,7 +16,6 @@ package android.widget; - import android.content.Context; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; @@ -25,8 +24,6 @@ import android.util.AttributeSet; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; -import com.android.internal.R; - /** * Displays checked/unchecked states as a button * with a "light" indicator and by default accompanied with the text "ON" or "OFF". @@ -46,13 +43,12 @@ public class ToggleButton extends CompoundButton { private static final int NO_ALPHA = 0xFF; private float mDisabledAlpha; - - public ToggleButton(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - TypedArray a = - context.obtainStyledAttributes( - attrs, com.android.internal.R.styleable.ToggleButton, defStyle, 0); + + public ToggleButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + final TypedArray a = context.obtainStyledAttributes( + attrs, com.android.internal.R.styleable.ToggleButton, defStyleAttr, defStyleRes); mTextOn = a.getText(com.android.internal.R.styleable.ToggleButton_textOn); mTextOff = a.getText(com.android.internal.R.styleable.ToggleButton_textOff); mDisabledAlpha = a.getFloat(com.android.internal.R.styleable.ToggleButton_disabledAlpha, 0.5f); @@ -60,6 +56,10 @@ public class ToggleButton extends CompoundButton { a.recycle(); } + public ToggleButton(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + public ToggleButton(Context context, AttributeSet attrs) { this(context, attrs, com.android.internal.R.attr.buttonStyleToggle); } diff --git a/core/java/android/widget/TwoLineListItem.java b/core/java/android/widget/TwoLineListItem.java index f7e5266..5606c60 100644 --- a/core/java/android/widget/TwoLineListItem.java +++ b/core/java/android/widget/TwoLineListItem.java @@ -56,11 +56,15 @@ public class TwoLineListItem extends RelativeLayout { this(context, attrs, 0); } - public TwoLineListItem(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public TwoLineListItem(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public TwoLineListItem(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); - TypedArray a = context.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.TwoLineListItem, defStyle, 0); + final TypedArray a = context.obtainStyledAttributes( + attrs, com.android.internal.R.styleable.TwoLineListItem, defStyleAttr, defStyleRes); a.recycle(); } diff --git a/core/java/android/widget/VideoView.java b/core/java/android/widget/VideoView.java index d57b739..f23c64f 100644 --- a/core/java/android/widget/VideoView.java +++ b/core/java/android/widget/VideoView.java @@ -19,7 +19,6 @@ package android.widget; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; -import android.content.Intent; import android.content.res.Resources; import android.graphics.Canvas; import android.media.AudioManager; @@ -127,8 +126,12 @@ public class VideoView extends SurfaceView initVideoView(); } - public VideoView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public VideoView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public VideoView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); initVideoView(); } @@ -297,11 +300,8 @@ public class VideoView extends SurfaceView // not ready for playback just yet, will try again later return; } - // Tell the music playback service to pause - // TODO: these constants need to be published somewhere in the framework. - Intent i = new Intent("com.android.music.musicservicecommand"); - i.putExtra("command", "pause"); - mContext.sendBroadcast(i); + AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); // we shouldn't clear the target state, because somebody might have // called start() previously diff --git a/core/java/android/widget/ZoomButton.java b/core/java/android/widget/ZoomButton.java index af17c94..715e868 100644 --- a/core/java/android/widget/ZoomButton.java +++ b/core/java/android/widget/ZoomButton.java @@ -49,8 +49,12 @@ public class ZoomButton extends ImageButton implements OnLongClickListener { this(context, attrs, 0); } - public ZoomButton(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public ZoomButton(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public ZoomButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); mHandler = new Handler(); setOnLongClickListener(this); } diff --git a/core/java/android/widget/ZoomButtonsController.java b/core/java/android/widget/ZoomButtonsController.java index 50c803b..f7e9648 100644 --- a/core/java/android/widget/ZoomButtonsController.java +++ b/core/java/android/widget/ZoomButtonsController.java @@ -32,7 +32,6 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; -import android.view.ViewParent; import android.view.ViewRootImpl; import android.view.WindowManager; import android.view.View.OnClickListener; diff --git a/core/java/com/android/internal/app/ActionBarImpl.java b/core/java/com/android/internal/app/ActionBarImpl.java index 066d6c3..cc51a8b 100644 --- a/core/java/com/android/internal/app/ActionBarImpl.java +++ b/core/java/com/android/internal/app/ActionBarImpl.java @@ -42,7 +42,6 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.os.Handler; -import android.util.Log; import android.util.TypedValue; import android.view.ActionMode; import android.view.ContextThemeWrapper; @@ -51,7 +50,6 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; -import android.view.ViewGroup; import android.view.Window; import android.view.accessibility.AccessibilityEvent; import android.view.animation.AnimationUtils; @@ -59,6 +57,7 @@ import android.widget.SpinnerAdapter; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Map; /** * ActionBarImpl is the ActionBar implementation used @@ -357,6 +356,10 @@ public class ActionBarImpl extends ActionBar { setSubtitle(mContext.getString(resId)); } + public void captureSharedElements(Map<String, View> sharedElements) { + mContainerView.findSharedElements(sharedElements); + } + public void setSelectedNavigationItem(int position) { switch (mActionView.getNavigationMode()) { case NAVIGATION_MODE_TABS: @@ -1083,7 +1086,7 @@ public class ActionBarImpl extends ActionBar { @Override public Tab setIcon(int resId) { - return setIcon(mContext.getResources().getDrawable(resId)); + return setIcon(mContext.getDrawable(resId)); } @Override diff --git a/core/java/com/android/internal/app/AlertController.java b/core/java/com/android/internal/app/AlertController.java index fe532b0..7640749 100644 --- a/core/java/com/android/internal/app/AlertController.java +++ b/core/java/com/android/internal/app/AlertController.java @@ -70,6 +70,8 @@ public class AlertController { private View mView; + private int mViewLayoutResId; + private int mViewSpacingLeft; private int mViewSpacingTop; @@ -126,16 +128,20 @@ public class AlertController { private Handler mHandler; - View.OnClickListener mButtonHandler = new View.OnClickListener() { + private final View.OnClickListener mButtonHandler = new View.OnClickListener() { + @Override public void onClick(View v) { - Message m = null; + final Message m; if (v == mButtonPositive && mButtonPositiveMessage != null) { m = Message.obtain(mButtonPositiveMessage); } else if (v == mButtonNegative && mButtonNegativeMessage != null) { m = Message.obtain(mButtonNegativeMessage); } else if (v == mButtonNeutral && mButtonNeutralMessage != null) { m = Message.obtain(mButtonNeutralMessage); + } else { + m = null; } + if (m != null) { m.sendToTarget(); } @@ -232,11 +238,6 @@ public class AlertController { public void installContent() { /* We use a custom title so never request a window title */ mWindow.requestFeature(Window.FEATURE_NO_TITLE); - - if (mView == null || !canTextInput(mView)) { - mWindow.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, - WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); - } mWindow.setContentView(mAlertDialogLayout); setupView(); } @@ -263,10 +264,20 @@ public class AlertController { } /** + * Set the view resource to display in the dialog. + */ + public void setView(int layoutResId) { + mView = null; + mViewLayoutResId = layoutResId; + mViewSpacingSpecified = false; + } + + /** * Set the view to display in the dialog. */ public void setView(View view) { mView = view; + mViewLayoutResId = 0; mViewSpacingSpecified = false; } @@ -276,6 +287,7 @@ public class AlertController { public void setView(View view, int viewSpacingLeft, int viewSpacingTop, int viewSpacingRight, int viewSpacingBottom) { mView = view; + mViewLayoutResId = 0; mViewSpacingSpecified = true; mViewSpacingLeft = viewSpacingLeft; mViewSpacingTop = viewSpacingTop; @@ -406,28 +418,44 @@ public class AlertController { mWindow.setCloseOnTouchOutsideIfNotSet(true); } - FrameLayout customPanel = null; + final FrameLayout customPanel = (FrameLayout) mWindow.findViewById(R.id.customPanel); + final View customView; if (mView != null) { - customPanel = (FrameLayout) mWindow.findViewById(R.id.customPanel); - FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.custom); - custom.addView(mView, new LayoutParams(MATCH_PARENT, MATCH_PARENT)); + customView = mView; + } else if (mViewLayoutResId != 0) { + final LayoutInflater inflater = LayoutInflater.from(mContext); + customView = inflater.inflate(mViewLayoutResId, customPanel, false); + } else { + customView = null; + } + + final boolean hasCustomView = customView != null; + if (!hasCustomView || !canTextInput(customView)) { + mWindow.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, + WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); + } + + if (hasCustomView) { + final FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.custom); + custom.addView(customView, new LayoutParams(MATCH_PARENT, MATCH_PARENT)); + if (mViewSpacingSpecified) { - custom.setPadding(mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight, - mViewSpacingBottom); + custom.setPadding( + mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight, mViewSpacingBottom); } + if (mListView != null) { ((LinearLayout.LayoutParams) customPanel.getLayoutParams()).weight = 0; } } else { - mWindow.findViewById(R.id.customPanel).setVisibility(View.GONE); + customPanel.setVisibility(View.GONE); } - - /* Only display the divider if we have a title and a - * custom view or a message. - */ + + // Only display the divider if we have a title and a custom view or a + // message. if (hasTitle) { - View divider = null; - if (mMessage != null || mView != null || mListView != null) { + final View divider; + if (mMessage != null || customView != null || mListView != null) { divider = mWindow.findViewById(R.id.titleDivider); } else { divider = mWindow.findViewById(R.id.titleDividerTop); @@ -437,8 +465,9 @@ public class AlertController { divider.setVisibility(View.VISIBLE); } } - - setBackground(topPanel, contentPanel, customPanel, hasButtons, a, hasTitle, buttonPanel); + + setBackground(a, topPanel, contentPanel, customPanel, buttonPanel, hasTitle, hasCustomView, + hasButtons); a.recycle(); } @@ -596,76 +625,64 @@ public class AlertController { } } - private void setBackground(LinearLayout topPanel, LinearLayout contentPanel, - View customPanel, boolean hasButtons, TypedArray a, boolean hasTitle, - View buttonPanel) { - - /* Get all the different background required */ - int fullDark = a.getResourceId( - R.styleable.AlertDialog_fullDark, R.drawable.popup_full_dark); - int topDark = a.getResourceId( - R.styleable.AlertDialog_topDark, R.drawable.popup_top_dark); - int centerDark = a.getResourceId( - R.styleable.AlertDialog_centerDark, R.drawable.popup_center_dark); - int bottomDark = a.getResourceId( - R.styleable.AlertDialog_bottomDark, R.drawable.popup_bottom_dark); - int fullBright = a.getResourceId( - R.styleable.AlertDialog_fullBright, R.drawable.popup_full_bright); - int topBright = a.getResourceId( + private void setBackground(TypedArray a, View topPanel, View contentPanel, View customPanel, + View buttonPanel, boolean hasTitle, boolean hasCustomView, boolean hasButtons) { + final int topBright = a.getResourceId( R.styleable.AlertDialog_topBright, R.drawable.popup_top_bright); - int centerBright = a.getResourceId( + final int topDark = a.getResourceId( + R.styleable.AlertDialog_topDark, R.drawable.popup_top_dark); + final int centerBright = a.getResourceId( R.styleable.AlertDialog_centerBright, R.drawable.popup_center_bright); - int bottomBright = a.getResourceId( - R.styleable.AlertDialog_bottomBright, R.drawable.popup_bottom_bright); - int bottomMedium = a.getResourceId( - R.styleable.AlertDialog_bottomMedium, R.drawable.popup_bottom_medium); - - /* - * We now set the background of all of the sections of the alert. + final int centerDark = a.getResourceId( + R.styleable.AlertDialog_centerDark, R.drawable.popup_center_dark); + + /* We now set the background of all of the sections of the alert. * First collect together each section that is being displayed along * with whether it is on a light or dark background, then run through * them setting their backgrounds. This is complicated because we need * to correctly use the full, top, middle, and bottom graphics depending * on how many views they are and where they appear. */ - - View[] views = new View[4]; - boolean[] light = new boolean[4]; + + final View[] views = new View[4]; + final boolean[] light = new boolean[4]; View lastView = null; boolean lastLight = false; - + int pos = 0; if (hasTitle) { views[pos] = topPanel; light[pos] = false; pos++; } - + /* The contentPanel displays either a custom text message or * a ListView. If it's text we should use the dark background * for ListView we should use the light background. If neither * are there the contentPanel will be hidden so set it as null. */ - views[pos] = (contentPanel.getVisibility() == View.GONE) - ? null : contentPanel; + views[pos] = contentPanel.getVisibility() == View.GONE ? null : contentPanel; light[pos] = mListView != null; pos++; - if (customPanel != null) { + + if (hasCustomView) { views[pos] = customPanel; light[pos] = mForceInverseBackground; pos++; } + if (hasButtons) { views[pos] = buttonPanel; light[pos] = true; } - + boolean setView = false; - for (pos=0; pos<views.length; pos++) { - View v = views[pos]; + for (pos = 0; pos < views.length; pos++) { + final View v = views[pos]; if (v == null) { continue; } + if (lastView != null) { if (!setView) { lastView.setBackgroundResource(lastLight ? topBright : topDark); @@ -674,23 +691,34 @@ public class AlertController { } setView = true; } + lastView = v; lastLight = light[pos]; } - + if (lastView != null) { if (setView) { - - /* ListViews will use the Bright background but buttons use - * the Medium background. - */ + final int bottomBright = a.getResourceId( + R.styleable.AlertDialog_bottomBright, R.drawable.popup_bottom_bright); + final int bottomMedium = a.getResourceId( + R.styleable.AlertDialog_bottomMedium, R.drawable.popup_bottom_medium); + final int bottomDark = a.getResourceId( + R.styleable.AlertDialog_bottomDark, R.drawable.popup_bottom_dark); + + // ListViews will use the Bright background, but buttons use the + // Medium background. lastView.setBackgroundResource( lastLight ? (hasButtons ? bottomMedium : bottomBright) : bottomDark); } else { + final int fullBright = a.getResourceId( + R.styleable.AlertDialog_fullBright, R.drawable.popup_full_bright); + final int fullDark = a.getResourceId( + R.styleable.AlertDialog_fullDark, R.drawable.popup_full_dark); + lastView.setBackgroundResource(lastLight ? fullBright : fullDark); } } - + /* TODO: uncomment section below. The logic for this should be if * it's a Contextual menu being displayed AND only a Cancel button * is shown then do this. @@ -715,12 +743,14 @@ public class AlertController { mListView.addFooterView(buttonPanel); */ // } - - if ((mListView != null) && (mAdapter != null)) { - mListView.setAdapter(mAdapter); - if (mCheckedItem > -1) { - mListView.setItemChecked(mCheckedItem, true); - mListView.setSelection(mCheckedItem); + + final ListView listView = mListView; + if (listView != null && mAdapter != null) { + listView.setAdapter(mAdapter); + final int checkedItem = mCheckedItem; + if (checkedItem > -1) { + listView.setItemChecked(checkedItem, true); + listView.setSelection(checkedItem); } } } @@ -736,8 +766,13 @@ public class AlertController { super(context, attrs); } - public RecycleListView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public RecycleListView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public RecycleListView( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); } @Override @@ -769,6 +804,7 @@ public class AlertController { public CharSequence[] mItems; public ListAdapter mAdapter; public DialogInterface.OnClickListener mOnClickListener; + public int mViewLayoutResId; public View mView; public int mViewSpacingLeft; public int mViewSpacingTop; @@ -854,8 +890,10 @@ public class AlertController { } else { dialog.setView(mView); } + } else if (mViewLayoutResId != 0) { + dialog.setView(mViewLayoutResId); } - + /* dialog.setCancelable(mCancelable); dialog.setOnCancelListener(mOnCancelListener); @@ -937,7 +975,8 @@ public class AlertController { if (mOnClickListener != null) { listView.setOnItemClickListener(new OnItemClickListener() { - public void onItemClick(AdapterView parent, View v, int position, long id) { + @Override + public void onItemClick(AdapterView<?> parent, View v, int position, long id) { mOnClickListener.onClick(dialog.mDialogInterface, position); if (!mIsSingleChoice) { dialog.mDialogInterface.dismiss(); @@ -946,7 +985,8 @@ public class AlertController { }); } else if (mOnCheckboxClickListener != null) { listView.setOnItemClickListener(new OnItemClickListener() { - public void onItemClick(AdapterView parent, View v, int position, long id) { + @Override + public void onItemClick(AdapterView<?> parent, View v, int position, long id) { if (mCheckedItems != null) { mCheckedItems[position] = listView.isItemChecked(position); } diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 70f90d3..1eda373 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -27,8 +27,9 @@ public class ChooserActivity extends ResolverActivity { Intent intent = getIntent(); Parcelable targetParcelable = intent.getParcelableExtra(Intent.EXTRA_INTENT); if (!(targetParcelable instanceof Intent)) { - Log.w("ChooseActivity", "Target is not an intent: " + targetParcelable); + Log.w("ChooserActivity", "Target is not an intent: " + targetParcelable); finish(); + super.onCreate(null); return; } Intent target = (Intent)targetParcelable; @@ -42,9 +43,10 @@ public class ChooserActivity extends ResolverActivity { initialIntents = new Intent[pa.length]; for (int i=0; i<pa.length; i++) { if (!(pa[i] instanceof Intent)) { - Log.w("ChooseActivity", "Initial intent #" + i + Log.w("ChooserActivity", "Initial intent #" + i + " not an Intent: " + pa[i]); finish(); + super.onCreate(null); return; } initialIntents[i] = (Intent)pa[i]; diff --git a/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java b/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java index 3d46cdd..83ad9dc 100644 --- a/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java +++ b/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java @@ -32,7 +32,6 @@ import android.util.TypedValue; import android.view.View; import android.view.Window; import android.view.View.OnClickListener; -import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl index 43c4b49..5ba5c57 100644 --- a/core/java/com/android/internal/app/IBatteryStats.aidl +++ b/core/java/com/android/internal/app/IBatteryStats.aidl @@ -22,19 +22,28 @@ import android.os.WorkSource; import android.telephony.SignalStrength; interface IBatteryStats { - byte[] getStatistics(); - void noteStartWakelock(int uid, int pid, String name, int type); - void noteStopWakelock(int uid, int pid, String name, int type); - - /* DO NOT CHANGE the position of noteStartSensor without updating - SensorService.cpp */ + // These first methods are also called by native code, so must + // be kept in sync with frameworks/native/include/binder/IBatteryStats.h void noteStartSensor(int uid, int sensor); - - /* DO NOT CHANGE the position of noteStopSensor without updating - SensorService.cpp */ void noteStopSensor(int uid, int sensor); - void noteStartWakelockFromSource(in WorkSource ws, int pid, String name, int type); + // Remaining methods are only used in Java. + byte[] getStatistics(); + + void addIsolatedUid(int isolatedUid, int appUid); + void removeIsolatedUid(int isolatedUid, int appUid); + + void noteEvent(int code, String name, int uid); + + void noteStartWakelock(int uid, int pid, String name, String historyName, + int type, boolean unimportantForLogging); + void noteStopWakelock(int uid, int pid, String name, int type); + + void noteStartWakelockFromSource(in WorkSource ws, int pid, String name, String historyName, + int type, boolean unimportantForLogging); + void noteChangeWakelockFromSource(in WorkSource ws, int pid, String name, int type, + in WorkSource newWs, int newPid, String newName, + String newHistoryName, int newType, boolean newUnimportantForLogging); void noteStopWakelockFromSource(in WorkSource ws, int pid, String name, int type); void noteVibratorOn(int uid, long durationMillis); @@ -46,6 +55,7 @@ interface IBatteryStats { void noteScreenOff(); void noteInputEvent(); void noteUserActivity(int uid, int event); + void noteDataConnectionActive(int type, boolean active); void notePhoneOn(); void notePhoneOff(); void notePhoneSignalStrength(in SignalStrength signalStrength); @@ -56,8 +66,10 @@ interface IBatteryStats { void noteWifiRunning(in WorkSource ws); void noteWifiRunningChanged(in WorkSource oldWs, in WorkSource newWs); void noteWifiStopped(in WorkSource ws); + void noteWifiState(int wifiState, String accessPoint); void noteBluetoothOn(); void noteBluetoothOff(); + void noteBluetoothState(int bluetoothState); void noteFullWifiLockAcquired(int uid); void noteFullWifiLockReleased(int uid); void noteWifiScanStarted(int uid); diff --git a/core/java/com/android/internal/app/LocalePicker.java b/core/java/com/android/internal/app/LocalePicker.java index 043964f..ec2d654 100644 --- a/core/java/com/android/internal/app/LocalePicker.java +++ b/core/java/com/android/internal/app/LocalePicker.java @@ -117,7 +117,7 @@ public class LocalePicker extends ListFragment { /** - TODO: Enable when zz_ZY Pseudolocale is complete * if (!localeList.contains("zz_ZY")) { * localeList.add("zz_ZY"); - * } + * } */ } String[] locales = new String[localeList.size()]; diff --git a/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java index ae362af..237feed 100644 --- a/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java +++ b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java @@ -21,7 +21,6 @@ import android.app.DialogFragment; import android.content.Context; import android.os.Bundle; import android.view.View; -import android.view.View.OnClickListener; /** * Media route chooser dialog fragment. diff --git a/core/java/com/android/internal/app/MediaRouteControllerDialog.java b/core/java/com/android/internal/app/MediaRouteControllerDialog.java index 8fc99c7..b0e0373 100644 --- a/core/java/com/android/internal/app/MediaRouteControllerDialog.java +++ b/core/java/com/android/internal/app/MediaRouteControllerDialog.java @@ -256,13 +256,13 @@ public class MediaRouteControllerDialog extends Dialog { private Drawable getIconDrawable() { if (mRoute.isConnecting()) { if (mMediaRouteConnectingDrawable == null) { - mMediaRouteConnectingDrawable = getContext().getResources().getDrawable( + mMediaRouteConnectingDrawable = getContext().getDrawable( R.drawable.ic_media_route_connecting_holo_dark); } return mMediaRouteConnectingDrawable; } else { if (mMediaRouteOnDrawable == null) { - mMediaRouteOnDrawable = getContext().getResources().getDrawable( + mMediaRouteOnDrawable = getContext().getDrawable( R.drawable.ic_media_route_on_holo_dark); } return mMediaRouteOnDrawable; diff --git a/core/java/com/android/internal/app/PlatLogoActivity.java b/core/java/com/android/internal/app/PlatLogoActivity.java index 40a705c..8cdaf91 100644 --- a/core/java/com/android/internal/app/PlatLogoActivity.java +++ b/core/java/com/android/internal/app/PlatLogoActivity.java @@ -18,7 +18,6 @@ package com.android.internal.app; import android.app.Activity; import android.content.ActivityNotFoundException; -import android.content.Context; import android.content.Intent; import android.graphics.Typeface; import android.provider.Settings; @@ -26,19 +25,15 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.text.method.AllCapsTransformationMethod; -import android.text.method.TransformationMethod; import android.util.DisplayMetrics; import android.view.Gravity; import android.view.View; -import android.view.ViewGroup; import android.view.animation.AccelerateInterpolator; import android.view.animation.AnticipateOvershootInterpolator; import android.view.animation.DecelerateInterpolator; import android.widget.FrameLayout; import android.widget.ImageView; -import android.widget.LinearLayout; import android.widget.TextView; -import android.widget.Toast; public class PlatLogoActivity extends Activity { FrameLayout mContent; diff --git a/core/java/com/android/internal/app/ProcessStats.java b/core/java/com/android/internal/app/ProcessStats.java index a87992a..b1535e3 100644 --- a/core/java/com/android/internal/app/ProcessStats.java +++ b/core/java/com/android/internal/app/ProcessStats.java @@ -171,7 +171,7 @@ public final class ProcessStats implements Parcelable { static final String CSV_SEP = "\t"; // Current version of the parcel format. - private static final int PARCEL_VERSION = 13; + private static final int PARCEL_VERSION = 14; // In-memory Parcel magic number, used to detect attempts to unmarshall bad data private static final int MAGIC = 0x50535453; @@ -189,7 +189,8 @@ public final class ProcessStats implements Parcelable { public String mTimePeriodStartClockStr; public int mFlags; - public final ProcessMap<PackageState> mPackages = new ProcessMap<PackageState>(); + public final ProcessMap<SparseArray<PackageState>> mPackages + = new ProcessMap<SparseArray<PackageState>>(); public final ProcessMap<ProcessState> mProcesses = new ProcessMap<ProcessState>(); public final long[] mMemFactorDurations = new long[ADJ_COUNT]; @@ -227,40 +228,45 @@ public final class ProcessStats implements Parcelable { } public void add(ProcessStats other) { - ArrayMap<String, SparseArray<PackageState>> pkgMap = other.mPackages.getMap(); + ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = other.mPackages.getMap(); for (int ip=0; ip<pkgMap.size(); ip++) { - String pkgName = pkgMap.keyAt(ip); - SparseArray<PackageState> uids = pkgMap.valueAt(ip); + final String pkgName = pkgMap.keyAt(ip); + final SparseArray<SparseArray<PackageState>> uids = pkgMap.valueAt(ip); for (int iu=0; iu<uids.size(); iu++) { - int uid = uids.keyAt(iu); - PackageState otherState = uids.valueAt(iu); - final int NPROCS = otherState.mProcesses.size(); - final int NSRVS = otherState.mServices.size(); - for (int iproc=0; iproc<NPROCS; iproc++) { - ProcessState otherProc = otherState.mProcesses.valueAt(iproc); - if (otherProc.mCommonProcess != otherProc) { - if (DEBUG) Slog.d(TAG, "Adding pkg " + pkgName + " uid " + uid - + " proc " + otherProc.mName); - ProcessState thisProc = getProcessStateLocked(pkgName, uid, - otherProc.mName); - if (thisProc.mCommonProcess == thisProc) { - if (DEBUG) Slog.d(TAG, "Existing process is single-package, splitting"); - thisProc.mMultiPackage = true; - long now = SystemClock.uptimeMillis(); - final PackageState pkgState = getPackageStateLocked(pkgName, uid); - thisProc = thisProc.clone(thisProc.mPackage, now); - pkgState.mProcesses.put(thisProc.mName, thisProc); + final int uid = uids.keyAt(iu); + final SparseArray<PackageState> versions = uids.valueAt(iu); + for (int iv=0; iv<versions.size(); iv++) { + final int vers = versions.keyAt(iv); + final PackageState otherState = versions.valueAt(iv); + final int NPROCS = otherState.mProcesses.size(); + final int NSRVS = otherState.mServices.size(); + for (int iproc=0; iproc<NPROCS; iproc++) { + ProcessState otherProc = otherState.mProcesses.valueAt(iproc); + if (otherProc.mCommonProcess != otherProc) { + if (DEBUG) Slog.d(TAG, "Adding pkg " + pkgName + " uid " + uid + + " vers " + vers + " proc " + otherProc.mName); + ProcessState thisProc = getProcessStateLocked(pkgName, uid, vers, + otherProc.mName); + if (thisProc.mCommonProcess == thisProc) { + if (DEBUG) Slog.d(TAG, "Existing process is single-package, splitting"); + thisProc.mMultiPackage = true; + long now = SystemClock.uptimeMillis(); + final PackageState pkgState = getPackageStateLocked(pkgName, uid, + vers); + thisProc = thisProc.clone(thisProc.mPackage, now); + pkgState.mProcesses.put(thisProc.mName, thisProc); + } + thisProc.add(otherProc); } - thisProc.add(otherProc); } - } - for (int isvc=0; isvc<NSRVS; isvc++) { - ServiceState otherSvc = otherState.mServices.valueAt(isvc); - if (DEBUG) Slog.d(TAG, "Adding pkg " + pkgName + " uid " + uid - + " service " + otherSvc.mName); - ServiceState thisSvc = getServiceStateLocked(pkgName, uid, - otherSvc.mProcessName, otherSvc.mName); - thisSvc.add(otherSvc); + for (int isvc=0; isvc<NSRVS; isvc++) { + ServiceState otherSvc = otherState.mServices.valueAt(isvc); + if (DEBUG) Slog.d(TAG, "Adding pkg " + pkgName + " uid " + uid + + " service " + otherSvc.mName); + ServiceState thisSvc = getServiceStateLocked(pkgName, uid, vers, + otherSvc.mProcessName, otherSvc.mName); + thisSvc.add(otherSvc); + } } } } @@ -275,9 +281,11 @@ public final class ProcessStats implements Parcelable { if (DEBUG) Slog.d(TAG, "Adding uid " + uid + " proc " + otherProc.mName); if (thisProc == null) { if (DEBUG) Slog.d(TAG, "Creating new process!"); - thisProc = new ProcessState(this, otherProc.mPackage, uid, otherProc.mName); + thisProc = new ProcessState(this, otherProc.mPackage, uid, otherProc.mVersion, + otherProc.mName); mProcesses.put(otherProc.mName, uid, thisProc); - PackageState thisState = getPackageStateLocked(otherProc.mPackage, uid); + PackageState thisState = getPackageStateLocked(otherProc.mPackage, uid, + otherProc.mVersion); if (!thisState.mProcesses.containsKey(otherProc.mName)) { thisState.mProcesses.put(otherProc.mName, thisProc); } @@ -440,7 +448,7 @@ public final class ProcessStats implements Parcelable { } static void dumpServiceTimeCheckin(PrintWriter pw, String label, String packageName, - int uid, String serviceName, ServiceState svc, int serviceType, int opCount, + int uid, int vers, String serviceName, ServiceState svc, int serviceType, int opCount, int curState, long curStartTime, long now) { if (opCount <= 0) { return; @@ -451,6 +459,8 @@ public final class ProcessStats implements Parcelable { pw.print(","); pw.print(uid); pw.print(","); + pw.print(vers); + pw.print(","); pw.print(serviceName); pw.print(","); pw.print(opCount); @@ -775,7 +785,7 @@ public final class ProcessStats implements Parcelable { static void dumpProcessSummaryLocked(PrintWriter pw, String prefix, ArrayList<ProcessState> procs, int[] screenStates, int[] memStates, int[] procStates, - long now, long totalTime) { + boolean inclUidVers, long now, long totalTime) { for (int i=procs.size()-1; i>=0; i--) { ProcessState proc = procs.get(i); pw.print(prefix); @@ -783,6 +793,8 @@ public final class ProcessStats implements Parcelable { pw.print(proc.mName); pw.print(" / "); UserHandle.formatUid(pw, proc.mUid); + pw.print(" / v"); + pw.print(proc.mVersion); pw.println(":"); dumpProcessSummaryDetails(pw, proc, prefix, " TOTAL: ", screenStates, memStates, procStates, now, totalTime, true); @@ -869,6 +881,8 @@ public final class ProcessStats implements Parcelable { pw.print("process"); pw.print(CSV_SEP); pw.print("uid"); + pw.print(CSV_SEP); + pw.print("vers"); dumpStateHeadersCsv(pw, CSV_SEP, sepScreenStates ? screenStates : null, sepMemStates ? memStates : null, sepProcStates ? procStates : null); @@ -878,6 +892,8 @@ public final class ProcessStats implements Parcelable { pw.print(proc.mName); pw.print(CSV_SEP); UserHandle.formatUid(pw, proc.mUid); + pw.print(CSV_SEP); + pw.print(proc.mVersion); dumpProcessStateCsv(pw, proc, sepScreenStates, screenStates, sepMemStates, memStates, sepProcStates, procStates, now); pw.println(); @@ -979,53 +995,88 @@ public final class ProcessStats implements Parcelable { public void resetSafely() { if (DEBUG) Slog.d(TAG, "Safely resetting state of " + mTimePeriodStartClockStr); resetCommon(); - long now = SystemClock.uptimeMillis(); - ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap(); + + // First initialize use count of all common processes. + final long now = SystemClock.uptimeMillis(); + final ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap(); for (int ip=procMap.size()-1; ip>=0; ip--) { - SparseArray<ProcessState> uids = procMap.valueAt(ip); + final SparseArray<ProcessState> uids = procMap.valueAt(ip); for (int iu=uids.size()-1; iu>=0; iu--) { - ProcessState ps = uids.valueAt(iu); - if (ps.isInUse()) { - uids.valueAt(iu).resetSafely(now); - } else { - uids.valueAt(iu).makeDead(); + uids.valueAt(iu).mTmpNumInUse = 0; + } + } + + // Next reset or prune all per-package processes, and for the ones that are reset + // track this back to the common processes. + final ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap(); + for (int ip=pkgMap.size()-1; ip>=0; ip--) { + final SparseArray<SparseArray<PackageState>> uids = pkgMap.valueAt(ip); + for (int iu=uids.size()-1; iu>=0; iu--) { + final SparseArray<PackageState> vpkgs = uids.valueAt(iu); + for (int iv=vpkgs.size()-1; iv>=0; iv--) { + final PackageState pkgState = vpkgs.valueAt(iv); + for (int iproc=pkgState.mProcesses.size()-1; iproc>=0; iproc--) { + final ProcessState ps = pkgState.mProcesses.valueAt(iproc); + if (ps.isInUse()) { + ps.resetSafely(now); + ps.mCommonProcess.mTmpNumInUse++; + ps.mCommonProcess.mTmpFoundSubProc = ps; + } else { + pkgState.mProcesses.valueAt(iproc).makeDead(); + pkgState.mProcesses.removeAt(iproc); + } + } + for (int isvc=pkgState.mServices.size()-1; isvc>=0; isvc--) { + final ServiceState ss = pkgState.mServices.valueAt(isvc); + if (ss.isInUse()) { + ss.resetSafely(now); + } else { + pkgState.mServices.removeAt(isvc); + } + } + if (pkgState.mProcesses.size() <= 0 && pkgState.mServices.size() <= 0) { + vpkgs.removeAt(iv); + } + } + if (vpkgs.size() <= 0) { uids.removeAt(iu); } } if (uids.size() <= 0) { - procMap.removeAt(ip); + pkgMap.removeAt(ip); } } - ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap(); - for (int ip=pkgMap.size()-1; ip>=0; ip--) { - SparseArray<PackageState> uids = pkgMap.valueAt(ip); + + // Finally prune out any common processes that are no longer in use. + for (int ip=procMap.size()-1; ip>=0; ip--) { + final SparseArray<ProcessState> uids = procMap.valueAt(ip); for (int iu=uids.size()-1; iu>=0; iu--) { - PackageState pkgState = uids.valueAt(iu); - for (int iproc=pkgState.mProcesses.size()-1; iproc>=0; iproc--) { - ProcessState ps = pkgState.mProcesses.valueAt(iproc); - if (ps.isInUse() || ps.mCommonProcess.isInUse()) { - pkgState.mProcesses.valueAt(iproc).resetSafely(now); - } else { - pkgState.mProcesses.valueAt(iproc).makeDead(); - pkgState.mProcesses.removeAt(iproc); - } - } - for (int isvc=pkgState.mServices.size()-1; isvc>=0; isvc--) { - ServiceState ss = pkgState.mServices.valueAt(isvc); - if (ss.isInUse()) { - pkgState.mServices.valueAt(isvc).resetSafely(now); + ProcessState ps = uids.valueAt(iu); + if (ps.isInUse() || ps.mTmpNumInUse > 0) { + // If this is a process for multiple packages, we could at this point + // be back down to one package. In that case, we want to revert back + // to a single shared ProcessState. We can do this by converting the + // current package-specific ProcessState up to the shared ProcessState, + // throwing away the current one we have here (because nobody else is + // using it). + if (!ps.mActive && ps.mMultiPackage && ps.mTmpNumInUse == 1) { + // Here we go... + ps = ps.mTmpFoundSubProc; + ps.mCommonProcess = ps; + uids.setValueAt(iu, ps); } else { - pkgState.mServices.removeAt(isvc); + ps.resetSafely(now); } - } - if (pkgState.mProcesses.size() <= 0 && pkgState.mServices.size() <= 0) { + } else { + ps.makeDead(); uids.removeAt(iu); } } if (uids.size() <= 0) { - pkgMap.removeAt(ip); + procMap.removeAt(ip); } } + mStartTime = now; if (DEBUG) Slog.d(TAG, "State reset; now " + mTimePeriodStartClockStr); } @@ -1193,23 +1244,27 @@ public final class ProcessStats implements Parcelable { uids.valueAt(iu).commitStateTime(now); } } - ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap(); + final ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap(); final int NPKG = pkgMap.size(); for (int ip=0; ip<NPKG; ip++) { - SparseArray<PackageState> uids = pkgMap.valueAt(ip); + final SparseArray<SparseArray<PackageState>> uids = pkgMap.valueAt(ip); final int NUID = uids.size(); for (int iu=0; iu<NUID; iu++) { - PackageState pkgState = uids.valueAt(iu); - final int NPROCS = pkgState.mProcesses.size(); - for (int iproc=0; iproc<NPROCS; iproc++) { - ProcessState proc = pkgState.mProcesses.valueAt(iproc); - if (proc.mCommonProcess != proc) { - proc.commitStateTime(now); + final SparseArray<PackageState> vpkgs = uids.valueAt(iu); + final int NVERS = vpkgs.size(); + for (int iv=0; iv<NVERS; iv++) { + PackageState pkgState = vpkgs.valueAt(iv); + final int NPROCS = pkgState.mProcesses.size(); + for (int iproc=0; iproc<NPROCS; iproc++) { + ProcessState proc = pkgState.mProcesses.valueAt(iproc); + if (proc.mCommonProcess != proc) { + proc.commitStateTime(now); + } + } + final int NSRVS = pkgState.mServices.size(); + for (int isvc=0; isvc<NSRVS; isvc++) { + pkgState.mServices.valueAt(isvc).commitStateTime(now); } - } - final int NSRVS = pkgState.mServices.size(); - for (int isvc=0; isvc<NSRVS; isvc++) { - pkgState.mServices.valueAt(isvc).commitStateTime(now); } } } @@ -1239,46 +1294,53 @@ public final class ProcessStats implements Parcelable { out.writeInt(NPROC); for (int ip=0; ip<NPROC; ip++) { writeCommonString(out, procMap.keyAt(ip)); - SparseArray<ProcessState> uids = procMap.valueAt(ip); + final SparseArray<ProcessState> uids = procMap.valueAt(ip); final int NUID = uids.size(); out.writeInt(NUID); for (int iu=0; iu<NUID; iu++) { out.writeInt(uids.keyAt(iu)); - ProcessState proc = uids.valueAt(iu); + final ProcessState proc = uids.valueAt(iu); writeCommonString(out, proc.mPackage); + out.writeInt(proc.mVersion); proc.writeToParcel(out, now); } } out.writeInt(NPKG); for (int ip=0; ip<NPKG; ip++) { writeCommonString(out, pkgMap.keyAt(ip)); - SparseArray<PackageState> uids = pkgMap.valueAt(ip); + final SparseArray<SparseArray<PackageState>> uids = pkgMap.valueAt(ip); final int NUID = uids.size(); out.writeInt(NUID); for (int iu=0; iu<NUID; iu++) { out.writeInt(uids.keyAt(iu)); - PackageState pkgState = uids.valueAt(iu); - final int NPROCS = pkgState.mProcesses.size(); - out.writeInt(NPROCS); - for (int iproc=0; iproc<NPROCS; iproc++) { - writeCommonString(out, pkgState.mProcesses.keyAt(iproc)); - ProcessState proc = pkgState.mProcesses.valueAt(iproc); - if (proc.mCommonProcess == proc) { - // This is the same as the common process we wrote above. - out.writeInt(0); - } else { - // There is separate data for this package's process. - out.writeInt(1); - proc.writeToParcel(out, now); + final SparseArray<PackageState> vpkgs = uids.valueAt(iu); + final int NVERS = vpkgs.size(); + out.writeInt(NVERS); + for (int iv=0; iv<NVERS; iv++) { + out.writeInt(vpkgs.keyAt(iv)); + final PackageState pkgState = vpkgs.valueAt(iv); + final int NPROCS = pkgState.mProcesses.size(); + out.writeInt(NPROCS); + for (int iproc=0; iproc<NPROCS; iproc++) { + writeCommonString(out, pkgState.mProcesses.keyAt(iproc)); + final ProcessState proc = pkgState.mProcesses.valueAt(iproc); + if (proc.mCommonProcess == proc) { + // This is the same as the common process we wrote above. + out.writeInt(0); + } else { + // There is separate data for this package's process. + out.writeInt(1); + proc.writeToParcel(out, now); + } + } + final int NSRVS = pkgState.mServices.size(); + out.writeInt(NSRVS); + for (int isvc=0; isvc<NSRVS; isvc++) { + out.writeString(pkgState.mServices.keyAt(isvc)); + final ServiceState svc = pkgState.mServices.valueAt(isvc); + writeCommonString(out, svc.mProcessName); + svc.writeToParcel(out, now); } - } - final int NSRVS = pkgState.mServices.size(); - out.writeInt(NSRVS); - for (int isvc=0; isvc<NSRVS; isvc++) { - out.writeString(pkgState.mServices.keyAt(isvc)); - ServiceState svc = pkgState.mServices.valueAt(isvc); - writeCommonString(out, svc.mProcessName); - svc.writeToParcel(out, now); } } } @@ -1396,7 +1458,7 @@ public final class ProcessStats implements Parcelable { } while (NPROC > 0) { NPROC--; - String procName = readCommonString(in, version); + final String procName = readCommonString(in, version); if (procName == null) { mReadError = "bad process name"; return; @@ -1408,23 +1470,24 @@ public final class ProcessStats implements Parcelable { } while (NUID > 0) { NUID--; - int uid = in.readInt(); + final int uid = in.readInt(); if (uid < 0) { mReadError = "bad uid: " + uid; return; } - String pkgName = readCommonString(in, version); + final String pkgName = readCommonString(in, version); if (pkgName == null) { mReadError = "bad process package name"; return; } + final int vers = in.readInt(); ProcessState proc = hadData ? mProcesses.get(procName, uid) : null; if (proc != null) { if (!proc.readFromParcel(in, false)) { return; } } else { - proc = new ProcessState(this, pkgName, uid, procName); + proc = new ProcessState(this, pkgName, uid, vers, procName); if (!proc.readFromParcel(in, true)) { return; } @@ -1444,7 +1507,7 @@ public final class ProcessStats implements Parcelable { } while (NPKG > 0) { NPKG--; - String pkgName = readCommonString(in, version); + final String pkgName = readCommonString(in, version); if (pkgName == null) { mReadError = "bad package name"; return; @@ -1456,83 +1519,98 @@ public final class ProcessStats implements Parcelable { } while (NUID > 0) { NUID--; - int uid = in.readInt(); + final int uid = in.readInt(); if (uid < 0) { mReadError = "bad uid: " + uid; return; } - PackageState pkgState = new PackageState(pkgName, uid); - mPackages.put(pkgName, uid, pkgState); - int NPROCS = in.readInt(); - if (NPROCS < 0) { - mReadError = "bad package process count: " + NPROCS; + int NVERS = in.readInt(); + if (NVERS < 0) { + mReadError = "bad versions count: " + NVERS; return; } - while (NPROCS > 0) { - NPROCS--; - String procName = readCommonString(in, version); - if (procName == null) { - mReadError = "bad package process name"; - return; + while (NVERS > 0) { + NVERS--; + final int vers = in.readInt(); + PackageState pkgState = new PackageState(pkgName, uid); + SparseArray<PackageState> vpkg = mPackages.get(pkgName, uid); + if (vpkg == null) { + vpkg = new SparseArray<PackageState>(); + mPackages.put(pkgName, uid, vpkg); } - int hasProc = in.readInt(); - if (DEBUG_PARCEL) Slog.d(TAG, "Reading package " + pkgName + " " + uid - + " process " + procName + " hasProc=" + hasProc); - ProcessState commonProc = mProcesses.get(procName, uid); - if (DEBUG_PARCEL) Slog.d(TAG, "Got common proc " + procName + " " + uid - + ": " + commonProc); - if (commonProc == null) { - mReadError = "no common proc: " + procName; + vpkg.put(vers, pkgState); + int NPROCS = in.readInt(); + if (NPROCS < 0) { + mReadError = "bad package process count: " + NPROCS; return; } - if (hasProc != 0) { - // The process for this package is unique to the package; we - // need to load it. We don't need to do anything about it if - // it is not unique because if someone later looks for it - // they will find and use it from the global procs. - ProcessState proc = hadData ? pkgState.mProcesses.get(procName) : null; - if (proc != null) { - if (!proc.readFromParcel(in, false)) { - return; + while (NPROCS > 0) { + NPROCS--; + String procName = readCommonString(in, version); + if (procName == null) { + mReadError = "bad package process name"; + return; + } + int hasProc = in.readInt(); + if (DEBUG_PARCEL) Slog.d(TAG, "Reading package " + pkgName + " " + uid + + " process " + procName + " hasProc=" + hasProc); + ProcessState commonProc = mProcesses.get(procName, uid); + if (DEBUG_PARCEL) Slog.d(TAG, "Got common proc " + procName + " " + uid + + ": " + commonProc); + if (commonProc == null) { + mReadError = "no common proc: " + procName; + return; + } + if (hasProc != 0) { + // The process for this package is unique to the package; we + // need to load it. We don't need to do anything about it if + // it is not unique because if someone later looks for it + // they will find and use it from the global procs. + ProcessState proc = hadData ? pkgState.mProcesses.get(procName) : null; + if (proc != null) { + if (!proc.readFromParcel(in, false)) { + return; + } + } else { + proc = new ProcessState(commonProc, pkgName, uid, vers, procName, + 0); + if (!proc.readFromParcel(in, true)) { + return; + } } + if (DEBUG_PARCEL) Slog.d(TAG, "Adding package " + pkgName + " process: " + + procName + " " + uid + " " + proc); + pkgState.mProcesses.put(procName, proc); } else { - proc = new ProcessState(commonProc, pkgName, uid, procName, 0); - if (!proc.readFromParcel(in, true)) { - return; - } + if (DEBUG_PARCEL) Slog.d(TAG, "Adding package " + pkgName + " process: " + + procName + " " + uid + " " + commonProc); + pkgState.mProcesses.put(procName, commonProc); } - if (DEBUG_PARCEL) Slog.d(TAG, "Adding package " + pkgName + " process: " - + procName + " " + uid + " " + proc); - pkgState.mProcesses.put(procName, proc); - } else { - if (DEBUG_PARCEL) Slog.d(TAG, "Adding package " + pkgName + " process: " - + procName + " " + uid + " " + commonProc); - pkgState.mProcesses.put(procName, commonProc); } - } - int NSRVS = in.readInt(); - if (NSRVS < 0) { - mReadError = "bad package service count: " + NSRVS; - return; - } - while (NSRVS > 0) { - NSRVS--; - String serviceName = in.readString(); - if (serviceName == null) { - mReadError = "bad package service name"; + int NSRVS = in.readInt(); + if (NSRVS < 0) { + mReadError = "bad package service count: " + NSRVS; return; } - String processName = version > 9 ? readCommonString(in, version) : null; - ServiceState serv = hadData ? pkgState.mServices.get(serviceName) : null; - if (serv == null) { - serv = new ServiceState(this, pkgName, serviceName, processName, null); - } - if (!serv.readFromParcel(in)) { - return; + while (NSRVS > 0) { + NSRVS--; + String serviceName = in.readString(); + if (serviceName == null) { + mReadError = "bad package service name"; + return; + } + String processName = version > 9 ? readCommonString(in, version) : null; + ServiceState serv = hadData ? pkgState.mServices.get(serviceName) : null; + if (serv == null) { + serv = new ServiceState(this, pkgName, serviceName, processName, null); + } + if (!serv.readFromParcel(in)) { + return; + } + if (DEBUG_PARCEL) Slog.d(TAG, "Adding package " + pkgName + " service: " + + serviceName + " " + uid + " " + serv); + pkgState.mServices.put(serviceName, serv); } - if (DEBUG_PARCEL) Slog.d(TAG, "Adding package " + pkgName + " service: " - + serviceName + " " + uid + " " + serv); - pkgState.mServices.put(serviceName, serv); } } } @@ -1627,30 +1705,36 @@ public final class ProcessStats implements Parcelable { return ~lo; // value not present } - public PackageState getPackageStateLocked(String packageName, int uid) { - PackageState as = mPackages.get(packageName, uid); + public PackageState getPackageStateLocked(String packageName, int uid, int vers) { + SparseArray<PackageState> vpkg = mPackages.get(packageName, uid); + if (vpkg == null) { + vpkg = new SparseArray<PackageState>(); + mPackages.put(packageName, uid, vpkg); + } + PackageState as = vpkg.get(vers); if (as != null) { return as; } as = new PackageState(packageName, uid); - mPackages.put(packageName, uid, as); + vpkg.put(vers, as); return as; } - public ProcessState getProcessStateLocked(String packageName, int uid, String processName) { - final PackageState pkgState = getPackageStateLocked(packageName, uid); + public ProcessState getProcessStateLocked(String packageName, int uid, int vers, + String processName) { + final PackageState pkgState = getPackageStateLocked(packageName, uid, vers); ProcessState ps = pkgState.mProcesses.get(processName); if (ps != null) { return ps; } ProcessState commonProc = mProcesses.get(processName, uid); if (commonProc == null) { - commonProc = new ProcessState(this, packageName, uid, processName); + commonProc = new ProcessState(this, packageName, uid, vers, processName); mProcesses.put(processName, uid, commonProc); if (DEBUG) Slog.d(TAG, "GETPROC created new common " + commonProc); } if (!commonProc.mMultiPackage) { - if (packageName.equals(commonProc.mPackage)) { + if (packageName.equals(commonProc.mPackage) && vers == commonProc.mVersion) { // This common process is not in use by multiple packages, and // is for the calling package, so we can just use it directly. ps = commonProc; @@ -1668,7 +1752,8 @@ public final class ProcessStats implements Parcelable { long now = SystemClock.uptimeMillis(); // First let's make a copy of the current process state and put // that under the now unique state for its original package name. - final PackageState commonPkgState = getPackageStateLocked(commonProc.mPackage, uid); + final PackageState commonPkgState = getPackageStateLocked(commonProc.mPackage, + uid, commonProc.mVersion); if (commonPkgState != null) { ProcessState cloned = commonProc.clone(commonProc.mPackage, now); if (DEBUG) Slog.d(TAG, "GETPROC setting clone to pkg " + commonProc.mPackage @@ -1691,13 +1776,13 @@ public final class ProcessStats implements Parcelable { + "/" + uid + " for proc " + commonProc.mName); } // And now make a fresh new process state for the new package name. - ps = new ProcessState(commonProc, packageName, uid, processName, now); + ps = new ProcessState(commonProc, packageName, uid, vers, processName, now); if (DEBUG) Slog.d(TAG, "GETPROC created new pkg " + ps); } } else { // The common process is for multiple packages, we need to create a // separate object for the per-package data. - ps = new ProcessState(commonProc, packageName, uid, processName, + ps = new ProcessState(commonProc, packageName, uid, vers, processName, SystemClock.uptimeMillis()); if (DEBUG) Slog.d(TAG, "GETPROC created new pkg " + ps); } @@ -1706,16 +1791,16 @@ public final class ProcessStats implements Parcelable { return ps; } - public ProcessStats.ServiceState getServiceStateLocked(String packageName, int uid, + public ProcessStats.ServiceState getServiceStateLocked(String packageName, int uid, int vers, String processName, String className) { - final ProcessStats.PackageState as = getPackageStateLocked(packageName, uid); + final ProcessStats.PackageState as = getPackageStateLocked(packageName, uid, vers); ProcessStats.ServiceState ss = as.mServices.get(className); if (ss != null) { if (DEBUG) Slog.d(TAG, "GETSVC: returning existing " + ss); return ss; } final ProcessStats.ProcessState ps = processName != null - ? getProcessStateLocked(packageName, uid, processName) : null; + ? getProcessStateLocked(packageName, uid, vers, processName) : null; ss = new ProcessStats.ServiceState(this, packageName, className, processName, ps); as.mServices.put(className, ss); if (DEBUG) Slog.d(TAG, "GETSVC: creating " + ss + " in " + ps); @@ -1756,119 +1841,124 @@ public final class ProcessStats implements Parcelable { boolean dumpAll, boolean activeOnly) { long totalTime = dumpSingleTime(null, null, mMemFactorDurations, mMemFactor, mStartTime, now); - ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap(); + ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap(); boolean printedHeader = false; boolean sepNeeded = false; for (int ip=0; ip<pkgMap.size(); ip++) { final String pkgName = pkgMap.keyAt(ip); - final SparseArray<PackageState> uids = pkgMap.valueAt(ip); + final SparseArray<SparseArray<PackageState>> uids = pkgMap.valueAt(ip); for (int iu=0; iu<uids.size(); iu++) { final int uid = uids.keyAt(iu); - final PackageState pkgState = uids.valueAt(iu); - final int NPROCS = pkgState.mProcesses.size(); - final int NSRVS = pkgState.mServices.size(); - final boolean pkgMatch = reqPackage == null || reqPackage.equals(pkgName); - if (!pkgMatch) { - boolean procMatch = false; - for (int iproc=0; iproc<NPROCS; iproc++) { - ProcessState proc = pkgState.mProcesses.valueAt(iproc); - if (reqPackage.equals(proc.mName)) { - procMatch = true; - break; + final SparseArray<PackageState> vpkgs = uids.valueAt(iu); + for (int iv=0; iv<vpkgs.size(); iv++) { + final int vers = vpkgs.keyAt(iv); + final PackageState pkgState = vpkgs.valueAt(iv); + final int NPROCS = pkgState.mProcesses.size(); + final int NSRVS = pkgState.mServices.size(); + final boolean pkgMatch = reqPackage == null || reqPackage.equals(pkgName); + if (!pkgMatch) { + boolean procMatch = false; + for (int iproc=0; iproc<NPROCS; iproc++) { + ProcessState proc = pkgState.mProcesses.valueAt(iproc); + if (reqPackage.equals(proc.mName)) { + procMatch = true; + break; + } + } + if (!procMatch) { + continue; } } - if (!procMatch) { - continue; + if (NPROCS > 0 || NSRVS > 0) { + if (!printedHeader) { + pw.println("Per-Package Stats:"); + printedHeader = true; + sepNeeded = true; + } + pw.print(" * "); pw.print(pkgName); pw.print(" / "); + UserHandle.formatUid(pw, uid); pw.print(" / v"); + pw.print(vers); pw.println(":"); } - } - if (NPROCS > 0 || NSRVS > 0) { - if (!printedHeader) { - pw.println("Per-Package Stats:"); - printedHeader = true; - sepNeeded = true; + if (!dumpSummary || dumpAll) { + for (int iproc=0; iproc<NPROCS; iproc++) { + ProcessState proc = pkgState.mProcesses.valueAt(iproc); + if (!pkgMatch && !reqPackage.equals(proc.mName)) { + continue; + } + if (activeOnly && !proc.isInUse()) { + pw.print(" (Not active: "); + pw.print(pkgState.mProcesses.keyAt(iproc)); pw.println(")"); + continue; + } + pw.print(" Process "); + pw.print(pkgState.mProcesses.keyAt(iproc)); + if (proc.mCommonProcess.mMultiPackage) { + pw.print(" (multi, "); + } else { + pw.print(" (unique, "); + } + pw.print(proc.mDurationsTableSize); + pw.print(" entries)"); + pw.println(":"); + dumpProcessState(pw, " ", proc, ALL_SCREEN_ADJ, ALL_MEM_ADJ, + ALL_PROC_STATES, now); + dumpProcessPss(pw, " ", proc, ALL_SCREEN_ADJ, ALL_MEM_ADJ, + ALL_PROC_STATES); + dumpProcessInternalLocked(pw, " ", proc, dumpAll); + } + } else { + ArrayList<ProcessState> procs = new ArrayList<ProcessState>(); + for (int iproc=0; iproc<NPROCS; iproc++) { + ProcessState proc = pkgState.mProcesses.valueAt(iproc); + if (!pkgMatch && !reqPackage.equals(proc.mName)) { + continue; + } + if (activeOnly && !proc.isInUse()) { + continue; + } + procs.add(proc); + } + dumpProcessSummaryLocked(pw, " ", procs, ALL_SCREEN_ADJ, ALL_MEM_ADJ, + NON_CACHED_PROC_STATES, false, now, totalTime); } - pw.print(" * "); pw.print(pkgName); pw.print(" / "); - UserHandle.formatUid(pw, uid); pw.println(":"); - } - if (!dumpSummary || dumpAll) { - for (int iproc=0; iproc<NPROCS; iproc++) { - ProcessState proc = pkgState.mProcesses.valueAt(iproc); - if (!pkgMatch && !reqPackage.equals(proc.mName)) { + for (int isvc=0; isvc<NSRVS; isvc++) { + ServiceState svc = pkgState.mServices.valueAt(isvc); + if (!pkgMatch && !reqPackage.equals(svc.mProcessName)) { continue; } - if (activeOnly && !proc.isInUse()) { + if (activeOnly && !svc.isInUse()) { pw.print(" (Not active: "); - pw.print(pkgState.mProcesses.keyAt(iproc)); pw.println(")"); + pw.print(pkgState.mServices.keyAt(isvc)); pw.println(")"); continue; } - pw.print(" Process "); - pw.print(pkgState.mProcesses.keyAt(iproc)); - if (proc.mCommonProcess.mMultiPackage) { - pw.print(" (multi, "); + if (dumpAll) { + pw.print(" Service "); } else { - pw.print(" (unique, "); + pw.print(" * "); } - pw.print(proc.mDurationsTableSize); - pw.print(" entries)"); + pw.print(pkgState.mServices.keyAt(isvc)); pw.println(":"); - dumpProcessState(pw, " ", proc, ALL_SCREEN_ADJ, ALL_MEM_ADJ, - ALL_PROC_STATES, now); - dumpProcessPss(pw, " ", proc, ALL_SCREEN_ADJ, ALL_MEM_ADJ, - ALL_PROC_STATES); - dumpProcessInternalLocked(pw, " ", proc, dumpAll); - } - } else { - ArrayList<ProcessState> procs = new ArrayList<ProcessState>(); - for (int iproc=0; iproc<NPROCS; iproc++) { - ProcessState proc = pkgState.mProcesses.valueAt(iproc); - if (!pkgMatch && !reqPackage.equals(proc.mName)) { - continue; - } - if (activeOnly && !proc.isInUse()) { - continue; - } - procs.add(proc); - } - dumpProcessSummaryLocked(pw, " ", procs, ALL_SCREEN_ADJ, ALL_MEM_ADJ, - NON_CACHED_PROC_STATES, now, totalTime); - } - for (int isvc=0; isvc<NSRVS; isvc++) { - ServiceState svc = pkgState.mServices.valueAt(isvc); - if (!pkgMatch && !reqPackage.equals(svc.mProcessName)) { - continue; - } - if (activeOnly && !svc.isInUse()) { - pw.print(" (Not active: "); - pw.print(pkgState.mServices.keyAt(isvc)); pw.println(")"); - continue; - } - if (dumpAll) { - pw.print(" Service "); - } else { - pw.print(" * "); - } - pw.print(pkgState.mServices.keyAt(isvc)); - pw.println(":"); - pw.print(" Process: "); pw.println(svc.mProcessName); - dumpServiceStats(pw, " ", " ", " ", "Running", svc, - svc.mRunCount, ServiceState.SERVICE_RUN, svc.mRunState, - svc.mRunStartTime, now, totalTime, !dumpSummary || dumpAll); - dumpServiceStats(pw, " ", " ", " ", "Started", svc, - svc.mStartedCount, ServiceState.SERVICE_STARTED, svc.mStartedState, - svc.mStartedStartTime, now, totalTime, !dumpSummary || dumpAll); - dumpServiceStats(pw, " ", " ", " ", "Bound", svc, - svc.mBoundCount, ServiceState.SERVICE_BOUND, svc.mBoundState, - svc.mBoundStartTime, now, totalTime, !dumpSummary || dumpAll); - dumpServiceStats(pw, " ", " ", " ", "Executing", svc, - svc.mExecCount, ServiceState.SERVICE_EXEC, svc.mExecState, - svc.mExecStartTime, now, totalTime, !dumpSummary || dumpAll); - if (dumpAll) { - if (svc.mOwner != null) { - pw.print(" mOwner="); pw.println(svc.mOwner); - } - if (svc.mStarted || svc.mRestarting) { - pw.print(" mStarted="); pw.print(svc.mStarted); - pw.print(" mRestarting="); pw.println(svc.mRestarting); + pw.print(" Process: "); pw.println(svc.mProcessName); + dumpServiceStats(pw, " ", " ", " ", "Running", svc, + svc.mRunCount, ServiceState.SERVICE_RUN, svc.mRunState, + svc.mRunStartTime, now, totalTime, !dumpSummary || dumpAll); + dumpServiceStats(pw, " ", " ", " ", "Started", svc, + svc.mStartedCount, ServiceState.SERVICE_STARTED, svc.mStartedState, + svc.mStartedStartTime, now, totalTime, !dumpSummary || dumpAll); + dumpServiceStats(pw, " ", " ", " ", "Bound", svc, + svc.mBoundCount, ServiceState.SERVICE_BOUND, svc.mBoundState, + svc.mBoundStartTime, now, totalTime, !dumpSummary || dumpAll); + dumpServiceStats(pw, " ", " ", " ", "Executing", svc, + svc.mExecCount, ServiceState.SERVICE_EXEC, svc.mExecState, + svc.mExecStartTime, now, totalTime, !dumpSummary || dumpAll); + if (dumpAll) { + if (svc.mOwner != null) { + pw.print(" mOwner="); pw.println(svc.mOwner); + } + if (svc.mStarted || svc.mRestarting) { + pw.print(" mStarted="); pw.print(svc.mStarted); + pw.print(" mRestarting="); pw.println(svc.mRestarting); + } } } } @@ -2059,7 +2149,7 @@ public final class ProcessStats implements Parcelable { pw.println(header); } dumpProcessSummaryLocked(pw, prefix, procs, screenStates, memStates, - sortProcStates, now, totalTime); + sortProcStates, true, now, totalTime); } } @@ -2067,23 +2157,27 @@ public final class ProcessStats implements Parcelable { int[] procStates, int sortProcStates[], long now, String reqPackage, boolean activeOnly) { final ArraySet<ProcessState> foundProcs = new ArraySet<ProcessState>(); - final ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap(); + final ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap(); for (int ip=0; ip<pkgMap.size(); ip++) { final String pkgName = pkgMap.keyAt(ip); - final SparseArray<PackageState> procs = pkgMap.valueAt(ip); + final SparseArray<SparseArray<PackageState>> procs = pkgMap.valueAt(ip); for (int iu=0; iu<procs.size(); iu++) { - final PackageState state = procs.valueAt(iu); - final int NPROCS = state.mProcesses.size(); - final boolean pkgMatch = reqPackage == null || reqPackage.equals(pkgName); - for (int iproc=0; iproc<NPROCS; iproc++) { - final ProcessState proc = state.mProcesses.valueAt(iproc); - if (!pkgMatch && !reqPackage.equals(proc.mName)) { - continue; - } - if (activeOnly && !proc.isInUse()) { - continue; + final SparseArray<PackageState> vpkgs = procs.valueAt(iu); + final int NVERS = vpkgs.size(); + for (int iv=0; iv<NVERS; iv++) { + final PackageState state = vpkgs.valueAt(iv); + final int NPROCS = state.mProcesses.size(); + final boolean pkgMatch = reqPackage == null || reqPackage.equals(pkgName); + for (int iproc=0; iproc<NPROCS; iproc++) { + final ProcessState proc = state.mProcesses.valueAt(iproc); + if (!pkgMatch && !reqPackage.equals(proc.mName)) { + continue; + } + if (activeOnly && !proc.isInUse()) { + continue; + } + foundProcs.add(proc.mCommonProcess); } - foundProcs.add(proc.mCommonProcess); } } } @@ -2128,8 +2222,8 @@ public final class ProcessStats implements Parcelable { public void dumpCheckinLocked(PrintWriter pw, String reqPackage) { final long now = SystemClock.uptimeMillis(); - ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap(); - pw.println("vers,3"); + final ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap(); + pw.println("vers,4"); pw.print("period,"); pw.print(mTimePeriodStartClockStr); pw.print(","); pw.print(mTimePeriodStartRealtime); pw.print(","); pw.print(mRunning ? SystemClock.elapsedRealtime() : mTimePeriodEndRealtime); @@ -2152,75 +2246,85 @@ public final class ProcessStats implements Parcelable { pw.println(); pw.print("config,"); pw.print(mRuntime); pw.print(','); pw.println(mWebView); for (int ip=0; ip<pkgMap.size(); ip++) { - String pkgName = pkgMap.keyAt(ip); + final String pkgName = pkgMap.keyAt(ip); if (reqPackage != null && !reqPackage.equals(pkgName)) { continue; } - SparseArray<PackageState> uids = pkgMap.valueAt(ip); + final SparseArray<SparseArray<PackageState>> uids = pkgMap.valueAt(ip); for (int iu=0; iu<uids.size(); iu++) { - int uid = uids.keyAt(iu); - PackageState pkgState = uids.valueAt(iu); - final int NPROCS = pkgState.mProcesses.size(); - final int NSRVS = pkgState.mServices.size(); - for (int iproc=0; iproc<NPROCS; iproc++) { - ProcessState proc = pkgState.mProcesses.valueAt(iproc); - pw.print("pkgproc,"); - pw.print(pkgName); - pw.print(","); - pw.print(uid); - pw.print(","); - pw.print(collapseString(pkgName, pkgState.mProcesses.keyAt(iproc))); - dumpAllProcessStateCheckin(pw, proc, now); - pw.println(); - if (proc.mPssTableSize > 0) { - pw.print("pkgpss,"); + final int uid = uids.keyAt(iu); + final SparseArray<PackageState> vpkgs = uids.valueAt(iu); + for (int iv=0; iv<vpkgs.size(); iv++) { + final int vers = vpkgs.keyAt(iv); + final PackageState pkgState = vpkgs.valueAt(iv); + final int NPROCS = pkgState.mProcesses.size(); + final int NSRVS = pkgState.mServices.size(); + for (int iproc=0; iproc<NPROCS; iproc++) { + ProcessState proc = pkgState.mProcesses.valueAt(iproc); + pw.print("pkgproc,"); pw.print(pkgName); pw.print(","); pw.print(uid); pw.print(","); - pw.print(collapseString(pkgName, pkgState.mProcesses.keyAt(iproc))); - dumpAllProcessPssCheckin(pw, proc); - pw.println(); - } - if (proc.mNumExcessiveWake > 0 || proc.mNumExcessiveCpu > 0 - || proc.mNumCachedKill > 0) { - pw.print("pkgkills,"); - pw.print(pkgName); - pw.print(","); - pw.print(uid); + pw.print(vers); pw.print(","); pw.print(collapseString(pkgName, pkgState.mProcesses.keyAt(iproc))); - pw.print(","); - pw.print(proc.mNumExcessiveWake); - pw.print(","); - pw.print(proc.mNumExcessiveCpu); - pw.print(","); - pw.print(proc.mNumCachedKill); - pw.print(","); - pw.print(proc.mMinCachedKillPss); - pw.print(":"); - pw.print(proc.mAvgCachedKillPss); - pw.print(":"); - pw.print(proc.mMaxCachedKillPss); + dumpAllProcessStateCheckin(pw, proc, now); pw.println(); + if (proc.mPssTableSize > 0) { + pw.print("pkgpss,"); + pw.print(pkgName); + pw.print(","); + pw.print(uid); + pw.print(","); + pw.print(vers); + pw.print(","); + pw.print(collapseString(pkgName, pkgState.mProcesses.keyAt(iproc))); + dumpAllProcessPssCheckin(pw, proc); + pw.println(); + } + if (proc.mNumExcessiveWake > 0 || proc.mNumExcessiveCpu > 0 + || proc.mNumCachedKill > 0) { + pw.print("pkgkills,"); + pw.print(pkgName); + pw.print(","); + pw.print(uid); + pw.print(","); + pw.print(vers); + pw.print(","); + pw.print(collapseString(pkgName, pkgState.mProcesses.keyAt(iproc))); + pw.print(","); + pw.print(proc.mNumExcessiveWake); + pw.print(","); + pw.print(proc.mNumExcessiveCpu); + pw.print(","); + pw.print(proc.mNumCachedKill); + pw.print(","); + pw.print(proc.mMinCachedKillPss); + pw.print(":"); + pw.print(proc.mAvgCachedKillPss); + pw.print(":"); + pw.print(proc.mMaxCachedKillPss); + pw.println(); + } + } + for (int isvc=0; isvc<NSRVS; isvc++) { + String serviceName = collapseString(pkgName, + pkgState.mServices.keyAt(isvc)); + ServiceState svc = pkgState.mServices.valueAt(isvc); + dumpServiceTimeCheckin(pw, "pkgsvc-run", pkgName, uid, vers, serviceName, + svc, ServiceState.SERVICE_RUN, svc.mRunCount, + svc.mRunState, svc.mRunStartTime, now); + dumpServiceTimeCheckin(pw, "pkgsvc-start", pkgName, uid, vers, serviceName, + svc, ServiceState.SERVICE_STARTED, svc.mStartedCount, + svc.mStartedState, svc.mStartedStartTime, now); + dumpServiceTimeCheckin(pw, "pkgsvc-bound", pkgName, uid, vers, serviceName, + svc, ServiceState.SERVICE_BOUND, svc.mBoundCount, + svc.mBoundState, svc.mBoundStartTime, now); + dumpServiceTimeCheckin(pw, "pkgsvc-exec", pkgName, uid, vers, serviceName, + svc, ServiceState.SERVICE_EXEC, svc.mExecCount, + svc.mExecState, svc.mExecStartTime, now); } - } - for (int isvc=0; isvc<NSRVS; isvc++) { - String serviceName = collapseString(pkgName, - pkgState.mServices.keyAt(isvc)); - ServiceState svc = pkgState.mServices.valueAt(isvc); - dumpServiceTimeCheckin(pw, "pkgsvc-run", pkgName, uid, serviceName, - svc, ServiceState.SERVICE_RUN, svc.mRunCount, - svc.mRunState, svc.mRunStartTime, now); - dumpServiceTimeCheckin(pw, "pkgsvc-start", pkgName, uid, serviceName, - svc, ServiceState.SERVICE_STARTED, svc.mStartedCount, - svc.mStartedState, svc.mStartedStartTime, now); - dumpServiceTimeCheckin(pw, "pkgsvc-bound", pkgName, uid, serviceName, - svc, ServiceState.SERVICE_BOUND, svc.mBoundCount, - svc.mBoundState, svc.mBoundStartTime, now); - dumpServiceTimeCheckin(pw, "pkgsvc-exec", pkgName, uid, serviceName, - svc, ServiceState.SERVICE_EXEC, svc.mExecCount, - svc.mExecState, svc.mExecStartTime, now); } } } @@ -2364,9 +2468,10 @@ public final class ProcessStats implements Parcelable { } public static final class ProcessState extends DurationsTable { - public final ProcessState mCommonProcess; + public ProcessState mCommonProcess; public final String mPackage; public final int mUid; + public final int mVersion; //final long[] mDurations = new long[STATE_COUNT*ADJ_COUNT]; int mCurState = STATE_NOTHING; @@ -2393,16 +2498,19 @@ public final class ProcessStats implements Parcelable { boolean mDead; public long mTmpTotalTime; + int mTmpNumInUse; + ProcessState mTmpFoundSubProc; /** * Create a new top-level process state, for the initial case where there is only * a single package running in a process. The initial state is not running. */ - public ProcessState(ProcessStats processStats, String pkg, int uid, String name) { + public ProcessState(ProcessStats processStats, String pkg, int uid, int vers, String name) { super(processStats, name); mCommonProcess = this; mPackage = pkg; mUid = uid; + mVersion = vers; } /** @@ -2410,18 +2518,19 @@ public final class ProcessStats implements Parcelable { * state. The current running state of the top-level process is also copied, * marked as started running at 'now'. */ - public ProcessState(ProcessState commonProcess, String pkg, int uid, String name, + public ProcessState(ProcessState commonProcess, String pkg, int uid, int vers, String name, long now) { super(commonProcess.mStats, name); mCommonProcess = commonProcess; mPackage = pkg; mUid = uid; + mVersion = vers; mCurState = commonProcess.mCurState; mStartTime = now; } ProcessState clone(String pkg, long now) { - ProcessState pnew = new ProcessState(this, pkg, mUid, mName, now); + ProcessState pnew = new ProcessState(this, pkg, mUid, mVersion, mName, now); copyDurationsTo(pnew); if (mPssTable != null) { mStats.mAddLongTable = new int[mPssTable.length]; @@ -2811,9 +2920,20 @@ public final class ProcessStats implements Parcelable { // The array map is still pointing to a common process state // that is now shared across packages. Update it to point to // the new per-package state. - ProcessState proc = mStats.mPackages.get(pkgName, mUid).mProcesses.get(mName); + SparseArray<PackageState> vpkg = mStats.mPackages.get(pkgName, mUid); + if (vpkg == null) { + throw new IllegalStateException("Didn't find package " + pkgName + + " / " + mUid); + } + PackageState pkg = vpkg.get(mVersion); + if (pkg == null) { + throw new IllegalStateException("Didn't find package " + pkgName + + " / " + mUid + " vers " + mVersion); + } + ProcessState proc = pkg.mProcesses.get(mName); if (proc == null) { - throw new IllegalStateException("Didn't create per-package process"); + throw new IllegalStateException("Didn't create per-package process " + + mName + " in pkg " + pkgName + " / " + mUid + " vers " + mVersion); } return proc; } @@ -2829,18 +2949,26 @@ public final class ProcessStats implements Parcelable { // are losing whatever data we had in the old process state. Log.wtf(TAG, "Pulling dead proc: name=" + mName + " pkg=" + mPackage + " uid=" + mUid + " common.name=" + mCommonProcess.mName); - proc = mStats.getProcessStateLocked(proc.mPackage, proc.mUid, proc.mName); + proc = mStats.getProcessStateLocked(proc.mPackage, proc.mUid, proc.mVersion, + proc.mName); } if (proc.mMultiPackage) { // The array map is still pointing to a common process state // that is now shared across packages. Update it to point to // the new per-package state. - PackageState pkg = mStats.mPackages.get(pkgList.keyAt(index), proc.mUid); - if (pkg == null) { + SparseArray<PackageState> vpkg = mStats.mPackages.get(pkgList.keyAt(index), + proc.mUid); + if (vpkg == null) { throw new IllegalStateException("No existing package " + pkgList.keyAt(index) + "/" + proc.mUid + " for multi-proc " + proc.mName); } + PackageState pkg = vpkg.get(proc.mVersion); + if (pkg == null) { + throw new IllegalStateException("No existing package " + + pkgList.keyAt(index) + "/" + proc.mUid + + " for multi-proc " + proc.mName + " version " + proc.mVersion); + } proc = pkg.mProcesses.get(proc.mName); if (proc == null) { throw new IllegalStateException("Didn't create per-package process " @@ -3014,7 +3142,7 @@ public final class ProcessStats implements Parcelable { } public boolean isInUse() { - return mOwner != null; + return mOwner != null || mRestarting; } void add(ServiceState other) { diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java index 494bc78..a604d84 100644 --- a/core/java/com/android/internal/backup/LocalTransport.java +++ b/core/java/com/android/internal/backup/LocalTransport.java @@ -23,22 +23,22 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; import android.os.Environment; import android.os.ParcelFileDescriptor; -import android.os.RemoteException; import android.os.SELinux; import android.util.Log; import com.android.org.bouncycastle.util.encoders.Base64; import java.io.File; -import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; -import java.util.ArrayList; + +import libcore.io.ErrnoException; +import libcore.io.Libcore; +import libcore.io.StructStat; +import static libcore.io.OsConstants.*; /** * Backup transport for stashing stuff into a known location on disk, and @@ -101,7 +101,16 @@ public class LocalTransport extends IBackupTransport.Stub { } public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data) { - if (DEBUG) Log.v(TAG, "performBackup() pkg=" + packageInfo.packageName); + if (DEBUG) { + try { + StructStat ss = Libcore.os.fstat(data.getFileDescriptor()); + Log.v(TAG, "performBackup() pkg=" + packageInfo.packageName + + " size=" + ss.st_size); + } catch (ErrnoException e) { + Log.w(TAG, "Unable to stat input file in performBackup() on " + + packageInfo.packageName); + } + } File packageDir = new File(mDataDir, packageInfo.packageName); packageDir.mkdirs(); @@ -135,7 +144,16 @@ public class LocalTransport extends IBackupTransport.Stub { buf = new byte[bufSize]; } changeSet.readEntityData(buf, 0, dataSize); - if (DEBUG) Log.v(TAG, " data size " + dataSize); + if (DEBUG) { + try { + long cur = Libcore.os.lseek(data.getFileDescriptor(), 0, SEEK_CUR); + Log.v(TAG, " read entity data; new pos=" + cur); + } + catch (ErrnoException e) { + Log.w(TAG, "Unable to stat input file in performBackup() on " + + packageInfo.packageName); + } + } try { entity.write(buf, 0, dataSize); @@ -215,7 +233,9 @@ public class LocalTransport extends IBackupTransport.Stub { if (mRestorePackages == null) throw new IllegalStateException("startRestore not called"); while (++mRestorePackage < mRestorePackages.length) { String name = mRestorePackages[mRestorePackage].packageName; - if (new File(mDataDir, name).isDirectory()) { + // skip packages where we have a data dir but no actual contents + String[] contents = (new File(mDataDir, name)).list(); + if (contents != null && contents.length > 0) { if (DEBUG) Log.v(TAG, " nextRestorePackage() = " + name); return name; } diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java index 942995b..31ca3de 100644 --- a/core/java/com/android/internal/content/PackageMonitor.java +++ b/core/java/com/android/internal/content/PackageMonitor.java @@ -22,7 +22,6 @@ import android.content.Intent; import android.content.IntentFilter; import android.net.Uri; import android.os.Handler; -import android.os.HandlerThread; import android.os.Looper; import android.os.UserHandle; import com.android.internal.os.BackgroundThread; diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java new file mode 100644 index 0000000..dcc0a4c --- /dev/null +++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.inputmethod; + +import com.android.internal.inputmethod.InputMethodUtils.InputMethodSettings; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.text.TextUtils; +import android.util.Slog; +import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodSubtype; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.TreeMap; + +/** + * InputMethodSubtypeSwitchingController controls the switching behavior of the subtypes. + */ +public class InputMethodSubtypeSwitchingController { + private static final String TAG = InputMethodSubtypeSwitchingController.class.getSimpleName(); + private static final boolean DEBUG = false; + // TODO: Turn on this flag and add CTS when the platform starts expecting that all IMEs return + // true for supportsSwitchingToNextInputMethod(). + private static final boolean REQUIRE_SWITCHING_SUPPORT = false; + private static final int MAX_HISTORY_SIZE = 4; + private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID; + + private static class SubtypeParams { + public final InputMethodInfo mImi; + public final InputMethodSubtype mSubtype; + public final long mTime; + + public SubtypeParams(InputMethodInfo imi, InputMethodSubtype subtype) { + mImi = imi; + mSubtype = subtype; + mTime = System.currentTimeMillis(); + } + } + + public static class ImeSubtypeListItem implements Comparable<ImeSubtypeListItem> { + public final CharSequence mImeName; + public final CharSequence mSubtypeName; + public final InputMethodInfo mImi; + public final int mSubtypeId; + private final boolean mIsSystemLocale; + private final boolean mIsSystemLanguage; + + public ImeSubtypeListItem(CharSequence imeName, CharSequence subtypeName, + InputMethodInfo imi, int subtypeId, String subtypeLocale, String systemLocale) { + mImeName = imeName; + mSubtypeName = subtypeName; + mImi = imi; + mSubtypeId = subtypeId; + if (TextUtils.isEmpty(subtypeLocale)) { + mIsSystemLocale = false; + mIsSystemLanguage = false; + } else { + mIsSystemLocale = subtypeLocale.equals(systemLocale); + mIsSystemLanguage = mIsSystemLocale + || subtypeLocale.startsWith(systemLocale.substring(0, 2)); + } + } + + @Override + public int compareTo(ImeSubtypeListItem other) { + if (TextUtils.isEmpty(mImeName)) { + return 1; + } + if (TextUtils.isEmpty(other.mImeName)) { + return -1; + } + if (!TextUtils.equals(mImeName, other.mImeName)) { + return mImeName.toString().compareTo(other.mImeName.toString()); + } + if (TextUtils.equals(mSubtypeName, other.mSubtypeName)) { + return 0; + } + if (mIsSystemLocale) { + return -1; + } + if (other.mIsSystemLocale) { + return 1; + } + if (mIsSystemLanguage) { + return -1; + } + if (other.mIsSystemLanguage) { + return 1; + } + if (TextUtils.isEmpty(mSubtypeName)) { + return 1; + } + if (TextUtils.isEmpty(other.mSubtypeName)) { + return -1; + } + return mSubtypeName.toString().compareTo(other.mSubtypeName.toString()); + } + } + + private static class InputMethodAndSubtypeCircularList { + private final Context mContext; + // Used to load label + private final PackageManager mPm; + private final String mSystemLocaleStr; + private final InputMethodSettings mSettings; + + public InputMethodAndSubtypeCircularList(Context context, InputMethodSettings settings) { + mContext = context; + mSettings = settings; + mPm = context.getPackageManager(); + final Locale locale = context.getResources().getConfiguration().locale; + mSystemLocaleStr = locale != null ? locale.toString() : ""; + } + + private final TreeMap<InputMethodInfo, List<InputMethodSubtype>> mSortedImmis = + new TreeMap<InputMethodInfo, List<InputMethodSubtype>>( + new Comparator<InputMethodInfo>() { + @Override + public int compare(InputMethodInfo imi1, InputMethodInfo imi2) { + if (imi2 == null) + return 0; + if (imi1 == null) + return 1; + if (mPm == null) { + return imi1.getId().compareTo(imi2.getId()); + } + CharSequence imiId1 = imi1.loadLabel(mPm) + "/" + imi1.getId(); + CharSequence imiId2 = imi2.loadLabel(mPm) + "/" + imi2.getId(); + return imiId1.toString().compareTo(imiId2.toString()); + } + }); + + public ImeSubtypeListItem getNextInputMethod( + boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype) { + if (imi == null) { + return null; + } + final List<ImeSubtypeListItem> imList = + getSortedInputMethodAndSubtypeList(); + if (imList.size() <= 1) { + return null; + } + final int N = imList.size(); + final int currentSubtypeId = + subtype != null ? InputMethodUtils.getSubtypeIdFromHashCode(imi, + subtype.hashCode()) : NOT_A_SUBTYPE_ID; + for (int i = 0; i < N; ++i) { + final ImeSubtypeListItem isli = imList.get(i); + if (isli.mImi.equals(imi) && isli.mSubtypeId == currentSubtypeId) { + if (!onlyCurrentIme) { + return imList.get((i + 1) % N); + } + for (int j = 0; j < N - 1; ++j) { + final ImeSubtypeListItem candidate = imList.get((i + j + 1) % N); + if (candidate.mImi.equals(imi)) { + return candidate; + } + } + return null; + } + } + return null; + } + + public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList() { + return getSortedInputMethodAndSubtypeList(true, false, false); + } + + public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList( + boolean showSubtypes, boolean inputShown, boolean isScreenLocked) { + final ArrayList<ImeSubtypeListItem> imList = + new ArrayList<ImeSubtypeListItem>(); + final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis = + mSettings.getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked( + mContext); + if (immis == null || immis.size() == 0) { + return Collections.emptyList(); + } + mSortedImmis.clear(); + mSortedImmis.putAll(immis); + for (InputMethodInfo imi : mSortedImmis.keySet()) { + if (imi == null) { + continue; + } + List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypeList = immis.get(imi); + HashSet<String> enabledSubtypeSet = new HashSet<String>(); + for (InputMethodSubtype subtype : explicitlyOrImplicitlyEnabledSubtypeList) { + enabledSubtypeSet.add(String.valueOf(subtype.hashCode())); + } + final CharSequence imeLabel = imi.loadLabel(mPm); + if (showSubtypes && enabledSubtypeSet.size() > 0) { + final int subtypeCount = imi.getSubtypeCount(); + if (DEBUG) { + Slog.v(TAG, "Add subtypes: " + subtypeCount + ", " + imi.getId()); + } + for (int j = 0; j < subtypeCount; ++j) { + final InputMethodSubtype subtype = imi.getSubtypeAt(j); + final String subtypeHashCode = String.valueOf(subtype.hashCode()); + // We show all enabled IMEs and subtypes when an IME is shown. + if (enabledSubtypeSet.contains(subtypeHashCode) + && ((inputShown && !isScreenLocked) || !subtype.isAuxiliary())) { + final CharSequence subtypeLabel = + subtype.overridesImplicitlyEnabledSubtype() ? null : subtype + .getDisplayName(mContext, imi.getPackageName(), + imi.getServiceInfo().applicationInfo); + imList.add(new ImeSubtypeListItem(imeLabel, + subtypeLabel, imi, j, subtype.getLocale(), mSystemLocaleStr)); + + // Removing this subtype from enabledSubtypeSet because we no + // longer need to add an entry of this subtype to imList to avoid + // duplicated entries. + enabledSubtypeSet.remove(subtypeHashCode); + } + } + } else { + imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_ID, null, + mSystemLocaleStr)); + } + } + Collections.sort(imList); + return imList; + } + } + + private final ArrayDeque<SubtypeParams> mTypedSubtypeHistory = new ArrayDeque<SubtypeParams>(); + private final Object mLock = new Object(); + private final InputMethodSettings mSettings; + private InputMethodAndSubtypeCircularList mSubtypeList; + + public InputMethodSubtypeSwitchingController(InputMethodSettings settings) { + mSettings = settings; + } + + // TODO: write unit tests for this method and the logic that determines the next subtype + public void onCommitText(InputMethodInfo imi, InputMethodSubtype subtype) { + synchronized (mTypedSubtypeHistory) { + if (subtype == null) { + Slog.w(TAG, "Invalid InputMethodSubtype: " + imi.getId() + ", " + subtype); + return; + } + if (DEBUG) { + Slog.d(TAG, "onCommitText: " + imi.getId() + ", " + subtype); + } + if (REQUIRE_SWITCHING_SUPPORT) { + if (!imi.supportsSwitchingToNextInputMethod()) { + Slog.w(TAG, imi.getId() + " doesn't support switching to next input method."); + return; + } + } + if (mTypedSubtypeHistory.size() >= MAX_HISTORY_SIZE) { + mTypedSubtypeHistory.poll(); + } + mTypedSubtypeHistory.addFirst(new SubtypeParams(imi, subtype)); + } + } + + public void resetCircularListLocked(Context context) { + synchronized(mLock) { + mSubtypeList = new InputMethodAndSubtypeCircularList(context, mSettings); + } + } + + public ImeSubtypeListItem getNextInputMethod( + boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype) { + synchronized(mLock) { + return mSubtypeList.getNextInputMethod(onlyCurrentIme, imi, subtype); + } + } + + public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList(boolean showSubtypes, + boolean inputShown, boolean isScreenLocked) { + synchronized(mLock) { + return mSubtypeList.getSortedInputMethodAndSubtypeList( + showSubtypes, inputShown, isScreenLocked); + } + } +} diff --git a/core/java/com/android/internal/inputmethod/InputMethodUtils.java b/core/java/com/android/internal/inputmethod/InputMethodUtils.java index 63d018f..03a053c 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodUtils.java +++ b/core/java/com/android/internal/inputmethod/InputMethodUtils.java @@ -504,6 +504,7 @@ public class InputMethodUtils { private String mEnabledInputMethodsStrCache; private int mCurrentUserId; + private int[] mRelatedUserIds = new int[0]; private static void buildEnabledInputMethodsSettingString( StringBuilder builder, Pair<String, ArrayList<String>> pair) { @@ -536,6 +537,22 @@ public class InputMethodUtils { mCurrentUserId = userId; } + public void setRelatedUserIds(int[] relatedUserIds) { + synchronized (this) { + mRelatedUserIds = relatedUserIds; + } + } + + public boolean isRelatedToOrCurrentUser(int userId) { + synchronized (this) { + if (userId == mCurrentUserId) return true; + for (int i = 0; i < mRelatedUserIds.length; i++) { + if (userId == mRelatedUserIds[i]) return true; + } + return false; + } + } + public List<InputMethodInfo> getEnabledInputMethodListLocked() { return createEnabledInputMethodListLocked( getEnabledInputMethodsAndSubtypeListLocked()); @@ -959,5 +976,16 @@ public class InputMethodUtils { addSubtypeToHistory(curMethodId, subtypeId); } } + + public HashMap<InputMethodInfo, List<InputMethodSubtype>> + getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked(Context context) { + HashMap<InputMethodInfo, List<InputMethodSubtype>> enabledInputMethodAndSubtypes = + new HashMap<InputMethodInfo, List<InputMethodSubtype>>(); + for (InputMethodInfo imi: getEnabledInputMethodListLocked()) { + enabledInputMethodAndSubtypes.put( + imi, getEnabledInputMethodSubtypeListLocked(context, imi, true)); + } + return enabledInputMethodAndSubtypes; + } } } diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java index 8282d23..e2a2b1e 100644 --- a/core/java/com/android/internal/net/NetworkStatsFactory.java +++ b/core/java/com/android/internal/net/NetworkStatsFactory.java @@ -17,6 +17,7 @@ package com.android.internal.net; import static android.net.NetworkStats.SET_ALL; +import static android.net.NetworkStats.TAG_ALL; import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStats.UID_ALL; import static com.android.server.NetworkManagementSocketTagger.kernelToTag; @@ -26,6 +27,7 @@ import android.os.StrictMode; import android.os.SystemClock; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.ProcFileReader; import java.io.File; @@ -165,22 +167,32 @@ public class NetworkStatsFactory { } public NetworkStats readNetworkStatsDetail() throws IOException { - return readNetworkStatsDetail(UID_ALL); + return readNetworkStatsDetail(UID_ALL, null, TAG_ALL, null); } - public NetworkStats readNetworkStatsDetail(int limitUid) throws IOException { + public NetworkStats readNetworkStatsDetail(int limitUid, String[] limitIfaces, int limitTag, + NetworkStats lastStats) + throws IOException { if (USE_NATIVE_PARSING) { - final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0); - if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), limitUid) != 0) { + final NetworkStats stats; + if (lastStats != null) { + stats = lastStats; + stats.setElapsedRealtime(SystemClock.elapsedRealtime()); + } else { + stats = new NetworkStats(SystemClock.elapsedRealtime(), -1); + } + if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), limitUid, + limitIfaces, limitTag) != 0) { throw new IOException("Failed to parse network stats"); } if (SANITY_CHECK_NATIVE) { - final NetworkStats javaStats = javaReadNetworkStatsDetail(mStatsXtUid, limitUid); + final NetworkStats javaStats = javaReadNetworkStatsDetail(mStatsXtUid, limitUid, + limitIfaces, limitTag); assertEquals(javaStats, stats); } return stats; } else { - return javaReadNetworkStatsDetail(mStatsXtUid, limitUid); + return javaReadNetworkStatsDetail(mStatsXtUid, limitUid, limitIfaces, limitTag); } } @@ -189,7 +201,8 @@ public class NetworkStatsFactory { * expected to monotonically increase since device boot. */ @VisibleForTesting - public static NetworkStats javaReadNetworkStatsDetail(File detailPath, int limitUid) + public static NetworkStats javaReadNetworkStatsDetail(File detailPath, int limitUid, + String[] limitIfaces, int limitTag) throws IOException { final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads(); @@ -222,7 +235,9 @@ public class NetworkStatsFactory { entry.txBytes = reader.nextLong(); entry.txPackets = reader.nextLong(); - if (limitUid == UID_ALL || limitUid == entry.uid) { + if ((limitIfaces == null || ArrayUtils.contains(limitIfaces, entry.iface)) + && (limitUid == UID_ALL || limitUid == entry.uid) + && (limitTag == TAG_ALL || limitTag == entry.tag)) { stats.addValues(entry); } @@ -264,5 +279,5 @@ public class NetworkStatsFactory { */ @VisibleForTesting public static native int nativeReadNetworkStatsDetail( - NetworkStats stats, String path, int limitUid); + NetworkStats stats, String path, int limitUid, String[] limitIfaces, int limitTag); } diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java index 98599d0..0d00f41 100644 --- a/core/java/com/android/internal/net/VpnConfig.java +++ b/core/java/com/android/internal/net/VpnConfig.java @@ -25,8 +25,6 @@ import android.os.UserHandle; import android.net.RouteInfo; import android.net.LinkAddress; -import com.android.internal.util.Preconditions; - import java.net.InetAddress; import java.util.List; import java.util.ArrayList; diff --git a/core/java/com/android/internal/os/BatterySipper.java b/core/java/com/android/internal/os/BatterySipper.java new file mode 100644 index 0000000..6ca24d7 --- /dev/null +++ b/core/java/com/android/internal/os/BatterySipper.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.os; + +import android.os.BatteryStats.Uid; + +/** + * Contains power usage of an application, system service, or hardware type. + */ +public class BatterySipper implements Comparable<BatterySipper> { + public int userId; + public Uid uidObj; + public double value; + public double[] values; + public DrainType drainType; + public long usageTime; + public long cpuTime; + public long gpsTime; + public long wifiRunningTime; + public long cpuFgTime; + public long wakeLockTime; + public long mobileRxPackets; + public long mobileTxPackets; + public long mobileActive; + public int mobileActiveCount; + public double mobilemspp; // milliseconds per packet + public long wifiRxPackets; + public long wifiTxPackets; + public long mobileRxBytes; + public long mobileTxBytes; + public long wifiRxBytes; + public long wifiTxBytes; + public double percent; + public double noCoveragePercent; + public String[] mPackages; + public String packageWithHighestDrain; + + public enum DrainType { + IDLE, + CELL, + PHONE, + WIFI, + BLUETOOTH, + SCREEN, + APP, + USER, + UNACCOUNTED, + OVERCOUNTED + } + + public BatterySipper(DrainType drainType, Uid uid, double[] values) { + this.values = values; + if (values != null) value = values[0]; + this.drainType = drainType; + uidObj = uid; + } + + public double[] getValues() { + return values; + } + + public void computeMobilemspp() { + long packets = mobileRxPackets+mobileTxPackets; + mobilemspp = packets > 0 ? (mobileActive / (double)packets) : 0; + } + + @Override + public int compareTo(BatterySipper other) { + // Return the flipped value because we want the items in descending order + return Double.compare(other.value, value); + } + + /** + * Gets a list of packages associated with the current user + */ + public String[] getPackages() { + return mPackages; + } + + public int getUid() { + // Bail out if the current sipper is not an App sipper. + if (uidObj == null) { + return 0; + } + return uidObj.getUid(); + } +} diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java new file mode 100644 index 0000000..1dd1f5e --- /dev/null +++ b/core/java/com/android/internal/os/BatteryStatsHelper.java @@ -0,0 +1,807 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.os; + +import static android.os.BatteryStats.NETWORK_MOBILE_RX_DATA; +import static android.os.BatteryStats.NETWORK_MOBILE_TX_DATA; +import static android.os.BatteryStats.NETWORK_WIFI_RX_DATA; +import static android.os.BatteryStats.NETWORK_WIFI_TX_DATA; + +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorManager; +import android.net.ConnectivityManager; +import android.os.BatteryStats; +import android.os.BatteryStats.Uid; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Process; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.os.UserHandle; +import android.telephony.SignalStrength; +import android.util.Log; +import android.util.SparseArray; + +import com.android.internal.app.IBatteryStats; +import com.android.internal.os.BatterySipper.DrainType; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +/** + * A helper class for retrieving the power usage information for all applications and services. + * + * The caller must initialize this class as soon as activity object is ready to use (for example, in + * onAttach() for Fragment), call create() in onCreate() and call destroy() in onDestroy(). + */ +public class BatteryStatsHelper { + + private static final boolean DEBUG = false; + + private static final String TAG = BatteryStatsHelper.class.getSimpleName(); + + private static BatteryStats sStatsXfer; + + final private Context mContext; + + private IBatteryStats mBatteryInfo; + private BatteryStats mStats; + private PowerProfile mPowerProfile; + + private final List<BatterySipper> mUsageList = new ArrayList<BatterySipper>(); + private final List<BatterySipper> mWifiSippers = new ArrayList<BatterySipper>(); + private final List<BatterySipper> mBluetoothSippers = new ArrayList<BatterySipper>(); + private final SparseArray<List<BatterySipper>> mUserSippers + = new SparseArray<List<BatterySipper>>(); + private final SparseArray<Double> mUserPower = new SparseArray<Double>(); + + private final List<BatterySipper> mMobilemsppList = new ArrayList<BatterySipper>(); + + private int mStatsType = BatteryStats.STATS_SINCE_CHARGED; + private int mAsUser = 0; + + long mRawRealtime; + long mRawUptime; + long mBatteryRealtime; + long mBatteryUptime; + long mTypeBatteryRealtime; + long mTypeBatteryUptime; + + private long mStatsPeriod = 0; + private double mMaxPower = 1; + private double mComputedPower; + private double mTotalPower; + private double mWifiPower; + private double mBluetoothPower; + private double mMinDrainedPower; + private double mMaxDrainedPower; + + // How much the apps together have kept the mobile radio active. + private long mAppMobileActive; + + // How much the apps together have left WIFI running. + private long mAppWifiRunning; + + public BatteryStatsHelper(Context context) { + mContext = context; + } + + /** Clears the current stats and forces recreating for future use. */ + public void clearStats() { + mStats = null; + } + + public BatteryStats getStats() { + if (mStats == null) { + load(); + } + return mStats; + } + + public PowerProfile getPowerProfile() { + return mPowerProfile; + } + + public void create(BatteryStats stats) { + mPowerProfile = new PowerProfile(mContext); + mStats = stats; + } + + public void create(Bundle icicle) { + if (icicle != null) { + mStats = sStatsXfer; + } + mBatteryInfo = IBatteryStats.Stub.asInterface( + ServiceManager.getService(BatteryStats.SERVICE_NAME)); + mPowerProfile = new PowerProfile(mContext); + } + + public void storeState() { + sStatsXfer = mStats; + } + + public static String makemAh(double power) { + if (power < .00001) return String.format("%.8f", power); + else if (power < .0001) return String.format("%.7f", power); + else if (power < .001) return String.format("%.6f", power); + else if (power < .01) return String.format("%.5f", power); + else if (power < .1) return String.format("%.4f", power); + else if (power < 1) return String.format("%.3f", power); + else if (power < 10) return String.format("%.2f", power); + else if (power < 100) return String.format("%.1f", power); + else return String.format("%.0f", power); + } + + /** + * Refreshes the power usage list. + */ + public void refreshStats(int statsType, int asUser) { + refreshStats(statsType, asUser, SystemClock.elapsedRealtime() * 1000, + SystemClock.uptimeMillis() * 1000); + } + + public void refreshStats(int statsType, int asUser, long rawRealtimeUs, long rawUptimeUs) { + // Initialize mStats if necessary. + getStats(); + + mMaxPower = 0; + mComputedPower = 0; + mTotalPower = 0; + mWifiPower = 0; + mBluetoothPower = 0; + mAppMobileActive = 0; + mAppWifiRunning = 0; + + mUsageList.clear(); + mWifiSippers.clear(); + mBluetoothSippers.clear(); + mUserSippers.clear(); + mUserPower.clear(); + mMobilemsppList.clear(); + + if (mStats == null) { + return; + } + + mStatsType = statsType; + mAsUser = asUser; + mRawUptime = rawUptimeUs; + mRawRealtime = rawRealtimeUs; + mBatteryUptime = mStats.getBatteryUptime(rawUptimeUs); + mBatteryRealtime = mStats.getBatteryRealtime(rawRealtimeUs); + mTypeBatteryUptime = mStats.computeBatteryUptime(rawUptimeUs, mStatsType); + mTypeBatteryRealtime = mStats.computeBatteryRealtime(rawRealtimeUs, mStatsType); + + if (DEBUG) { + Log.d(TAG, "Raw time: realtime=" + (rawRealtimeUs/1000) + " uptime=" + + (rawUptimeUs/1000)); + Log.d(TAG, "Battery time: realtime=" + (mBatteryRealtime/1000) + " uptime=" + + (mBatteryUptime/1000)); + Log.d(TAG, "Battery type time: realtime=" + (mTypeBatteryRealtime/1000) + " uptime=" + + (mTypeBatteryUptime/1000)); + } + mMinDrainedPower = (mStats.getLowDischargeAmountSinceCharge() + * mPowerProfile.getBatteryCapacity()) / 100; + mMaxDrainedPower = (mStats.getHighDischargeAmountSinceCharge() + * mPowerProfile.getBatteryCapacity()) / 100; + + processAppUsage(); + + // Before aggregating apps in to users, collect all apps to sort by their ms per packet. + for (int i=0; i<mUsageList.size(); i++) { + BatterySipper bs = mUsageList.get(i); + bs.computeMobilemspp(); + if (bs.mobilemspp != 0) { + mMobilemsppList.add(bs); + } + } + for (int i=0; i<mUserSippers.size(); i++) { + List<BatterySipper> user = mUserSippers.valueAt(i); + for (int j=0; j<user.size(); j++) { + BatterySipper bs = user.get(j); + bs.computeMobilemspp(); + if (bs.mobilemspp != 0) { + mMobilemsppList.add(bs); + } + } + } + Collections.sort(mMobilemsppList, new Comparator<BatterySipper>() { + @Override + public int compare(BatterySipper lhs, BatterySipper rhs) { + if (lhs.mobilemspp < rhs.mobilemspp) { + return 1; + } else if (lhs.mobilemspp > rhs.mobilemspp) { + return -1; + } + return 0; + } + }); + + processMiscUsage(); + + if (DEBUG) { + Log.d(TAG, "Accuracy: total computed=" + makemAh(mComputedPower) + ", min discharge=" + + makemAh(mMinDrainedPower) + ", max discharge=" + makemAh(mMaxDrainedPower)); + } + mTotalPower = mComputedPower; + if (mStats.getLowDischargeAmountSinceCharge() > 1) { + if (mMinDrainedPower > mComputedPower) { + double amount = mMinDrainedPower - mComputedPower; + mTotalPower = mMinDrainedPower; + addEntryNoTotal(BatterySipper.DrainType.UNACCOUNTED, 0, amount); + } else if (mMaxDrainedPower < mComputedPower) { + double amount = mComputedPower - mMaxDrainedPower; + addEntryNoTotal(BatterySipper.DrainType.OVERCOUNTED, 0, amount); + } + } + + Collections.sort(mUsageList); + } + + private void processAppUsage() { + SensorManager sensorManager = (SensorManager) mContext.getSystemService( + Context.SENSOR_SERVICE); + final int which = mStatsType; + final int speedSteps = mPowerProfile.getNumSpeedSteps(); + final double[] powerCpuNormal = new double[speedSteps]; + final long[] cpuSpeedStepTimes = new long[speedSteps]; + for (int p = 0; p < speedSteps; p++) { + powerCpuNormal[p] = mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_ACTIVE, p); + } + final double mobilePowerPerPacket = getMobilePowerPerPacket(); + final double mobilePowerPerMs = getMobilePowerPerMs(); + final double wifiPowerPerPacket = getWifiPowerPerPacket(); + long appWakelockTimeUs = 0; + BatterySipper osApp = null; + mStatsPeriod = mTypeBatteryRealtime; + SparseArray<? extends Uid> uidStats = mStats.getUidStats(); + final int NU = uidStats.size(); + for (int iu = 0; iu < NU; iu++) { + Uid u = uidStats.valueAt(iu); + double p; // in mAs + double power = 0; // in mAs + double highestDrain = 0; + String packageWithHighestDrain = null; + Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats(); + long cpuTime = 0; + long cpuFgTime = 0; + long wakelockTime = 0; + long gpsTime = 0; + if (processStats.size() > 0) { + // Process CPU time + for (Map.Entry<String, ? extends BatteryStats.Uid.Proc> ent + : processStats.entrySet()) { + Uid.Proc ps = ent.getValue(); + final long userTime = ps.getUserTime(which); + final long systemTime = ps.getSystemTime(which); + final long foregroundTime = ps.getForegroundTime(which); + cpuFgTime += foregroundTime * 10; // convert to millis + final long tmpCpuTime = (userTime + systemTime) * 10; // convert to millis + int totalTimeAtSpeeds = 0; + // Get the total first + for (int step = 0; step < speedSteps; step++) { + cpuSpeedStepTimes[step] = ps.getTimeAtCpuSpeedStep(step, which); + totalTimeAtSpeeds += cpuSpeedStepTimes[step]; + } + if (totalTimeAtSpeeds == 0) totalTimeAtSpeeds = 1; + // Then compute the ratio of time spent at each speed + double processPower = 0; + for (int step = 0; step < speedSteps; step++) { + double ratio = (double) cpuSpeedStepTimes[step] / totalTimeAtSpeeds; + if (DEBUG && ratio != 0) Log.d(TAG, "UID " + u.getUid() + ": CPU step #" + + step + " ratio=" + makemAh(ratio) + " power=" + + makemAh(ratio*tmpCpuTime*powerCpuNormal[step] / (60*60*1000))); + processPower += ratio * tmpCpuTime * powerCpuNormal[step]; + } + cpuTime += tmpCpuTime; + if (DEBUG && processPower != 0) { + Log.d(TAG, String.format("process %s, cpu power=%s", + ent.getKey(), makemAh(processPower / (60*60*1000)))); + } + power += processPower; + if (packageWithHighestDrain == null + || packageWithHighestDrain.startsWith("*")) { + highestDrain = processPower; + packageWithHighestDrain = ent.getKey(); + } else if (highestDrain < processPower + && !ent.getKey().startsWith("*")) { + highestDrain = processPower; + packageWithHighestDrain = ent.getKey(); + } + } + } + if (cpuFgTime > cpuTime) { + if (DEBUG && cpuFgTime > cpuTime + 10000) { + Log.d(TAG, "WARNING! Cputime is more than 10 seconds behind Foreground time"); + } + cpuTime = cpuFgTime; // Statistics may not have been gathered yet. + } + power /= (60*60*1000); + + // Process wake lock usage + Map<String, ? extends BatteryStats.Uid.Wakelock> wakelockStats = u.getWakelockStats(); + for (Map.Entry<String, ? extends BatteryStats.Uid.Wakelock> wakelockEntry + : wakelockStats.entrySet()) { + Uid.Wakelock wakelock = wakelockEntry.getValue(); + // Only care about partial wake locks since full wake locks + // are canceled when the user turns the screen off. + BatteryStats.Timer timer = wakelock.getWakeTime(BatteryStats.WAKE_TYPE_PARTIAL); + if (timer != null) { + wakelockTime += timer.getTotalTimeLocked(mRawRealtime, which); + } + } + appWakelockTimeUs += wakelockTime; + wakelockTime /= 1000; // convert to millis + + // Add cost of holding a wake lock + p = (wakelockTime + * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE)) / (60*60*1000); + if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wake " + + wakelockTime + " power=" + makemAh(p)); + power += p; + + // Add cost of mobile traffic + final long mobileRx = u.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, mStatsType); + final long mobileTx = u.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, mStatsType); + final long mobileRxB = u.getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, mStatsType); + final long mobileTxB = u.getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, mStatsType); + final long mobileActive = u.getMobileRadioActiveTime(mStatsType); + if (mobileActive > 0) { + // We are tracking when the radio is up, so can use the active time to + // determine power use. + mAppMobileActive += mobileActive; + p = (mobilePowerPerMs * mobileActive) / 1000; + } else { + // We are not tracking when the radio is up, so must approximate power use + // based on the number of packets. + p = (mobileRx + mobileTx) * mobilePowerPerPacket; + } + if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": mobile packets " + + (mobileRx+mobileTx) + " active time " + mobileActive + + " power=" + makemAh(p)); + power += p; + + // Add cost of wifi traffic + final long wifiRx = u.getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, mStatsType); + final long wifiTx = u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, mStatsType); + final long wifiRxB = u.getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, mStatsType); + final long wifiTxB = u.getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, mStatsType); + p = (wifiRx + wifiTx) * wifiPowerPerPacket; + if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi packets " + + (mobileRx+mobileTx) + " power=" + makemAh(p)); + power += p; + + // Add cost of keeping WIFI running. + long wifiRunningTimeMs = u.getWifiRunningTime(mRawRealtime, which) / 1000; + mAppWifiRunning += wifiRunningTimeMs; + p = (wifiRunningTimeMs + * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)) / (60*60*1000); + if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi running " + + wifiRunningTimeMs + " power=" + makemAh(p)); + power += p; + + // Add cost of WIFI scans + long wifiScanTimeMs = u.getWifiScanTime(mRawRealtime, which) / 1000; + p = (wifiScanTimeMs + * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_SCAN)) / (60*60*1000); + if (DEBUG) Log.d(TAG, "UID " + u.getUid() + ": wifi scan " + wifiScanTimeMs + + " power=" + makemAh(p)); + power += p; + for (int bin = 0; bin < BatteryStats.Uid.NUM_WIFI_BATCHED_SCAN_BINS; bin++) { + long batchScanTimeMs = u.getWifiBatchedScanTime(bin, mRawRealtime, which) / 1000; + p = ((batchScanTimeMs + * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_BATCHED_SCAN, bin)) + ) / (60*60*1000); + if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi batched scan # " + bin + + " time=" + batchScanTimeMs + " power=" + makemAh(p)); + power += p; + } + + // Process Sensor usage + Map<Integer, ? extends BatteryStats.Uid.Sensor> sensorStats = u.getSensorStats(); + for (Map.Entry<Integer, ? extends BatteryStats.Uid.Sensor> sensorEntry + : sensorStats.entrySet()) { + Uid.Sensor sensor = sensorEntry.getValue(); + int sensorHandle = sensor.getHandle(); + BatteryStats.Timer timer = sensor.getSensorTime(); + long sensorTime = timer.getTotalTimeLocked(mRawRealtime, which) / 1000; + double multiplier = 0; + switch (sensorHandle) { + case Uid.Sensor.GPS: + multiplier = mPowerProfile.getAveragePower(PowerProfile.POWER_GPS_ON); + gpsTime = sensorTime; + break; + default: + List<Sensor> sensorList = sensorManager.getSensorList( + android.hardware.Sensor.TYPE_ALL); + for (android.hardware.Sensor s : sensorList) { + if (s.getHandle() == sensorHandle) { + multiplier = s.getPower(); + break; + } + } + } + p = (multiplier * sensorTime) / (60*60*1000); + if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": sensor #" + sensorHandle + + " time=" + sensorTime + " power=" + makemAh(p)); + power += p; + } + + if (DEBUG && power != 0) Log.d(TAG, String.format("UID %d: total power=%s", + u.getUid(), makemAh(power))); + + // Add the app to the list if it is consuming power + final int userId = UserHandle.getUserId(u.getUid()); + if (power != 0 || u.getUid() == 0) { + BatterySipper app = new BatterySipper(BatterySipper.DrainType.APP, u, + new double[] {power}); + app.cpuTime = cpuTime; + app.gpsTime = gpsTime; + app.wifiRunningTime = wifiRunningTimeMs; + app.cpuFgTime = cpuFgTime; + app.wakeLockTime = wakelockTime; + app.mobileRxPackets = mobileRx; + app.mobileTxPackets = mobileTx; + app.mobileActive = mobileActive / 1000; + app.mobileActiveCount = u.getMobileRadioActiveCount(mStatsType); + app.wifiRxPackets = wifiRx; + app.wifiTxPackets = wifiTx; + app.mobileRxBytes = mobileRxB; + app.mobileTxBytes = mobileTxB; + app.wifiRxBytes = wifiRxB; + app.wifiTxBytes = wifiTxB; + app.packageWithHighestDrain = packageWithHighestDrain; + if (u.getUid() == Process.WIFI_UID) { + mWifiSippers.add(app); + mWifiPower += power; + } else if (u.getUid() == Process.BLUETOOTH_UID) { + mBluetoothSippers.add(app); + mBluetoothPower += power; + } else if (mAsUser != UserHandle.USER_ALL && userId != mAsUser + && UserHandle.getAppId(u.getUid()) >= Process.FIRST_APPLICATION_UID) { + List<BatterySipper> list = mUserSippers.get(userId); + if (list == null) { + list = new ArrayList<BatterySipper>(); + mUserSippers.put(userId, list); + } + list.add(app); + if (power != 0) { + Double userPower = mUserPower.get(userId); + if (userPower == null) { + userPower = power; + } else { + userPower += power; + } + mUserPower.put(userId, userPower); + } + } else { + mUsageList.add(app); + if (power > mMaxPower) mMaxPower = power; + mComputedPower += power; + } + if (u.getUid() == 0) { + osApp = app; + } + } + } + + // The device has probably been awake for longer than the screen on + // time and application wake lock time would account for. Assign + // this remainder to the OS, if possible. + if (osApp != null) { + long wakeTimeMillis = mBatteryUptime / 1000; + wakeTimeMillis -= (appWakelockTimeUs / 1000) + + (mStats.getScreenOnTime(mRawRealtime, which) / 1000); + if (wakeTimeMillis > 0) { + double power = (wakeTimeMillis + * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE)) + / (60*60*1000); + if (DEBUG) Log.d(TAG, "OS wakeLockTime " + wakeTimeMillis + " power " + + makemAh(power)); + osApp.wakeLockTime += wakeTimeMillis; + osApp.value += power; + osApp.values[0] += power; + if (osApp.value > mMaxPower) mMaxPower = osApp.value; + mComputedPower += power; + } + } + } + + private void addPhoneUsage() { + long phoneOnTimeMs = mStats.getPhoneOnTime(mRawRealtime, mStatsType) / 1000; + double phoneOnPower = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE) + * phoneOnTimeMs / (60*60*1000); + if (phoneOnPower != 0) { + BatterySipper bs = addEntry(BatterySipper.DrainType.PHONE, phoneOnTimeMs, phoneOnPower); + } + } + + private void addScreenUsage() { + double power = 0; + long screenOnTimeMs = mStats.getScreenOnTime(mRawRealtime, mStatsType) / 1000; + power += screenOnTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_ON); + final double screenFullPower = + mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL); + for (int i = 0; i < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; i++) { + double screenBinPower = screenFullPower * (i + 0.5f) + / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; + long brightnessTime = mStats.getScreenBrightnessTime(i, mRawRealtime, mStatsType) + / 1000; + double p = screenBinPower*brightnessTime; + if (DEBUG && p != 0) { + Log.d(TAG, "Screen bin #" + i + ": time=" + brightnessTime + + " power=" + makemAh(p / (60 * 60 * 1000))); + } + power += p; + } + power /= (60*60*1000); // To hours + if (power != 0) { + addEntry(BatterySipper.DrainType.SCREEN, screenOnTimeMs, power); + } + } + + private void addRadioUsage() { + double power = 0; + final int BINS = SignalStrength.NUM_SIGNAL_STRENGTH_BINS; + long signalTimeMs = 0; + long noCoverageTimeMs = 0; + for (int i = 0; i < BINS; i++) { + long strengthTimeMs = mStats.getPhoneSignalStrengthTime(i, mRawRealtime, mStatsType) + / 1000; + double p = (strengthTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ON, i)) + / (60*60*1000); + if (DEBUG && p != 0) { + Log.d(TAG, "Cell strength #" + i + ": time=" + strengthTimeMs + " power=" + + makemAh(p)); + } + power += p; + signalTimeMs += strengthTimeMs; + if (i == 0) { + noCoverageTimeMs = strengthTimeMs; + } + } + long scanningTimeMs = mStats.getPhoneSignalScanningTime(mRawRealtime, mStatsType) + / 1000; + double p = (scanningTimeMs * mPowerProfile.getAveragePower( + PowerProfile.POWER_RADIO_SCANNING)) + / (60*60*1000); + if (DEBUG && p != 0) { + Log.d(TAG, "Cell radio scanning: time=" + scanningTimeMs + " power=" + makemAh(p)); + } + power += p; + long radioActiveTimeUs = mStats.getMobileRadioActiveTime(mRawRealtime, mStatsType); + long remainingActiveTime = (radioActiveTimeUs - mAppMobileActive) / 1000; + if (remainingActiveTime > 0) { + power += getMobilePowerPerMs() * remainingActiveTime; + } + if (power != 0) { + BatterySipper bs = + addEntry(BatterySipper.DrainType.CELL, signalTimeMs, power); + if (signalTimeMs != 0) { + bs.noCoveragePercent = noCoverageTimeMs * 100.0 / signalTimeMs; + } + bs.mobileActive = remainingActiveTime; + bs.mobileActiveCount = mStats.getMobileRadioActiveUnknownCount(mStatsType); + } + } + + private void aggregateSippers(BatterySipper bs, List<BatterySipper> from, String tag) { + for (int i=0; i<from.size(); i++) { + BatterySipper wbs = from.get(i); + if (DEBUG) Log.d(TAG, tag + " adding sipper " + wbs + ": cpu=" + wbs.cpuTime); + bs.cpuTime += wbs.cpuTime; + bs.gpsTime += wbs.gpsTime; + bs.wifiRunningTime += wbs.wifiRunningTime; + bs.cpuFgTime += wbs.cpuFgTime; + bs.wakeLockTime += wbs.wakeLockTime; + bs.mobileRxPackets += wbs.mobileRxPackets; + bs.mobileTxPackets += wbs.mobileTxPackets; + bs.mobileActive += wbs.mobileActive; + bs.mobileActiveCount += wbs.mobileActiveCount; + bs.wifiRxPackets += wbs.wifiRxPackets; + bs.wifiTxPackets += wbs.wifiTxPackets; + bs.mobileRxBytes += wbs.mobileRxBytes; + bs.mobileTxBytes += wbs.mobileTxBytes; + bs.wifiRxBytes += wbs.wifiRxBytes; + bs.wifiTxBytes += wbs.wifiTxBytes; + } + bs.computeMobilemspp(); + } + + private void addWiFiUsage() { + long onTimeMs = mStats.getWifiOnTime(mRawRealtime, mStatsType) / 1000; + long runningTimeMs = mStats.getGlobalWifiRunningTime(mRawRealtime, mStatsType) / 1000; + if (DEBUG) Log.d(TAG, "WIFI runningTime=" + runningTimeMs + + " app runningTime=" + mAppWifiRunning); + runningTimeMs -= mAppWifiRunning; + if (runningTimeMs < 0) runningTimeMs = 0; + double wifiPower = (onTimeMs * 0 /* TODO */ + * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON) + + runningTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)) + / (60*60*1000); + if (DEBUG && wifiPower != 0) { + Log.d(TAG, "Wifi: time=" + runningTimeMs + " power=" + makemAh(wifiPower)); + } + if ((wifiPower+mWifiPower) != 0) { + BatterySipper bs = addEntry(BatterySipper.DrainType.WIFI, runningTimeMs, + wifiPower + mWifiPower); + aggregateSippers(bs, mWifiSippers, "WIFI"); + } + } + + private void addIdleUsage() { + long idleTimeMs = (mTypeBatteryRealtime + - mStats.getScreenOnTime(mRawRealtime, mStatsType)) / 1000; + double idlePower = (idleTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_IDLE)) + / (60*60*1000); + if (DEBUG && idlePower != 0) { + Log.d(TAG, "Idle: time=" + idleTimeMs + " power=" + makemAh(idlePower)); + } + if (idlePower != 0) { + addEntry(BatterySipper.DrainType.IDLE, idleTimeMs, idlePower); + } + } + + private void addBluetoothUsage() { + long btOnTimeMs = mStats.getBluetoothOnTime(mRawRealtime, mStatsType) / 1000; + double btPower = btOnTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_ON) + / (60*60*1000); + if (DEBUG && btPower != 0) { + Log.d(TAG, "Bluetooth: time=" + btOnTimeMs + " power=" + makemAh(btPower)); + } + int btPingCount = mStats.getBluetoothPingCount(); + double pingPower = (btPingCount + * mPowerProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_AT_CMD)) + / (60*60*1000); + if (DEBUG && pingPower != 0) { + Log.d(TAG, "Bluetooth ping: count=" + btPingCount + " power=" + makemAh(pingPower)); + } + btPower += pingPower; + if ((btPower+mBluetoothPower) != 0) { + BatterySipper bs = addEntry(BatterySipper.DrainType.BLUETOOTH, btOnTimeMs, + btPower + mBluetoothPower); + aggregateSippers(bs, mBluetoothSippers, "Bluetooth"); + } + } + + private void addUserUsage() { + for (int i=0; i<mUserSippers.size(); i++) { + final int userId = mUserSippers.keyAt(i); + final List<BatterySipper> sippers = mUserSippers.valueAt(i); + Double userPower = mUserPower.get(userId); + double power = (userPower != null) ? userPower : 0.0; + BatterySipper bs = addEntry(BatterySipper.DrainType.USER, 0, power); + bs.userId = userId; + aggregateSippers(bs, sippers, "User"); + } + } + + /** + * Return estimated power (in mAs) of sending or receiving a packet with the mobile radio. + */ + private double getMobilePowerPerPacket() { + final long MOBILE_BPS = 200000; // TODO: Extract average bit rates from system + final double MOBILE_POWER = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE) + / 3600; + + final long mobileRx = mStats.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, mStatsType); + final long mobileTx = mStats.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, mStatsType); + final long mobileData = mobileRx + mobileTx; + + final long radioDataUptimeMs + = mStats.getMobileRadioActiveTime(mRawRealtime, mStatsType) / 1000; + final double mobilePps = (mobileData != 0 && radioDataUptimeMs != 0) + ? (mobileData / (double)radioDataUptimeMs) + : (((double)MOBILE_BPS) / 8 / 2048); + + return (MOBILE_POWER / mobilePps) / (60*60); + } + + /** + * Return estimated power (in mAs) of keeping the radio up + */ + private double getMobilePowerPerMs() { + return mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE) / (60*60*1000); + } + + /** + * Return estimated power (in mAs) of sending a byte with the Wi-Fi radio. + */ + private double getWifiPowerPerPacket() { + final long WIFI_BPS = 1000000; // TODO: Extract average bit rates from system + final double WIFI_POWER = mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ACTIVE) + / 3600; + return (WIFI_POWER / (((double)WIFI_BPS) / 8 / 2048)) / (60*60); + } + + private void processMiscUsage() { + addUserUsage(); + addPhoneUsage(); + addScreenUsage(); + addWiFiUsage(); + addBluetoothUsage(); + addIdleUsage(); // Not including cellular idle power + // Don't compute radio usage if it's a wifi-only device + ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService( + Context.CONNECTIVITY_SERVICE); + if (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)) { + addRadioUsage(); + } + } + + private BatterySipper addEntry(DrainType drainType, long time, double power) { + mComputedPower += power; + return addEntryNoTotal(drainType, time, power); + } + + private BatterySipper addEntryNoTotal(DrainType drainType, long time, double power) { + if (power > mMaxPower) mMaxPower = power; + BatterySipper bs = new BatterySipper(drainType, null, new double[] {power}); + bs.usageTime = time; + mUsageList.add(bs); + return bs; + } + + public List<BatterySipper> getUsageList() { + return mUsageList; + } + + public List<BatterySipper> getMobilemsppList() { + return mMobilemsppList; + } + + public long getStatsPeriod() { return mStatsPeriod; } + + public int getStatsType() { return mStatsType; }; + + public double getMaxPower() { return mMaxPower; } + + public double getTotalPower() { return mTotalPower; } + + public double getComputedPower() { return mComputedPower; } + + public double getMinDrainedPower() { + return mMinDrainedPower; + } + + public double getMaxDrainedPower() { + return mMaxDrainedPower; + } + + private void load() { + if (mBatteryInfo == null) { + return; + } + try { + byte[] data = mBatteryInfo.getStatistics(); + Parcel parcel = Parcel.obtain(); + parcel.unmarshall(data, 0, data.length); + parcel.setDataPosition(0); + BatteryStatsImpl stats = com.android.internal.os.BatteryStatsImpl.CREATOR + .createFromParcel(parcel); + stats.distributeWorkLocked(BatteryStats.STATS_SINCE_CHARGED); + mStats = stats; + } catch (RemoteException e) { + Log.e(TAG, "RemoteException:", e); + } + } +} diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 8728610..26d7f5f 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -16,12 +16,15 @@ package com.android.internal.os; +import static android.net.NetworkStats.UID_ALL; import static com.android.server.NetworkManagementSocketTagger.PROP_QTAGUID_ENABLED; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; +import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkStats; +import android.os.BadParcelableException; import android.os.BatteryManager; import android.os.BatteryStats; import android.os.FileUtils; @@ -44,24 +47,23 @@ import android.util.PrintWriterPrinter; import android.util.Printer; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseBooleanArray; +import android.util.SparseIntArray; import android.util.TimeUtils; import com.android.internal.annotations.GuardedBy; import com.android.internal.net.NetworkStatsFactory; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.JournaledFile; -import com.google.android.collect.Sets; -import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; -import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -85,7 +87,7 @@ public final class BatteryStatsImpl extends BatteryStats { private static final int MAGIC = 0xBA757475; // 'BATSTATS' // Current on-disk Parcel version - private static final int VERSION = 67 + (USE_OLD_HISTORY ? 1000 : 0); + private static final int VERSION = 99 + (USE_OLD_HISTORY ? 1000 : 0); // Maximum number of items we will record in the history. private static final int MAX_HISTORY_ITEMS = 2000; @@ -141,6 +143,11 @@ public final class BatteryStatsImpl extends BatteryStats { private BatteryCallback mCallback; /** + * Mapping isolated uids to the actual owning app uid. + */ + final SparseIntArray mIsolatedUids = new SparseIntArray(); + + /** * The statistics we have collected organized by uids. */ final SparseArray<BatteryStatsImpl.Uid> mUidStats = @@ -167,10 +174,21 @@ public final class BatteryStatsImpl extends BatteryStats { // These are the objects that will want to do something when the device // is unplugged from power. - final ArrayList<Unpluggable> mUnpluggables = new ArrayList<Unpluggable>(); + final TimeBase mOnBatteryTimeBase = new TimeBase(); + + // These are the objects that will want to do something when the device + // is unplugged from power *and* the screen is off. + final TimeBase mOnBatteryScreenOffTimeBase = new TimeBase(); + + // Set to true when we want to distribute CPU across wakelocks for the next + // CPU update, even if we aren't currently running wake locks. + boolean mDistributeWakelockCpu; boolean mShuttingDown; + HashMap<String, SparseBooleanArray>[] mActiveEvents + = (HashMap<String, SparseBooleanArray>[]) new HashMap[HistoryItem.EVENT_COUNT]; + long mHistoryBaseTime; boolean mHaveBatteryLevel = false; boolean mRecordingHistory = true; @@ -182,6 +200,12 @@ public final class BatteryStatsImpl extends BatteryStats { final HistoryItem mHistoryLastWritten = new HistoryItem(); final HistoryItem mHistoryLastLastWritten = new HistoryItem(); final HistoryItem mHistoryReadTmp = new HistoryItem(); + final HashMap<HistoryTag, Integer> mHistoryTagPool = new HashMap<HistoryTag, Integer>(); + String[] mReadHistoryStrings; + int[] mReadHistoryUids; + int mReadHistoryChars; + int mNextHistoryTagIdx = 0; + int mNumHistoryTagChars = 0; int mHistoryBufferLastPos = -1; boolean mHistoryOverflow = false; long mLastHistoryTime = 0; @@ -199,10 +223,7 @@ public final class BatteryStatsImpl extends BatteryStats { int mStartCount; - long mBatteryUptime; - long mBatteryLastUptime; - long mBatteryRealtime; - long mBatteryLastRealtime; + long mStartClockTime; long mUptime; long mUptimeStart; @@ -211,6 +232,9 @@ public final class BatteryStatsImpl extends BatteryStats { long mRealtimeStart; long mLastRealtime; + int mWakeLockNesting; + boolean mWakeLockImportant; + boolean mScreenOn; StopwatchTimer mScreenOnTimer; @@ -239,19 +263,32 @@ public final class BatteryStatsImpl extends BatteryStats { final StopwatchTimer[] mPhoneDataConnectionsTimer = new StopwatchTimer[NUM_DATA_CONNECTION_TYPES]; - final LongSamplingCounter[] mNetworkActivityCounters = + final LongSamplingCounter[] mNetworkByteActivityCounters = + new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES]; + final LongSamplingCounter[] mNetworkPacketActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES]; boolean mWifiOn; StopwatchTimer mWifiOnTimer; - int mWifiOnUid = -1; boolean mGlobalWifiRunning; StopwatchTimer mGlobalWifiRunningTimer; + int mWifiState = -1; + final StopwatchTimer[] mWifiStateTimer = new StopwatchTimer[NUM_WIFI_STATES]; + boolean mBluetoothOn; StopwatchTimer mBluetoothOnTimer; + int mBluetoothState = -1; + final StopwatchTimer[] mBluetoothStateTimer = new StopwatchTimer[NUM_BLUETOOTH_STATES]; + + boolean mMobileRadioActive; + StopwatchTimer mMobileRadioActiveTimer; + StopwatchTimer mMobileRadioActivePerAppTimer; + LongSamplingCounter mMobileRadioActiveUnknownTime; + LongSamplingCounter mMobileRadioActiveUnknownCount; + /** Bluetooth headset object */ BluetoothHeadset mBtHeadset; @@ -261,13 +298,6 @@ public final class BatteryStatsImpl extends BatteryStats { */ boolean mOnBattery; boolean mOnBatteryInternal; - long mTrackBatteryPastUptime; - long mTrackBatteryUptimeStart; - long mTrackBatteryPastRealtime; - long mTrackBatteryRealtimeStart; - - long mUnpluggedBatteryUptime; - long mUnpluggedBatteryRealtime; /* * These keep track of battery levels (1-100) at the last plug event and the last unplug event. @@ -286,9 +316,6 @@ public final class BatteryStatsImpl extends BatteryStats { long mLastWriteTime = 0; // Milliseconds - private long mRadioDataUptime; - private long mRadioDataStart; - private int mBluetoothPingCount; private int mBluetoothPingStart = -1; @@ -340,15 +367,18 @@ public final class BatteryStatsImpl extends BatteryStats { private final Map<String, KernelWakelockStats> mProcWakelockFileStats = new HashMap<String, KernelWakelockStats>(); - private HashMap<String, Integer> mUidCache = new HashMap<String, Integer>(); - private final NetworkStatsFactory mNetworkStatsFactory = new NetworkStatsFactory(); - private NetworkStats mLastSnapshot; + private NetworkStats mCurMobileSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), 50); + private NetworkStats mLastMobileSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), 50); + private NetworkStats mCurWifiSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), 50); + private NetworkStats mLastWifiSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), 50); + private NetworkStats mTmpNetworkStats; + private final NetworkStats.Entry mTmpNetworkStatsEntry = new NetworkStats.Entry(); @GuardedBy("this") - private HashSet<String> mMobileIfaces = Sets.newHashSet(); + private String[] mMobileIfaces = new String[0]; @GuardedBy("this") - private HashSet<String> mWifiIfaces = Sets.newHashSet(); + private String[] mWifiIfaces = new String[0]; // For debugging public BatteryStatsImpl() { @@ -356,35 +386,235 @@ public final class BatteryStatsImpl extends BatteryStats { mHandler = null; } - public static interface Unpluggable { - void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime); - void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime); + public static interface TimeBaseObs { + void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime); + void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime); + } + + static class TimeBase { + private final ArrayList<TimeBaseObs> mObservers = new ArrayList<TimeBaseObs>(); + + private long mUptime; + private long mLastUptime; + private long mRealtime; + private long mLastRealtime; + + private boolean mRunning; + + private long mPastUptime; + private long mUptimeStart; + private long mPastRealtime; + private long mRealtimeStart; + private long mUnpluggedUptime; + private long mUnpluggedRealtime; + + public void dump(PrintWriter pw, String prefix) { + StringBuilder sb = new StringBuilder(128); + pw.print(prefix); pw.print("mRunning="); pw.println(mRunning); + sb.setLength(0); + sb.append(prefix); + sb.append("mUptime="); + formatTimeMs(sb, mUptime / 1000); sb.append("mLastUptime="); + formatTimeMs(sb, mLastUptime / 1000); + pw.println(sb.toString()); + sb.setLength(0); + sb.append(prefix); + sb.append("mRealtime="); + formatTimeMs(sb, mRealtime / 1000); sb.append("mLastRealtime="); + formatTimeMs(sb, mLastRealtime / 1000); + pw.println(sb.toString()); + sb.setLength(0); + sb.append(prefix); + sb.append("mPastUptime="); + formatTimeMs(sb, mPastUptime / 1000); sb.append("mUptimeStart="); + formatTimeMs(sb, mUptimeStart / 1000); + sb.append("mUnpluggedUptime="); formatTimeMs(sb, mUnpluggedUptime / 1000); + pw.println(sb.toString()); + sb.setLength(0); + sb.append(prefix); + sb.append("mPastRealtime="); + formatTimeMs(sb, mPastRealtime / 1000); sb.append("mRealtimeStart="); + formatTimeMs(sb, mRealtimeStart / 1000); + sb.append("mUnpluggedRealtime="); formatTimeMs(sb, mUnpluggedRealtime / 1000); + pw.println(sb.toString()); + } + + public void add(TimeBaseObs observer) { + mObservers.add(observer); + } + + public void remove(TimeBaseObs observer) { + if (!mObservers.remove(observer)) { + Slog.wtf(TAG, "Removed unknown observer: " + observer); + } + } + + public void init(long uptime, long realtime) { + mRealtime = 0; + mUptime = 0; + mPastUptime = 0; + mPastRealtime = 0; + mUptimeStart = uptime; + mRealtimeStart = realtime; + mUnpluggedUptime = getUptime(mUptimeStart); + mUnpluggedRealtime = getRealtime(mRealtimeStart); + } + + public void reset(long uptime, long realtime) { + if (!mRunning) { + mPastUptime = 0; + mPastRealtime = 0; + } else { + mUptimeStart = uptime; + mRealtimeStart = realtime; + mUnpluggedUptime = getUptime(uptime); + mUnpluggedRealtime = getRealtime(realtime); + } + } + + public long computeUptime(long curTime, int which) { + switch (which) { + case STATS_SINCE_CHARGED: + return mUptime + getUptime(curTime); + case STATS_LAST: + return mLastUptime; + case STATS_CURRENT: + return getUptime(curTime); + case STATS_SINCE_UNPLUGGED: + return getUptime(curTime) - mUnpluggedUptime; + } + return 0; + } + + public long computeRealtime(long curTime, int which) { + switch (which) { + case STATS_SINCE_CHARGED: + return mRealtime + getRealtime(curTime); + case STATS_LAST: + return mLastRealtime; + case STATS_CURRENT: + return getRealtime(curTime); + case STATS_SINCE_UNPLUGGED: + return getRealtime(curTime) - mUnpluggedRealtime; + } + return 0; + } + + public long getUptime(long curTime) { + long time = mPastUptime; + if (mRunning) { + time += curTime - mUptimeStart; + } + return time; + } + + public long getRealtime(long curTime) { + long time = mPastRealtime; + if (mRunning) { + time += curTime - mRealtimeStart; + } + return time; + } + + public long getUptimeStart() { + return mUptimeStart; + } + + public long getRealtimeStart() { + return mRealtimeStart; + } + + public boolean isRunning() { + return mRunning; + } + + public boolean setRunning(boolean running, long uptime, long realtime) { + if (mRunning != running) { + mRunning = running; + if (running) { + mUptimeStart = uptime; + mRealtimeStart = realtime; + long batteryUptime = mUnpluggedUptime = getUptime(uptime); + long batteryRealtime = mUnpluggedRealtime = getRealtime(realtime); + + for (int i = mObservers.size() - 1; i >= 0; i--) { + mObservers.get(i).onTimeStarted(realtime, batteryUptime, batteryRealtime); + } + } else { + mPastUptime += uptime - mUptimeStart; + mPastRealtime += realtime - mRealtimeStart; + + long batteryUptime = getUptime(uptime); + long batteryRealtime = getRealtime(realtime); + + for (int i = mObservers.size() - 1; i >= 0; i--) { + mObservers.get(i).onTimeStopped(realtime, batteryUptime, batteryRealtime); + } + } + return true; + } + return false; + } + + public void readSummaryFromParcel(Parcel in) { + mUptime = in.readLong(); + mRealtime = in.readLong(); + } + + public void writeSummaryToParcel(Parcel out, long uptime, long realtime) { + out.writeLong(computeUptime(uptime, STATS_SINCE_CHARGED)); + out.writeLong(computeRealtime(realtime, STATS_SINCE_CHARGED)); + } + + public void readFromParcel(Parcel in) { + mRunning = false; + mUptime = in.readLong(); + mLastUptime = 0; + mPastUptime = in.readLong(); + mUptimeStart = in.readLong(); + mPastRealtime = in.readLong(); + mRealtimeStart = in.readLong(); + mUnpluggedUptime = in.readLong(); + mUnpluggedRealtime = in.readLong(); + } + + public void writeToParcel(Parcel out, long uptime, long realtime) { + final long runningUptime = getUptime(uptime); + final long runningRealtime = getRealtime(realtime); + out.writeLong(mUptime); + out.writeLong(runningUptime); + out.writeLong(mUptimeStart); + out.writeLong(runningRealtime); + out.writeLong(mRealtimeStart); + out.writeLong(mUnpluggedUptime); + out.writeLong(mUnpluggedRealtime); + } } /** * State for keeping track of counting information. */ - public static class Counter extends BatteryStats.Counter implements Unpluggable { + public static class Counter extends BatteryStats.Counter implements TimeBaseObs { final AtomicInteger mCount = new AtomicInteger(); - final ArrayList<Unpluggable> mUnpluggables; + final TimeBase mTimeBase; int mLoadedCount; int mLastCount; int mUnpluggedCount; int mPluggedCount; - Counter(ArrayList<Unpluggable> unpluggables, Parcel in) { - mUnpluggables = unpluggables; + Counter(TimeBase timeBase, Parcel in) { + mTimeBase = timeBase; mPluggedCount = in.readInt(); mCount.set(mPluggedCount); mLoadedCount = in.readInt(); mLastCount = 0; mUnpluggedCount = in.readInt(); - unpluggables.add(this); + timeBase.add(this); } - Counter(ArrayList<Unpluggable> unpluggables) { - mUnpluggables = unpluggables; - unpluggables.add(this); + Counter(TimeBase timeBase) { + mTimeBase = timeBase; + timeBase.add(this); } public void writeToParcel(Parcel out) { @@ -393,12 +623,12 @@ public final class BatteryStatsImpl extends BatteryStats { out.writeInt(mUnpluggedCount); } - public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) { + public void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime) { mUnpluggedCount = mPluggedCount; mCount.set(mPluggedCount); } - public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) { + public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) { mPluggedCount = mCount.get(); } @@ -458,7 +688,7 @@ public final class BatteryStatsImpl extends BatteryStats { } void detach() { - mUnpluggables.remove(this); + mTimeBase.remove(this); } void writeSummaryFromParcelLocked(Parcel out) { @@ -475,12 +705,12 @@ public final class BatteryStatsImpl extends BatteryStats { } public static class SamplingCounter extends Counter { - SamplingCounter(ArrayList<Unpluggable> unpluggables, Parcel in) { - super(unpluggables, in); + SamplingCounter(TimeBase timeBase, Parcel in) { + super(timeBase, in); } - SamplingCounter(ArrayList<Unpluggable> unpluggables) { - super(unpluggables); + SamplingCounter(TimeBase timeBase) { + super(timeBase); } public void addCountAtomic(long count) { @@ -488,27 +718,27 @@ public final class BatteryStatsImpl extends BatteryStats { } } - public static class LongSamplingCounter implements Unpluggable { - final ArrayList<Unpluggable> mUnpluggables; + public static class LongSamplingCounter implements TimeBaseObs { + final TimeBase mTimeBase; long mCount; long mLoadedCount; long mLastCount; long mUnpluggedCount; long mPluggedCount; - LongSamplingCounter(ArrayList<Unpluggable> unpluggables, Parcel in) { - mUnpluggables = unpluggables; + LongSamplingCounter(TimeBase timeBase, Parcel in) { + mTimeBase = timeBase; mPluggedCount = in.readLong(); mCount = mPluggedCount; mLoadedCount = in.readLong(); mLastCount = 0; mUnpluggedCount = in.readLong(); - unpluggables.add(this); + timeBase.add(this); } - LongSamplingCounter(ArrayList<Unpluggable> unpluggables) { - mUnpluggables = unpluggables; - unpluggables.add(this); + LongSamplingCounter(TimeBase timeBase) { + mTimeBase = timeBase; + timeBase.add(this); } public void writeToParcel(Parcel out) { @@ -518,13 +748,13 @@ public final class BatteryStatsImpl extends BatteryStats { } @Override - public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) { + public void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime) { mUnpluggedCount = mPluggedCount; mCount = mPluggedCount; } @Override - public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) { + public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) { mPluggedCount = mCount; } @@ -560,7 +790,7 @@ public final class BatteryStatsImpl extends BatteryStats { } void detach() { - mUnpluggables.remove(this); + mTimeBase.remove(this); } void writeSummaryFromParcelLocked(Parcel out) { @@ -578,9 +808,9 @@ public final class BatteryStatsImpl extends BatteryStats { /** * State for keeping track of timing information. */ - public static abstract class Timer extends BatteryStats.Timer implements Unpluggable { + public static abstract class Timer extends BatteryStats.Timer implements TimeBaseObs { final int mType; - final ArrayList<Unpluggable> mUnpluggables; + final TimeBase mTimeBase; int mCount; int mLoadedCount; @@ -619,12 +849,12 @@ public final class BatteryStatsImpl extends BatteryStats { /** * Constructs from a parcel. * @param type - * @param unpluggables + * @param timeBase * @param in */ - Timer(int type, ArrayList<Unpluggable> unpluggables, Parcel in) { + Timer(int type, TimeBase timeBase, Parcel in) { mType = type; - mUnpluggables = unpluggables; + mTimeBase = timeBase; mCount = in.readInt(); mLoadedCount = in.readInt(); @@ -634,13 +864,13 @@ public final class BatteryStatsImpl extends BatteryStats { mLoadedTime = in.readLong(); mLastTime = 0; mUnpluggedTime = in.readLong(); - unpluggables.add(this); + timeBase.add(this); } - Timer(int type, ArrayList<Unpluggable> unpluggables) { + Timer(int type, TimeBase timeBase) { mType = type; - mUnpluggables = unpluggables; - unpluggables.add(this); + mTimeBase = timeBase; + timeBase.add(this); } protected abstract long computeRunTimeLocked(long curBatteryRealtime); @@ -651,7 +881,7 @@ public final class BatteryStatsImpl extends BatteryStats { * Clear state of this timer. Returns true if the timer is inactive * so can be completely dropped. */ - boolean reset(BatteryStatsImpl stats, boolean detachIfReset) { + boolean reset(boolean detachIfReset) { mTotalTime = mLoadedTime = mLastTime = 0; mCount = mLoadedCount = mLastCount = 0; if (detachIfReset) { @@ -661,25 +891,25 @@ public final class BatteryStatsImpl extends BatteryStats { } void detach() { - mUnpluggables.remove(this); + mTimeBase.remove(this); } - public void writeToParcel(Parcel out, long batteryRealtime) { + public void writeToParcel(Parcel out, long elapsedRealtimeUs) { out.writeInt(mCount); out.writeInt(mLoadedCount); out.writeInt(mUnpluggedCount); - out.writeLong(computeRunTimeLocked(batteryRealtime)); + out.writeLong(computeRunTimeLocked(mTimeBase.getRealtime(elapsedRealtimeUs))); out.writeLong(mLoadedTime); out.writeLong(mUnpluggedTime); } - public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) { + public void onTimeStarted(long elapsedRealtime, long timeBaseUptime, long baseRealtime) { if (DEBUG && mType < 0) { - Log.v(TAG, "unplug #" + mType + ": realtime=" + batteryRealtime + Log.v(TAG, "unplug #" + mType + ": realtime=" + baseRealtime + " old mUnpluggedTime=" + mUnpluggedTime + " old mUnpluggedCount=" + mUnpluggedCount); } - mUnpluggedTime = computeRunTimeLocked(batteryRealtime); + mUnpluggedTime = computeRunTimeLocked(baseRealtime); mUnpluggedCount = mCount; if (DEBUG && mType < 0) { Log.v(TAG, "unplug #" + mType @@ -688,12 +918,12 @@ public final class BatteryStatsImpl extends BatteryStats { } } - public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) { + public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) { if (DEBUG && mType < 0) { - Log.v(TAG, "plug #" + mType + ": realtime=" + batteryRealtime + Log.v(TAG, "plug #" + mType + ": realtime=" + baseRealtime + " old mTotalTime=" + mTotalTime); } - mTotalTime = computeRunTimeLocked(batteryRealtime); + mTotalTime = computeRunTimeLocked(baseRealtime); mCount = computeCurrentCountLocked(); if (DEBUG && mType < 0) { Log.v(TAG, "plug #" + mType @@ -707,24 +937,23 @@ public final class BatteryStatsImpl extends BatteryStats { * @param out the Parcel to be written to. * @param timer a Timer, or null. */ - public static void writeTimerToParcel(Parcel out, Timer timer, - long batteryRealtime) { + public static void writeTimerToParcel(Parcel out, Timer timer, long elapsedRealtimeUs) { if (timer == null) { out.writeInt(0); // indicates null return; } out.writeInt(1); // indicates non-null - timer.writeToParcel(out, batteryRealtime); + timer.writeToParcel(out, elapsedRealtimeUs); } @Override - public long getTotalTimeLocked(long batteryRealtime, int which) { + public long getTotalTimeLocked(long elapsedRealtimeUs, int which) { long val; if (which == STATS_LAST) { val = mLastTime; } else { - val = computeRunTimeLocked(batteryRealtime); + val = computeRunTimeLocked(mTimeBase.getRealtime(elapsedRealtimeUs)); if (which == STATS_SINCE_UNPLUGGED) { val -= mUnpluggedTime; } else if (which != STATS_SINCE_CHARGED) { @@ -763,16 +992,15 @@ public final class BatteryStatsImpl extends BatteryStats { } - void writeSummaryFromParcelLocked(Parcel out, long batteryRealtime) { - long runTime = computeRunTimeLocked(batteryRealtime); - // Divide by 1000 for backwards compatibility - out.writeLong((runTime + 500) / 1000); + void writeSummaryFromParcelLocked(Parcel out, long elapsedRealtimeUs) { + long runTime = computeRunTimeLocked(mTimeBase.getRealtime(elapsedRealtimeUs)); + out.writeLong(runTime); out.writeInt(mCount); } void readSummaryFromParcelLocked(Parcel in) { // Multiply by 1000 for backwards compatibility - mTotalTime = mLoadedTime = in.readLong() * 1000; + mTotalTime = mLoadedTime = in.readLong(); mLastTime = 0; mUnpluggedTime = mTotalTime; mCount = mLoadedCount = in.readInt(); @@ -809,7 +1037,7 @@ public final class BatteryStatsImpl extends BatteryStats { /** * Whether we are currently in a discharge cycle. */ - boolean mInDischarge; + boolean mTimeBaseRunning; /** * Whether we are currently recording reported values. @@ -821,21 +1049,20 @@ public final class BatteryStatsImpl extends BatteryStats { */ int mUpdateVersion; - SamplingTimer(ArrayList<Unpluggable> unpluggables, boolean inDischarge, Parcel in) { - super(0, unpluggables, in); + SamplingTimer(TimeBase timeBase, Parcel in) { + super(0, timeBase, in); mCurrentReportedCount = in.readInt(); mUnpluggedReportedCount = in.readInt(); mCurrentReportedTotalTime = in.readLong(); mUnpluggedReportedTotalTime = in.readLong(); mTrackingReportedValues = in.readInt() == 1; - mInDischarge = inDischarge; + mTimeBaseRunning = timeBase.isRunning(); } - SamplingTimer(ArrayList<Unpluggable> unpluggables, boolean inDischarge, - boolean trackReportedValues) { - super(0, unpluggables); + SamplingTimer(TimeBase timeBase, boolean trackReportedValues) { + super(0, timeBase); mTrackingReportedValues = trackReportedValues; - mInDischarge = inDischarge; + mTimeBaseRunning = timeBase.isRunning(); } public void setStale() { @@ -853,7 +1080,7 @@ public final class BatteryStatsImpl extends BatteryStats { } public void updateCurrentReportedCount(int count) { - if (mInDischarge && mUnpluggedReportedCount == 0) { + if (mTimeBaseRunning && mUnpluggedReportedCount == 0) { // Updating the reported value for the first time. mUnpluggedReportedCount = count; // If we are receiving an update update mTrackingReportedValues; @@ -863,7 +1090,7 @@ public final class BatteryStatsImpl extends BatteryStats { } public void updateCurrentReportedTotalTime(long totalTime) { - if (mInDischarge && mUnpluggedReportedTotalTime == 0) { + if (mTimeBaseRunning && mUnpluggedReportedTotalTime == 0) { // Updating the reported value for the first time. mUnpluggedReportedTotalTime = totalTime; // If we are receiving an update update mTrackingReportedValues; @@ -872,18 +1099,18 @@ public final class BatteryStatsImpl extends BatteryStats { mCurrentReportedTotalTime = totalTime; } - public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) { - super.unplug(elapsedRealtime, batteryUptime, batteryRealtime); + public void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime) { + super.onTimeStarted(elapsedRealtime, baseUptime, baseRealtime); if (mTrackingReportedValues) { mUnpluggedReportedTotalTime = mCurrentReportedTotalTime; mUnpluggedReportedCount = mCurrentReportedCount; } - mInDischarge = true; + mTimeBaseRunning = true; } - public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) { - super.plug(elapsedRealtime, batteryUptime, batteryRealtime); - mInDischarge = false; + public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) { + super.onTimeStopped(elapsedRealtime, baseUptime, baseRealtime); + mTimeBaseRunning = false; } public void logState(Printer pw, String prefix) { @@ -895,17 +1122,17 @@ public final class BatteryStatsImpl extends BatteryStats { } protected long computeRunTimeLocked(long curBatteryRealtime) { - return mTotalTime + (mInDischarge && mTrackingReportedValues + return mTotalTime + (mTimeBaseRunning && mTrackingReportedValues ? mCurrentReportedTotalTime - mUnpluggedReportedTotalTime : 0); } protected int computeCurrentCountLocked() { - return mCount + (mInDischarge && mTrackingReportedValues + return mCount + (mTimeBaseRunning && mTrackingReportedValues ? mCurrentReportedCount - mUnpluggedReportedCount : 0); } - public void writeToParcel(Parcel out, long batteryRealtime) { - super.writeToParcel(out, batteryRealtime); + public void writeToParcel(Parcel out, long elapsedRealtimeUs) { + super.writeToParcel(out, elapsedRealtimeUs); out.writeInt(mCurrentReportedCount); out.writeInt(mUnpluggedReportedCount); out.writeLong(mCurrentReportedTotalTime); @@ -913,8 +1140,8 @@ public final class BatteryStatsImpl extends BatteryStats { out.writeInt(mTrackingReportedValues ? 1 : 0); } - boolean reset(BatteryStatsImpl stats, boolean detachIfReset) { - super.reset(stats, detachIfReset); + boolean reset(boolean detachIfReset) { + super.reset(detachIfReset); setStale(); return true; } @@ -956,45 +1183,43 @@ public final class BatteryStatsImpl extends BatteryStats { */ boolean mInDischarge; - BatchTimer(Uid uid, int type, ArrayList<Unpluggable> unpluggables, - boolean inDischarge, Parcel in) { - super(type, unpluggables, in); + BatchTimer(Uid uid, int type, TimeBase timeBase, Parcel in) { + super(type, timeBase, in); mUid = uid; mLastAddedTime = in.readLong(); mLastAddedDuration = in.readLong(); - mInDischarge = inDischarge; + mInDischarge = timeBase.isRunning(); } - BatchTimer(Uid uid, int type, ArrayList<Unpluggable> unpluggables, - boolean inDischarge) { - super(type, unpluggables); + BatchTimer(Uid uid, int type, TimeBase timeBase) { + super(type, timeBase); mUid = uid; - mInDischarge = inDischarge; + mInDischarge = timeBase.isRunning(); } @Override - public void writeToParcel(Parcel out, long batteryRealtime) { - super.writeToParcel(out, batteryRealtime); + public void writeToParcel(Parcel out, long elapsedRealtimeUs) { + super.writeToParcel(out, elapsedRealtimeUs); out.writeLong(mLastAddedTime); out.writeLong(mLastAddedDuration); } @Override - public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) { + public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) { recomputeLastDuration(SystemClock.elapsedRealtime() * 1000, false); mInDischarge = false; - super.plug(elapsedRealtime, batteryUptime, batteryRealtime); + super.onTimeStopped(elapsedRealtime, baseUptime, baseRealtime); } @Override - public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) { + public void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime) { recomputeLastDuration(elapsedRealtime, false); mInDischarge = true; // If we are still within the last added duration, then re-added whatever remains. if (mLastAddedTime == elapsedRealtime) { mTotalTime += mLastAddedDuration; } - super.unplug(elapsedRealtime, batteryUptime, batteryRealtime); + super.onTimeStarted(elapsedRealtime, baseUptime, baseRealtime); } @Override @@ -1060,11 +1285,11 @@ public final class BatteryStatsImpl extends BatteryStats { } @Override - boolean reset(BatteryStatsImpl stats, boolean detachIfReset) { + boolean reset(boolean detachIfReset) { final long now = SystemClock.elapsedRealtime() * 1000; recomputeLastDuration(now, true); boolean stillActive = mLastAddedTime == now; - super.reset(stats, !stillActive && detachIfReset); + super.reset(!stillActive && detachIfReset); return !stillActive; } } @@ -1100,16 +1325,16 @@ public final class BatteryStatsImpl extends BatteryStats { boolean mInList; StopwatchTimer(Uid uid, int type, ArrayList<StopwatchTimer> timerPool, - ArrayList<Unpluggable> unpluggables, Parcel in) { - super(type, unpluggables, in); + TimeBase timeBase, Parcel in) { + super(type, timeBase, in); mUid = uid; mTimerPool = timerPool; mUpdateTime = in.readLong(); } StopwatchTimer(Uid uid, int type, ArrayList<StopwatchTimer> timerPool, - ArrayList<Unpluggable> unpluggables) { - super(type, unpluggables); + TimeBase timeBase) { + super(type, timeBase); mUid = uid; mTimerPool = timerPool; } @@ -1118,18 +1343,18 @@ public final class BatteryStatsImpl extends BatteryStats { mTimeout = timeout; } - public void writeToParcel(Parcel out, long batteryRealtime) { - super.writeToParcel(out, batteryRealtime); + public void writeToParcel(Parcel out, long elapsedRealtimeUs) { + super.writeToParcel(out, elapsedRealtimeUs); out.writeLong(mUpdateTime); } - public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) { + public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) { if (mNesting > 0) { if (DEBUG && mType < 0) { Log.v(TAG, "old mUpdateTime=" + mUpdateTime); } - super.plug(elapsedRealtime, batteryUptime, batteryRealtime); - mUpdateTime = batteryRealtime; + super.onTimeStopped(elapsedRealtime, baseUptime, baseRealtime); + mUpdateTime = baseRealtime; if (DEBUG && mType < 0) { Log.v(TAG, "new mUpdateTime=" + mUpdateTime); } @@ -1142,14 +1367,14 @@ public final class BatteryStatsImpl extends BatteryStats { + " mAcquireTime=" + mAcquireTime); } - void startRunningLocked(BatteryStatsImpl stats) { + void startRunningLocked(long elapsedRealtimeMs) { if (mNesting++ == 0) { - mUpdateTime = stats.getBatteryRealtimeLocked( - SystemClock.elapsedRealtime() * 1000); + final long batteryRealtime = mTimeBase.getRealtime(elapsedRealtimeMs * 1000); + mUpdateTime = batteryRealtime; if (mTimerPool != null) { // Accumulate time to all currently active timers before adding // this new one to the pool. - refreshTimersLocked(stats, mTimerPool); + refreshTimersLocked(batteryRealtime, mTimerPool, null); // Add this timer to the active pool mTimerPool.add(this); } @@ -1168,21 +1393,35 @@ public final class BatteryStatsImpl extends BatteryStats { return mNesting > 0; } - void stopRunningLocked(BatteryStatsImpl stats) { + long checkpointRunningLocked(long elapsedRealtimeMs) { + if (mNesting > 0) { + // We are running... + final long batteryRealtime = mTimeBase.getRealtime(elapsedRealtimeMs * 1000); + if (mTimerPool != null) { + return refreshTimersLocked(batteryRealtime, mTimerPool, this); + } + final long heldTime = batteryRealtime - mUpdateTime; + mUpdateTime = batteryRealtime; + mTotalTime += heldTime; + return heldTime; + } + return 0; + } + + void stopRunningLocked(long elapsedRealtimeMs) { // Ignore attempt to stop a timer that isn't running if (mNesting == 0) { return; } if (--mNesting == 0) { + final long batteryRealtime = mTimeBase.getRealtime(elapsedRealtimeMs * 1000); if (mTimerPool != null) { // Accumulate time to all active counters, scaled by the total // active in the pool, before taking this one out of the pool. - refreshTimersLocked(stats, mTimerPool); + refreshTimersLocked(batteryRealtime, mTimerPool, null); // Remove this timer from the active pool mTimerPool.remove(this); } else { - final long realtime = SystemClock.elapsedRealtime() * 1000; - final long batteryRealtime = stats.getBatteryRealtimeLocked(realtime); mNesting = 1; mTotalTime = computeRunTimeLocked(batteryRealtime); mNesting = 0; @@ -1204,19 +1443,23 @@ public final class BatteryStatsImpl extends BatteryStats { // Update the total time for all other running Timers with the same type as this Timer // due to a change in timer count - private static void refreshTimersLocked(final BatteryStatsImpl stats, - final ArrayList<StopwatchTimer> pool) { - final long realtime = SystemClock.elapsedRealtime() * 1000; - final long batteryRealtime = stats.getBatteryRealtimeLocked(realtime); + private static long refreshTimersLocked(long batteryRealtime, + final ArrayList<StopwatchTimer> pool, StopwatchTimer self) { + long selfTime = 0; final int N = pool.size(); for (int i=N-1; i>= 0; i--) { final StopwatchTimer t = pool.get(i); long heldTime = batteryRealtime - t.mUpdateTime; if (heldTime > 0) { - t.mTotalTime += heldTime / N; + final long myTime = heldTime / N; + if (t == self) { + selfTime = myTime; + } + t.mTotalTime += myTime; } t.mUpdateTime = batteryRealtime; } + return selfTime; } @Override @@ -1235,12 +1478,11 @@ public final class BatteryStatsImpl extends BatteryStats { return mCount; } - boolean reset(BatteryStatsImpl stats, boolean detachIfReset) { + boolean reset(boolean detachIfReset) { boolean canDetach = mNesting <= 0; - super.reset(stats, canDetach && detachIfReset); + super.reset(canDetach && detachIfReset); if (mNesting > 0) { - mUpdateTime = stats.getBatteryRealtimeLocked( - SystemClock.elapsedRealtime() * 1000); + mUpdateTime = mTimeBase.getRealtime(SystemClock.elapsedRealtime() * 1000); } mAcquireTime = mTotalTime; return canDetach; @@ -1403,51 +1645,12 @@ public final class BatteryStatsImpl extends BatteryStats { public SamplingTimer getKernelWakelockTimerLocked(String name) { SamplingTimer kwlt = mKernelWakelockStats.get(name); if (kwlt == null) { - kwlt = new SamplingTimer(mUnpluggables, mOnBatteryInternal, - true /* track reported values */); + kwlt = new SamplingTimer(mOnBatteryScreenOffTimeBase, true /* track reported values */); mKernelWakelockStats.put(name, kwlt); } return kwlt; } - /** - * Radio uptime in microseconds when transferring data. This value is very approximate. - * @return - */ - private long getCurrentRadioDataUptime() { - try { - File awakeTimeFile = new File("/sys/devices/virtual/net/rmnet0/awake_time_ms"); - if (!awakeTimeFile.exists()) return 0; - BufferedReader br = new BufferedReader(new FileReader(awakeTimeFile)); - String line = br.readLine(); - br.close(); - return Long.parseLong(line) * 1000; - } catch (NumberFormatException nfe) { - // Nothing - } catch (IOException ioe) { - // Nothing - } - return 0; - } - - /** - * @deprecated use getRadioDataUptime - */ - public long getRadioDataUptimeMs() { - return getRadioDataUptime() / 1000; - } - - /** - * Returns the duration that the cell radio was up for data transfers. - */ - public long getRadioDataUptime() { - if (mRadioDataStart == -1) { - return mRadioDataUptime; - } else { - return getCurrentRadioDataUptime() - mRadioDataStart; - } - } - private int getCurrentBluetoothPingCount() { if (mBtHeadset != null) { List<BluetoothDevice> deviceList = mBtHeadset.getConnectedDevices(); @@ -1474,7 +1677,285 @@ public final class BatteryStatsImpl extends BatteryStats { mBtHeadset = headset; } - int mChangedBufferStates = 0; + private int writeHistoryTag(HistoryTag tag) { + Integer idxObj = mHistoryTagPool.get(tag); + int idx; + if (idxObj != null) { + idx = idxObj; + } else { + idx = mNextHistoryTagIdx; + HistoryTag key = new HistoryTag(); + key.setTo(tag); + tag.poolIdx = idx; + mHistoryTagPool.put(key, idx); + mNextHistoryTagIdx++; + mNumHistoryTagChars += key.string.length() + 1; + } + return idx; + } + + private void readHistoryTag(int index, HistoryTag tag) { + tag.string = mReadHistoryStrings[index]; + tag.uid = mReadHistoryUids[index]; + tag.poolIdx = index; + } + + // Part of initial delta int that specifies the time delta. + static final int DELTA_TIME_MASK = 0x7ffff; + static final int DELTA_TIME_LONG = 0x7ffff; // The delta is a following long + static final int DELTA_TIME_INT = 0x7fffe; // The delta is a following int + static final int DELTA_TIME_ABS = 0x7fffd; // Following is an entire abs update. + // Flag in delta int: a new battery level int follows. + static final int DELTA_BATTERY_LEVEL_FLAG = 0x00080000; + // Flag in delta int: a new full state and battery status int follows. + static final int DELTA_STATE_FLAG = 0x00100000; + // Flag in delta int: a new full state2 int follows. + static final int DELTA_STATE2_FLAG = 0x00200000; + // Flag in delta int: contains a wakelock or wakeReason tag. + static final int DELTA_WAKELOCK_FLAG = 0x00400000; + // Flag in delta int: contains an event description. + static final int DELTA_EVENT_FLAG = 0x00800000; + // These upper bits are the frequently changing state bits. + static final int DELTA_STATE_MASK = 0xff000000; + + // These are the pieces of battery state that are packed in to the upper bits of + // the state int that have been packed in to the first delta int. They must fit + // in DELTA_STATE_MASK. + static final int STATE_BATTERY_STATUS_MASK = 0x00000007; + static final int STATE_BATTERY_STATUS_SHIFT = 29; + static final int STATE_BATTERY_HEALTH_MASK = 0x00000007; + static final int STATE_BATTERY_HEALTH_SHIFT = 26; + static final int STATE_BATTERY_PLUG_MASK = 0x00000003; + static final int STATE_BATTERY_PLUG_SHIFT = 24; + + public void writeHistoryDelta(Parcel dest, HistoryItem cur, HistoryItem last) { + if (last == null || cur.cmd != HistoryItem.CMD_UPDATE) { + dest.writeInt(DELTA_TIME_ABS); + cur.writeToParcel(dest, 0); + return; + } + + final long deltaTime = cur.time - last.time; + final int lastBatteryLevelInt = buildBatteryLevelInt(last); + final int lastStateInt = buildStateInt(last); + + int deltaTimeToken; + if (deltaTime < 0 || deltaTime > Integer.MAX_VALUE) { + deltaTimeToken = DELTA_TIME_LONG; + } else if (deltaTime >= DELTA_TIME_ABS) { + deltaTimeToken = DELTA_TIME_INT; + } else { + deltaTimeToken = (int)deltaTime; + } + int firstToken = deltaTimeToken | (cur.states&DELTA_STATE_MASK); + final int batteryLevelInt = buildBatteryLevelInt(cur); + final boolean batteryLevelIntChanged = batteryLevelInt != lastBatteryLevelInt; + if (batteryLevelIntChanged) { + firstToken |= DELTA_BATTERY_LEVEL_FLAG; + } + final int stateInt = buildStateInt(cur); + final boolean stateIntChanged = stateInt != lastStateInt; + if (stateIntChanged) { + firstToken |= DELTA_STATE_FLAG; + } + if (cur.wakelockTag != null || cur.wakeReasonTag != null) { + firstToken |= DELTA_WAKELOCK_FLAG; + } + if (cur.eventCode != HistoryItem.EVENT_NONE) { + firstToken |= DELTA_EVENT_FLAG; + } + dest.writeInt(firstToken); + if (DEBUG) Slog.i(TAG, "WRITE DELTA: firstToken=0x" + Integer.toHexString(firstToken) + + " deltaTime=" + deltaTime); + + if (deltaTimeToken >= DELTA_TIME_INT) { + if (deltaTimeToken == DELTA_TIME_INT) { + if (DEBUG) Slog.i(TAG, "WRITE DELTA: int deltaTime=" + (int)deltaTime); + dest.writeInt((int)deltaTime); + } else { + if (DEBUG) Slog.i(TAG, "WRITE DELTA: long deltaTime=" + deltaTime); + dest.writeLong(deltaTime); + } + } + if (batteryLevelIntChanged) { + dest.writeInt(batteryLevelInt); + if (DEBUG) Slog.i(TAG, "WRITE DELTA: batteryToken=0x" + + Integer.toHexString(batteryLevelInt) + + " batteryLevel=" + cur.batteryLevel + + " batteryTemp=" + cur.batteryTemperature + + " batteryVolt=" + (int)cur.batteryVoltage); + } + if (stateIntChanged) { + dest.writeInt(stateInt); + if (DEBUG) Slog.i(TAG, "WRITE DELTA: stateToken=0x" + + Integer.toHexString(stateInt) + + " batteryStatus=" + cur.batteryStatus + + " batteryHealth=" + cur.batteryHealth + + " batteryPlugType=" + cur.batteryPlugType + + " states=0x" + Integer.toHexString(cur.states)); + } + if (cur.wakelockTag != null || cur.wakeReasonTag != null) { + int wakeLockIndex; + int wakeReasonIndex; + if (cur.wakelockTag != null) { + wakeLockIndex = writeHistoryTag(cur.wakelockTag); + if (DEBUG) Slog.i(TAG, "WRITE DELTA: wakelockTag=#" + cur.wakelockTag.poolIdx + + " " + cur.wakelockTag.uid + ":" + cur.wakelockTag.string); + } else { + wakeLockIndex = 0xffff; + } + if (cur.wakeReasonTag != null) { + wakeReasonIndex = writeHistoryTag(cur.wakeReasonTag); + if (DEBUG) Slog.i(TAG, "WRITE DELTA: wakeReasonTag=#" + cur.wakeReasonTag.poolIdx + + " " + cur.wakeReasonTag.uid + ":" + cur.wakeReasonTag.string); + } else { + wakeReasonIndex = 0xffff; + } + dest.writeInt((wakeReasonIndex<<16) | wakeLockIndex); + } + if (cur.eventCode != HistoryItem.EVENT_NONE) { + int index = writeHistoryTag(cur.eventTag); + int codeAndIndex = (cur.eventCode&0xffff) | (index<<16); + dest.writeInt(codeAndIndex); + if (DEBUG) Slog.i(TAG, "WRITE DELTA: event=" + cur.eventCode + " tag=#" + + cur.eventTag.poolIdx + " " + cur.eventTag.uid + ":" + + cur.eventTag.string); + } + } + + private int buildBatteryLevelInt(HistoryItem h) { + return ((((int)h.batteryLevel)<<25)&0xfe000000) + | ((((int)h.batteryTemperature)<<14)&0x01ffc000) + | (((int)h.batteryVoltage)&0x00003fff); + } + + private int buildStateInt(HistoryItem h) { + int plugType = 0; + if ((h.batteryPlugType&BatteryManager.BATTERY_PLUGGED_AC) != 0) { + plugType = 1; + } else if ((h.batteryPlugType&BatteryManager.BATTERY_PLUGGED_USB) != 0) { + plugType = 2; + } else if ((h.batteryPlugType&BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0) { + plugType = 3; + } + return ((h.batteryStatus&STATE_BATTERY_STATUS_MASK)<<STATE_BATTERY_STATUS_SHIFT) + | ((h.batteryHealth&STATE_BATTERY_HEALTH_MASK)<<STATE_BATTERY_HEALTH_SHIFT) + | ((plugType&STATE_BATTERY_PLUG_MASK)<<STATE_BATTERY_PLUG_SHIFT) + | (h.states&(~DELTA_STATE_MASK)); + } + + public void readHistoryDelta(Parcel src, HistoryItem cur) { + int firstToken = src.readInt(); + int deltaTimeToken = firstToken&DELTA_TIME_MASK; + cur.cmd = HistoryItem.CMD_UPDATE; + cur.numReadInts = 1; + if (DEBUG) Slog.i(TAG, "READ DELTA: firstToken=0x" + Integer.toHexString(firstToken) + + " deltaTimeToken=" + deltaTimeToken); + + if (deltaTimeToken < DELTA_TIME_ABS) { + cur.time += deltaTimeToken; + } else if (deltaTimeToken == DELTA_TIME_ABS) { + cur.time = src.readLong(); + cur.numReadInts += 2; + if (DEBUG) Slog.i(TAG, "READ DELTA: ABS time=" + cur.time); + cur.readFromParcel(src); + return; + } else if (deltaTimeToken == DELTA_TIME_INT) { + int delta = src.readInt(); + cur.time += delta; + cur.numReadInts += 1; + if (DEBUG) Slog.i(TAG, "READ DELTA: time delta=" + delta + " new time=" + cur.time); + } else { + long delta = src.readLong(); + if (DEBUG) Slog.i(TAG, "READ DELTA: time delta=" + delta + " new time=" + cur.time); + cur.time += delta; + cur.numReadInts += 2; + } + + if ((firstToken&DELTA_BATTERY_LEVEL_FLAG) != 0) { + int batteryLevelInt = src.readInt(); + cur.batteryLevel = (byte)((batteryLevelInt>>25)&0x7f); + cur.batteryTemperature = (short)((batteryLevelInt<<7)>>21); + cur.batteryVoltage = (char)(batteryLevelInt&0x3fff); + cur.numReadInts += 1; + if (DEBUG) Slog.i(TAG, "READ DELTA: batteryToken=0x" + + Integer.toHexString(batteryLevelInt) + + " batteryLevel=" + cur.batteryLevel + + " batteryTemp=" + cur.batteryTemperature + + " batteryVolt=" + (int)cur.batteryVoltage); + } + + if ((firstToken&DELTA_STATE_FLAG) != 0) { + int stateInt = src.readInt(); + cur.states = (firstToken&DELTA_STATE_MASK) | (stateInt&(~DELTA_STATE_MASK)); + cur.batteryStatus = (byte)((stateInt>>STATE_BATTERY_STATUS_SHIFT) + & STATE_BATTERY_STATUS_MASK); + cur.batteryHealth = (byte)((stateInt>>STATE_BATTERY_HEALTH_SHIFT) + & STATE_BATTERY_HEALTH_MASK); + cur.batteryPlugType = (byte)((stateInt>>STATE_BATTERY_PLUG_SHIFT) + & STATE_BATTERY_PLUG_MASK); + switch (cur.batteryPlugType) { + case 1: + cur.batteryPlugType = BatteryManager.BATTERY_PLUGGED_AC; + break; + case 2: + cur.batteryPlugType = BatteryManager.BATTERY_PLUGGED_USB; + break; + case 3: + cur.batteryPlugType = BatteryManager.BATTERY_PLUGGED_WIRELESS; + break; + } + cur.numReadInts += 1; + if (DEBUG) Slog.i(TAG, "READ DELTA: stateToken=0x" + + Integer.toHexString(stateInt) + + " batteryStatus=" + cur.batteryStatus + + " batteryHealth=" + cur.batteryHealth + + " batteryPlugType=" + cur.batteryPlugType + + " states=0x" + Integer.toHexString(cur.states)); + } else { + cur.states = (firstToken&DELTA_STATE_MASK) | (cur.states&(~DELTA_STATE_MASK)); + } + + if ((firstToken&DELTA_WAKELOCK_FLAG) != 0) { + int indexes = src.readInt(); + int wakeLockIndex = indexes&0xffff; + int wakeReasonIndex = (indexes>>16)&0xffff; + if (wakeLockIndex != 0xffff) { + cur.wakelockTag = cur.localWakelockTag; + readHistoryTag(wakeLockIndex, cur.wakelockTag); + if (DEBUG) Slog.i(TAG, "READ DELTA: wakelockTag=#" + cur.wakelockTag.poolIdx + + " " + cur.wakelockTag.uid + ":" + cur.wakelockTag.string); + } else { + cur.wakelockTag = null; + } + if (wakeReasonIndex != 0xffff) { + cur.wakeReasonTag = cur.localWakeReasonTag; + readHistoryTag(wakeReasonIndex, cur.wakeReasonTag); + if (DEBUG) Slog.i(TAG, "READ DELTA: wakeReasonTag=#" + cur.wakeReasonTag.poolIdx + + " " + cur.wakeReasonTag.uid + ":" + cur.wakeReasonTag.string); + } else { + cur.wakeReasonTag = null; + } + cur.numReadInts += 1; + } else { + cur.wakelockTag = null; + cur.wakeReasonTag = null; + } + + if ((firstToken&DELTA_EVENT_FLAG) != 0) { + cur.eventTag = cur.localEventTag; + final int codeAndIndex = src.readInt(); + cur.eventCode = (codeAndIndex&0xffff); + final int index = ((codeAndIndex>>16)&0xffff); + readHistoryTag(index, cur.eventTag); + cur.numReadInts += 1; + if (DEBUG) Slog.i(TAG, "READ DELTA: event=" + cur.eventCode + " tag=#" + + cur.eventTag.poolIdx + " " + cur.eventTag.uid + ":" + + cur.eventTag.string); + } else { + cur.eventCode = HistoryItem.EVENT_NONE; + } + } void addHistoryBufferLocked(long curTime) { if (!mHaveBatteryLevel || !mRecordingHistory) { @@ -1482,35 +1963,64 @@ public final class BatteryStatsImpl extends BatteryStats { } final long timeDiff = (mHistoryBaseTime+curTime) - mHistoryLastWritten.time; + final int diffStates = mHistoryLastWritten.states^mHistoryCur.states; + final int lastDiffStates = mHistoryLastWritten.states^mHistoryLastLastWritten.states; + if (DEBUG) Slog.i(TAG, "ADD: tdelta=" + timeDiff + " diff=" + + Integer.toHexString(diffStates) + " lastDiff=" + + Integer.toHexString(lastDiffStates)); if (mHistoryBufferLastPos >= 0 && mHistoryLastWritten.cmd == HistoryItem.CMD_UPDATE - && timeDiff < 2000 - && ((mHistoryLastWritten.states^mHistoryCur.states)&mChangedBufferStates) == 0) { - // If the current is the same as the one before, then we no - // longer need the entry. + && timeDiff < 1000 && (diffStates&lastDiffStates) == 0 + && (mHistoryLastWritten.wakelockTag == null || mHistoryCur.wakelockTag == null) + && (mHistoryLastWritten.wakeReasonTag == null || mHistoryCur.wakeReasonTag == null) + && (mHistoryLastWritten.eventCode == HistoryItem.EVENT_NONE + || mHistoryCur.eventCode == HistoryItem.EVENT_NONE) + && mHistoryLastWritten.batteryLevel == mHistoryCur.batteryLevel + && mHistoryLastWritten.batteryStatus == mHistoryCur.batteryStatus + && mHistoryLastWritten.batteryHealth == mHistoryCur.batteryHealth + && mHistoryLastWritten.batteryPlugType == mHistoryCur.batteryPlugType + && mHistoryLastWritten.batteryTemperature == mHistoryCur.batteryTemperature + && mHistoryLastWritten.batteryVoltage == mHistoryCur.batteryVoltage) { + // We can merge this new change in with the last one. Merging is + // allows as long as only the states have changed, and within those states + // as long as no bit has changed both between now and the last entry, as + // well as the last entry and the one before it (so we capture any toggles). + if (DEBUG) Slog.i(TAG, "ADD: rewinding back to " + mHistoryBufferLastPos); mHistoryBuffer.setDataSize(mHistoryBufferLastPos); mHistoryBuffer.setDataPosition(mHistoryBufferLastPos); mHistoryBufferLastPos = -1; - if (mHistoryLastLastWritten.cmd == HistoryItem.CMD_UPDATE - && timeDiff < 500 && mHistoryLastLastWritten.same(mHistoryCur)) { - // If this results in us returning to the state written - // prior to the last one, then we can just delete the last - // written one and drop the new one. Nothing more to do. - mHistoryLastWritten.setTo(mHistoryLastLastWritten); - mHistoryLastLastWritten.cmd = HistoryItem.CMD_NULL; - return; - } - mChangedBufferStates |= mHistoryLastWritten.states^mHistoryCur.states; curTime = mHistoryLastWritten.time - mHistoryBaseTime; + // If the last written history had a wakelock tag, we need to retain it. + // Note that the condition above made sure that we aren't in a case where + // both it and the current history item have a wakelock tag. + if (mHistoryLastWritten.wakelockTag != null) { + mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag; + mHistoryCur.wakelockTag.setTo(mHistoryLastWritten.wakelockTag); + } + // If the last written history had a wake reason tag, we need to retain it. + // Note that the condition above made sure that we aren't in a case where + // both it and the current history item have a wakelock tag. + if (mHistoryLastWritten.wakeReasonTag != null) { + mHistoryCur.wakeReasonTag = mHistoryCur.localWakeReasonTag; + mHistoryCur.wakeReasonTag.setTo(mHistoryLastWritten.wakeReasonTag); + } + // If the last written history had an event, we need to retain it. + // Note that the condition above made sure that we aren't in a case where + // both it and the current history item have an event. + if (mHistoryLastWritten.eventCode != HistoryItem.EVENT_NONE) { + mHistoryCur.eventCode = mHistoryLastWritten.eventCode; + mHistoryCur.eventTag = mHistoryCur.localEventTag; + mHistoryCur.eventTag.setTo(mHistoryLastWritten.eventTag); + } mHistoryLastWritten.setTo(mHistoryLastLastWritten); - } else { - mChangedBufferStates = 0; } final int dataSize = mHistoryBuffer.dataSize(); if (dataSize >= MAX_HISTORY_BUFFER) { if (!mHistoryOverflow) { mHistoryOverflow = true; + addHistoryBufferLocked(curTime, HistoryItem.CMD_UPDATE); addHistoryBufferLocked(curTime, HistoryItem.CMD_OVERFLOW); + return; } // Once we've reached the maximum number of items, we only @@ -1523,28 +2033,30 @@ public final class BatteryStatsImpl extends BatteryStats { & HistoryItem.MOST_INTERESTING_STATES) == 0)) { return; } + + addHistoryBufferLocked(curTime, HistoryItem.CMD_UPDATE); + return; } addHistoryBufferLocked(curTime, HistoryItem.CMD_UPDATE); } - void addHistoryBufferLocked(long curTime, byte cmd) { - int origPos = 0; + private void addHistoryBufferLocked(long curTime, byte cmd) { if (mIteratingHistory) { - origPos = mHistoryBuffer.dataPosition(); - mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize()); + throw new IllegalStateException("Can't do this while iterating history!"); } mHistoryBufferLastPos = mHistoryBuffer.dataPosition(); mHistoryLastLastWritten.setTo(mHistoryLastWritten); mHistoryLastWritten.setTo(mHistoryBaseTime + curTime, cmd, mHistoryCur); - mHistoryLastWritten.writeDelta(mHistoryBuffer, mHistoryLastLastWritten); + writeHistoryDelta(mHistoryBuffer, mHistoryLastWritten, mHistoryLastLastWritten); mLastHistoryTime = curTime; + mHistoryCur.wakelockTag = null; + mHistoryCur.wakeReasonTag = null; + mHistoryCur.eventCode = HistoryItem.EVENT_NONE; + mHistoryCur.eventTag = null; if (DEBUG_HISTORY) Slog.i(TAG, "Writing history buffer: was " + mHistoryBufferLastPos + " now " + mHistoryBuffer.dataPosition() + " size is now " + mHistoryBuffer.dataSize()); - if (mIteratingHistory) { - mHistoryBuffer.setDataPosition(origPos); - } } int mChangedStates = 0; @@ -1571,7 +2083,7 @@ public final class BatteryStatsImpl extends BatteryStats { // longer need the entry. if (mHistoryLastEnd != null && mHistoryLastEnd.cmd == HistoryItem.CMD_UPDATE && (mHistoryBaseTime+curTime) < (mHistoryEnd.time+500) - && mHistoryLastEnd.same(mHistoryCur)) { + && mHistoryLastEnd.sameNonEvent(mHistoryCur)) { mHistoryLastEnd.next = null; mHistoryEnd.next = mHistoryCache; mHistoryCache = mHistoryEnd; @@ -1608,6 +2120,14 @@ public final class BatteryStatsImpl extends BatteryStats { addHistoryRecordLocked(curTime, HistoryItem.CMD_UPDATE); } + void addHistoryEventLocked(long curTime, int code, String name, int uid) { + mHistoryCur.eventCode = code; + mHistoryCur.eventTag = mHistoryCur.localEventTag; + mHistoryCur.eventTag.string = name; + mHistoryCur.eventTag.uid = uid; + addHistoryBufferLocked(curTime); + } + void addHistoryRecordLocked(long curTime, byte cmd) { HistoryItem rec = mHistoryCache; if (rec != null) { @@ -1648,44 +2168,113 @@ public final class BatteryStatsImpl extends BatteryStats { mHistoryBuffer.setDataSize(0); mHistoryBuffer.setDataPosition(0); - mHistoryBuffer.setDataCapacity(MAX_HISTORY_BUFFER/2); - mHistoryLastLastWritten.cmd = HistoryItem.CMD_NULL; - mHistoryLastWritten.cmd = HistoryItem.CMD_NULL; + mHistoryBuffer.setDataCapacity(MAX_HISTORY_BUFFER / 2); + mHistoryLastLastWritten.clear(); + mHistoryLastWritten.clear(); + mHistoryTagPool.clear(); + mNextHistoryTagIdx = 0; + mNumHistoryTagChars = 0; mHistoryBufferLastPos = -1; mHistoryOverflow = false; } - public void doUnplugLocked(long elapsedRealtime, long batteryUptime, long batteryRealtime) { - for (int i = mUnpluggables.size() - 1; i >= 0; i--) { - mUnpluggables.get(i).unplug(elapsedRealtime, batteryUptime, batteryRealtime); + public void updateTimeBasesLocked(boolean unplugged, boolean screenOff, long uptime, + long realtime) { + if (mOnBatteryTimeBase.setRunning(unplugged, uptime, realtime)) { + if (unplugged) { + // Track bt headset ping count + mBluetoothPingStart = getCurrentBluetoothPingCount(); + mBluetoothPingCount = 0; + } else { + // Track bt headset ping count + mBluetoothPingCount = getBluetoothPingCount(); + mBluetoothPingStart = -1; + } } - // Track radio awake time - mRadioDataStart = getCurrentRadioDataUptime(); - mRadioDataUptime = 0; + boolean unpluggedScreenOff = unplugged && screenOff; + if (unpluggedScreenOff != mOnBatteryScreenOffTimeBase.isRunning()) { + updateKernelWakelocksLocked(); + requestWakelockCpuUpdate(); + if (!unpluggedScreenOff) { + // We are switching to no longer tracking wake locks, but we want + // the next CPU update we receive to take them in to account. + mDistributeWakelockCpu = true; + } + mOnBatteryScreenOffTimeBase.setRunning(unpluggedScreenOff, uptime, realtime); + } + } - // Track bt headset ping count - mBluetoothPingStart = getCurrentBluetoothPingCount(); - mBluetoothPingCount = 0; + public void addIsolatedUidLocked(int isolatedUid, int appUid) { + mIsolatedUids.put(isolatedUid, appUid); } - public void doPlugLocked(long elapsedRealtime, long batteryUptime, long batteryRealtime) { - for (int i = mUnpluggables.size() - 1; i >= 0; i--) { - mUnpluggables.get(i).plug(elapsedRealtime, batteryUptime, batteryRealtime); + public void removeIsolatedUidLocked(int isolatedUid, int appUid) { + int curUid = mIsolatedUids.get(isolatedUid, -1); + if (curUid == appUid) { + mIsolatedUids.delete(isolatedUid); } + } - // Track radio awake time - mRadioDataUptime = getRadioDataUptime(); - mRadioDataStart = -1; + public int mapUid(int uid) { + int isolated = mIsolatedUids.get(uid, -1); + return isolated > 0 ? isolated : uid; + } - // Track bt headset ping count - mBluetoothPingCount = getBluetoothPingCount(); - mBluetoothPingStart = -1; + public void noteEventLocked(int code, String name, int uid) { + uid = mapUid(uid); + if ((code&HistoryItem.EVENT_FLAG_START) != 0) { + int idx = code&~HistoryItem.EVENT_FLAG_START; + HashMap<String, SparseBooleanArray> active = mActiveEvents[idx]; + if (active == null) { + active = new HashMap<String, SparseBooleanArray>(); + mActiveEvents[idx] = active; + } + SparseBooleanArray uids = active.get(name); + if (uids == null) { + uids = new SparseBooleanArray(); + active.put(name, uids); + } + if (uids.get(uid)) { + // Already set, nothing to do! + return; + } + uids.put(uid, true); + } else if ((code&HistoryItem.EVENT_FLAG_FINISH) != 0) { + int idx = code&~HistoryItem.EVENT_FLAG_FINISH; + HashMap<String, SparseBooleanArray> active = mActiveEvents[idx]; + if (active == null) { + // not currently active, nothing to do. + return; + } + SparseBooleanArray uids = active.get(name); + if (uids == null) { + // not currently active, nothing to do. + return; + } + idx = uids.indexOfKey(uid); + if (idx < 0 || !uids.valueAt(idx)) { + // not currently active, nothing to do. + return; + } + uids.removeAt(idx); + if (uids.size() <= 0) { + active.remove(name); + } + } + addHistoryEventLocked(SystemClock.elapsedRealtime(), code, name, uid); } - int mWakeLockNesting; + private void requestWakelockCpuUpdate() { + if (!mHandler.hasMessages(MSG_UPDATE_WAKELOCKS)) { + Message m = mHandler.obtainMessage(MSG_UPDATE_WAKELOCKS); + mHandler.sendMessageDelayed(m, DELAY_UPDATE_WAKELOCKS); + } + } - public void noteStartWakeLocked(int uid, int pid, String name, int type) { + public void noteStartWakeLocked(int uid, int pid, String name, String historyName, int type, + boolean unimportantForLogging, long elapsedRealtime) { + uid = mapUid(uid); if (type == WAKE_TYPE_PARTIAL) { // Only care about partial wake locks, since full wake locks // will be canceled when the user puts the screen to sleep. @@ -1693,65 +2282,109 @@ public final class BatteryStatsImpl extends BatteryStats { mHistoryCur.states |= HistoryItem.STATE_WAKE_LOCK_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Start wake lock to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag; + mHistoryCur.wakelockTag.string = historyName != null ? historyName : name; + mHistoryCur.wakelockTag.uid = uid; + mWakeLockImportant = !unimportantForLogging; + addHistoryRecordLocked(elapsedRealtime); + } else if (!mWakeLockImportant && !unimportantForLogging) { + if (mHistoryLastWritten.wakelockTag != null) { + // We'll try to update the last tag. + mHistoryLastWritten.wakelockTag = null; + mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag; + mHistoryCur.wakelockTag.string = historyName != null ? historyName : name; + mHistoryCur.wakelockTag.uid = uid; + addHistoryRecordLocked(elapsedRealtime); + } + mWakeLockImportant = true; } mWakeLockNesting++; } if (uid >= 0) { - if (!mHandler.hasMessages(MSG_UPDATE_WAKELOCKS)) { - Message m = mHandler.obtainMessage(MSG_UPDATE_WAKELOCKS); - mHandler.sendMessageDelayed(m, DELAY_UPDATE_WAKELOCKS); - } - getUidStatsLocked(uid).noteStartWakeLocked(pid, name, type); + requestWakelockCpuUpdate(); + getUidStatsLocked(uid).noteStartWakeLocked(pid, name, type, elapsedRealtime); } } - public void noteStopWakeLocked(int uid, int pid, String name, int type) { + public void noteStopWakeLocked(int uid, int pid, String name, int type, long elapsedRealtime) { + uid = mapUid(uid); if (type == WAKE_TYPE_PARTIAL) { mWakeLockNesting--; if (mWakeLockNesting == 0) { mHistoryCur.states &= ~HistoryItem.STATE_WAKE_LOCK_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Stop wake lock to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime); } } if (uid >= 0) { - if (!mHandler.hasMessages(MSG_UPDATE_WAKELOCKS)) { - Message m = mHandler.obtainMessage(MSG_UPDATE_WAKELOCKS); - mHandler.sendMessageDelayed(m, DELAY_UPDATE_WAKELOCKS); - } - getUidStatsLocked(uid).noteStopWakeLocked(pid, name, type); + requestWakelockCpuUpdate(); + getUidStatsLocked(uid).noteStopWakeLocked(pid, name, type, elapsedRealtime); } } - public void noteStartWakeFromSourceLocked(WorkSource ws, int pid, String name, int type) { - int N = ws.size(); + public void noteStartWakeFromSourceLocked(WorkSource ws, int pid, String name, + String historyName, int type, boolean unimportantForLogging) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final int N = ws.size(); for (int i=0; i<N; i++) { - noteStartWakeLocked(ws.get(i), pid, name, type); + noteStartWakeLocked(ws.get(i), pid, name, historyName, type, unimportantForLogging, + elapsedRealtime); + } + } + + public void noteChangeWakelockFromSourceLocked(WorkSource ws, int pid, String name, int type, + WorkSource newWs, int newPid, String newName, + String newHistoryName, int newType, boolean newUnimportantForLogging) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); + // For correct semantics, we start the need worksources first, so that we won't + // make inappropriate history items as if all wake locks went away and new ones + // appeared. This is okay because tracking of wake locks allows nesting. + final int NN = ws.size(); + for (int i=0; i<NN; i++) { + noteStartWakeLocked(newWs.get(i), newPid, newName, newHistoryName, newType, + newUnimportantForLogging, elapsedRealtime); + } + final int NO = ws.size(); + for (int i=0; i<NO; i++) { + noteStopWakeLocked(ws.get(i), pid, name, type, elapsedRealtime); } } public void noteStopWakeFromSourceLocked(WorkSource ws, int pid, String name, int type) { - int N = ws.size(); + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final int N = ws.size(); for (int i=0; i<N; i++) { - noteStopWakeLocked(ws.get(i), pid, name, type); + noteStopWakeLocked(ws.get(i), pid, name, type, elapsedRealtime); } } + public void noteWakeupReasonLocked(int irq, String reason) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); + if (DEBUG_HISTORY) Slog.v(TAG, "Wakeup reason irq #" + irq + "\"" + reason +"\": " + + Integer.toHexString(mHistoryCur.states)); + mHistoryCur.wakeReasonTag = mHistoryCur.localWakeReasonTag; + mHistoryCur.wakeReasonTag.string = reason; + mHistoryCur.wakeReasonTag.uid = irq; + addHistoryRecordLocked(elapsedRealtime); + } + public int startAddingCpuLocked() { mHandler.removeMessages(MSG_UPDATE_WAKELOCKS); - if (mScreenOn) { - return 0; - } - final int N = mPartialTimers.size(); if (N == 0) { mLastPartialTimers.clear(); + mDistributeWakelockCpu = false; + return 0; + } + + if (!mOnBatteryScreenOffTimeBase.isRunning() && !mDistributeWakelockCpu) { return 0; } + mDistributeWakelockCpu = false; + // How many timers should consume CPU? Only want to include ones // that have already been in the list. for (int i=0; i<N; i++) { @@ -1838,6 +2471,7 @@ public final class BatteryStatsImpl extends BatteryStats { } public void noteProcessDiedLocked(int uid, int pid) { + uid = mapUid(uid); Uid u = mUidStats.get(uid); if (u != null) { u.mPids.remove(pid); @@ -1845,17 +2479,19 @@ public final class BatteryStatsImpl extends BatteryStats { } public long getProcessWakeTime(int uid, int pid, long realtime) { + uid = mapUid(uid); Uid u = mUidStats.get(uid); if (u != null) { Uid.Pid p = u.mPids.get(pid); if (p != null) { - return p.mWakeSum + (p.mWakeStart != 0 ? (realtime - p.mWakeStart) : 0); + return p.mWakeSumMs + (p.mWakeNesting > 0 ? (realtime - p.mWakeStartMs) : 0); } } return 0; } public void reportExcessiveWakeLocked(int uid, String proc, long overTime, long usedTime) { + uid = mapUid(uid); Uid u = mUidStats.get(uid); if (u != null) { u.reportExcessiveWakeLocked(proc, overTime, usedTime); @@ -1863,6 +2499,7 @@ public final class BatteryStatsImpl extends BatteryStats { } public void reportExcessiveCpuLocked(int uid, String proc, long overTime, long usedTime) { + uid = mapUid(uid); Uid u = mUidStats.get(uid); if (u != null) { u.reportExcessiveCpuLocked(proc, overTime, usedTime); @@ -1872,67 +2509,79 @@ public final class BatteryStatsImpl extends BatteryStats { int mSensorNesting; public void noteStartSensorLocked(int uid, int sensor) { + uid = mapUid(uid); + final long elapsedRealtime = SystemClock.elapsedRealtime(); if (mSensorNesting == 0) { mHistoryCur.states |= HistoryItem.STATE_SENSOR_ON_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Start sensor to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime); } mSensorNesting++; - getUidStatsLocked(uid).noteStartSensor(sensor); + getUidStatsLocked(uid).noteStartSensor(sensor, elapsedRealtime); } public void noteStopSensorLocked(int uid, int sensor) { + uid = mapUid(uid); + final long elapsedRealtime = SystemClock.elapsedRealtime(); mSensorNesting--; if (mSensorNesting == 0) { mHistoryCur.states &= ~HistoryItem.STATE_SENSOR_ON_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Stop sensor to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime); } - getUidStatsLocked(uid).noteStopSensor(sensor); + getUidStatsLocked(uid).noteStopSensor(sensor, elapsedRealtime); } int mGpsNesting; public void noteStartGpsLocked(int uid) { + uid = mapUid(uid); + final long elapsedRealtime = SystemClock.elapsedRealtime(); if (mGpsNesting == 0) { mHistoryCur.states |= HistoryItem.STATE_GPS_ON_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Start GPS to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime); } mGpsNesting++; - getUidStatsLocked(uid).noteStartGps(); + getUidStatsLocked(uid).noteStartGps(elapsedRealtime); } public void noteStopGpsLocked(int uid) { + uid = mapUid(uid); + final long elapsedRealtime = SystemClock.elapsedRealtime(); mGpsNesting--; if (mGpsNesting == 0) { mHistoryCur.states &= ~HistoryItem.STATE_GPS_ON_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Stop GPS to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime); } - getUidStatsLocked(uid).noteStopGps(); + getUidStatsLocked(uid).noteStopGps(elapsedRealtime); } public void noteScreenOnLocked() { if (!mScreenOn) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); mHistoryCur.states |= HistoryItem.STATE_SCREEN_ON_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Screen on to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime); mScreenOn = true; - mScreenOnTimer.startRunningLocked(this); + mScreenOnTimer.startRunningLocked(elapsedRealtime); if (mScreenBrightnessBin >= 0) { - mScreenBrightnessTimer[mScreenBrightnessBin].startRunningLocked(this); + mScreenBrightnessTimer[mScreenBrightnessBin].startRunningLocked(elapsedRealtime); } + updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), false, + SystemClock.uptimeMillis() * 1000, elapsedRealtime * 1000); + // Fake a wake lock, so we consider the device waked as long // as the screen is on. - noteStartWakeLocked(-1, -1, "dummy", WAKE_TYPE_PARTIAL); - + noteStartWakeLocked(-1, -1, "screen", null, WAKE_TYPE_PARTIAL, false, elapsedRealtime); + // Update discharge amounts. if (mOnBatteryInternal) { updateDischargeScreenLevelsLocked(false, true); @@ -1942,18 +2591,22 @@ public final class BatteryStatsImpl extends BatteryStats { public void noteScreenOffLocked() { if (mScreenOn) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); mHistoryCur.states &= ~HistoryItem.STATE_SCREEN_ON_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Screen off to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime); mScreenOn = false; - mScreenOnTimer.stopRunningLocked(this); + mScreenOnTimer.stopRunningLocked(elapsedRealtime); if (mScreenBrightnessBin >= 0) { - mScreenBrightnessTimer[mScreenBrightnessBin].stopRunningLocked(this); + mScreenBrightnessTimer[mScreenBrightnessBin].stopRunningLocked(elapsedRealtime); } - noteStopWakeLocked(-1, -1, "dummy", WAKE_TYPE_PARTIAL); - + noteStopWakeLocked(-1, -1, "screen", WAKE_TYPE_PARTIAL, elapsedRealtime); + + updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), true, + SystemClock.uptimeMillis() * 1000, elapsedRealtime * 1000); + // Update discharge amounts. if (mOnBatteryInternal) { updateDischargeScreenLevelsLocked(true, false); @@ -1967,16 +2620,17 @@ public final class BatteryStatsImpl extends BatteryStats { if (bin < 0) bin = 0; else if (bin >= NUM_SCREEN_BRIGHTNESS_BINS) bin = NUM_SCREEN_BRIGHTNESS_BINS-1; if (mScreenBrightnessBin != bin) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_BRIGHTNESS_MASK) | (bin << HistoryItem.STATE_BRIGHTNESS_SHIFT); if (DEBUG_HISTORY) Slog.v(TAG, "Screen brightness " + bin + " to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime); if (mScreenOn) { if (mScreenBrightnessBin >= 0) { - mScreenBrightnessTimer[mScreenBrightnessBin].stopRunningLocked(this); + mScreenBrightnessTimer[mScreenBrightnessBin].stopRunningLocked(elapsedRealtime); } - mScreenBrightnessTimer[bin].startRunningLocked(this); + mScreenBrightnessTimer[bin].startRunningLocked(elapsedRealtime); } mScreenBrightnessBin = bin; } @@ -1987,38 +2641,66 @@ public final class BatteryStatsImpl extends BatteryStats { } public void noteUserActivityLocked(int uid, int event) { - getUidStatsLocked(uid).noteUserActivityLocked(event); + if (mOnBatteryInternal) { + uid = mapUid(uid); + getUidStatsLocked(uid).noteUserActivityLocked(event); + } + } + + public void noteDataConnectionActive(int type, boolean active) { + if (ConnectivityManager.isNetworkTypeMobile(type)) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); + if (mMobileRadioActive != active) { + if (active) mHistoryCur.states |= HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG; + else mHistoryCur.states &= ~HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Mobile network active " + active + " to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(elapsedRealtime); + mMobileRadioActive = active; + if (active) { + mMobileRadioActiveTimer.startRunningLocked(elapsedRealtime); + mMobileRadioActivePerAppTimer.startRunningLocked(elapsedRealtime); + } else { + updateNetworkActivityLocked(NET_UPDATE_MOBILE, elapsedRealtime); + mMobileRadioActiveTimer.stopRunningLocked(elapsedRealtime); + mMobileRadioActivePerAppTimer.stopRunningLocked(elapsedRealtime); + } + } + } } public void notePhoneOnLocked() { if (!mPhoneOn) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); mHistoryCur.states |= HistoryItem.STATE_PHONE_IN_CALL_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Phone on to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime); mPhoneOn = true; - mPhoneOnTimer.startRunningLocked(this); + mPhoneOnTimer.startRunningLocked(elapsedRealtime); } } public void notePhoneOffLocked() { if (mPhoneOn) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); mHistoryCur.states &= ~HistoryItem.STATE_PHONE_IN_CALL_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Phone off to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime); mPhoneOn = false; - mPhoneOnTimer.stopRunningLocked(this); + mPhoneOnTimer.stopRunningLocked(elapsedRealtime); } } void stopAllSignalStrengthTimersLocked(int except) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); for (int i = 0; i < SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) { if (i == except) { continue; } while (mPhoneSignalStrengthsTimer[i].isRunningLocked()) { - mPhoneSignalStrengthsTimer[i].stopRunningLocked(this); + mPhoneSignalStrengthsTimer[i].stopRunningLocked(elapsedRealtime); } } } @@ -2036,26 +2718,28 @@ public final class BatteryStatsImpl extends BatteryStats { return state; } - private void updateAllPhoneStateLocked(int state, int simState, int bin) { + private void updateAllPhoneStateLocked(int state, int simState, int strengthBin) { boolean scanning = false; boolean newHistory = false; mPhoneServiceStateRaw = state; mPhoneSimStateRaw = simState; - mPhoneSignalStrengthBinRaw = bin; + mPhoneSignalStrengthBinRaw = strengthBin; + + final long elapsedRealtime = SystemClock.elapsedRealtime(); if (simState == TelephonyManager.SIM_STATE_ABSENT) { // In this case we will always be STATE_OUT_OF_SERVICE, so need // to infer that we are scanning from other data. if (state == ServiceState.STATE_OUT_OF_SERVICE - && bin > SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN) { + && strengthBin > SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN) { state = ServiceState.STATE_IN_SERVICE; } } // If the phone is powered off, stop all timers. if (state == ServiceState.STATE_POWER_OFF) { - bin = -1; + strengthBin = -1; // If we are in service, make sure the correct signal string timer is running. } else if (state == ServiceState.STATE_IN_SERVICE) { @@ -2065,13 +2749,13 @@ public final class BatteryStatsImpl extends BatteryStats { // bin and have the scanning bit set. } else if (state == ServiceState.STATE_OUT_OF_SERVICE) { scanning = true; - bin = SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + strengthBin = SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN; if (!mPhoneSignalScanningTimer.isRunningLocked()) { mHistoryCur.states |= HistoryItem.STATE_PHONE_SCANNING_FLAG; newHistory = true; if (DEBUG_HISTORY) Slog.v(TAG, "Phone started scanning to: " + Integer.toHexString(mHistoryCur.states)); - mPhoneSignalScanningTimer.startRunningLocked(this); + mPhoneSignalScanningTimer.startRunningLocked(elapsedRealtime); } } @@ -2082,7 +2766,7 @@ public final class BatteryStatsImpl extends BatteryStats { if (DEBUG_HISTORY) Slog.v(TAG, "Phone stopped scanning to: " + Integer.toHexString(mHistoryCur.states)); newHistory = true; - mPhoneSignalScanningTimer.stopRunningLocked(this); + mPhoneSignalScanningTimer.stopRunningLocked(elapsedRealtime); } } @@ -2095,27 +2779,28 @@ public final class BatteryStatsImpl extends BatteryStats { mPhoneServiceState = state; } - if (mPhoneSignalStrengthBin != bin) { + if (mPhoneSignalStrengthBin != strengthBin) { if (mPhoneSignalStrengthBin >= 0) { - mPhoneSignalStrengthsTimer[mPhoneSignalStrengthBin].stopRunningLocked(this); + mPhoneSignalStrengthsTimer[mPhoneSignalStrengthBin].stopRunningLocked( + elapsedRealtime); } - if (bin >= 0) { - if (!mPhoneSignalStrengthsTimer[bin].isRunningLocked()) { - mPhoneSignalStrengthsTimer[bin].startRunningLocked(this); + if (strengthBin >= 0) { + if (!mPhoneSignalStrengthsTimer[strengthBin].isRunningLocked()) { + mPhoneSignalStrengthsTimer[strengthBin].startRunningLocked(elapsedRealtime); } mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_SIGNAL_STRENGTH_MASK) - | (bin << HistoryItem.STATE_SIGNAL_STRENGTH_SHIFT); - if (DEBUG_HISTORY) Slog.v(TAG, "Signal strength " + bin + " to: " + | (strengthBin << HistoryItem.STATE_SIGNAL_STRENGTH_SHIFT); + if (DEBUG_HISTORY) Slog.v(TAG, "Signal strength " + strengthBin + " to: " + Integer.toHexString(mHistoryCur.states)); newHistory = true; } else { stopAllSignalStrengthTimersLocked(-1); } - mPhoneSignalStrengthBin = bin; + mPhoneSignalStrengthBin = strengthBin; } if (newHistory) { - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime); } } @@ -2189,120 +2874,134 @@ public final class BatteryStatsImpl extends BatteryStats { } if (DEBUG) Log.i(TAG, "Phone Data Connection -> " + dataType + " = " + hasData); if (mPhoneDataConnectionType != bin) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_DATA_CONNECTION_MASK) | (bin << HistoryItem.STATE_DATA_CONNECTION_SHIFT); if (DEBUG_HISTORY) Slog.v(TAG, "Data connection " + bin + " to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime); if (mPhoneDataConnectionType >= 0) { - mPhoneDataConnectionsTimer[mPhoneDataConnectionType].stopRunningLocked(this); + mPhoneDataConnectionsTimer[mPhoneDataConnectionType].stopRunningLocked( + elapsedRealtime); } mPhoneDataConnectionType = bin; - mPhoneDataConnectionsTimer[bin].startRunningLocked(this); + mPhoneDataConnectionsTimer[bin].startRunningLocked(elapsedRealtime); } } public void noteWifiOnLocked() { if (!mWifiOn) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); mHistoryCur.states |= HistoryItem.STATE_WIFI_ON_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "WIFI on to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime); mWifiOn = true; - mWifiOnTimer.startRunningLocked(this); + mWifiOnTimer.startRunningLocked(elapsedRealtime); } } public void noteWifiOffLocked() { + final long elapsedRealtime = SystemClock.elapsedRealtime(); if (mWifiOn) { mHistoryCur.states &= ~HistoryItem.STATE_WIFI_ON_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "WIFI off to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime); mWifiOn = false; - mWifiOnTimer.stopRunningLocked(this); - } - if (mWifiOnUid >= 0) { - getUidStatsLocked(mWifiOnUid).noteWifiStoppedLocked(); - mWifiOnUid = -1; + mWifiOnTimer.stopRunningLocked(elapsedRealtime); } } public void noteAudioOnLocked(int uid) { + uid = mapUid(uid); + final long elapsedRealtime = SystemClock.elapsedRealtime(); if (!mAudioOn) { mHistoryCur.states |= HistoryItem.STATE_AUDIO_ON_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Audio on to: " + Integer.toHexString(mHistoryCur.states)); addHistoryRecordLocked(SystemClock.elapsedRealtime()); mAudioOn = true; - mAudioOnTimer.startRunningLocked(this); + mAudioOnTimer.startRunningLocked(elapsedRealtime); } - getUidStatsLocked(uid).noteAudioTurnedOnLocked(); + getUidStatsLocked(uid).noteAudioTurnedOnLocked(elapsedRealtime); } public void noteAudioOffLocked(int uid) { + uid = mapUid(uid); + final long elapsedRealtime = SystemClock.elapsedRealtime(); if (mAudioOn) { mHistoryCur.states &= ~HistoryItem.STATE_AUDIO_ON_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Audio off to: " + Integer.toHexString(mHistoryCur.states)); addHistoryRecordLocked(SystemClock.elapsedRealtime()); mAudioOn = false; - mAudioOnTimer.stopRunningLocked(this); + mAudioOnTimer.stopRunningLocked(elapsedRealtime); } - getUidStatsLocked(uid).noteAudioTurnedOffLocked(); + getUidStatsLocked(uid).noteAudioTurnedOffLocked(elapsedRealtime); } public void noteVideoOnLocked(int uid) { + uid = mapUid(uid); + final long elapsedRealtime = SystemClock.elapsedRealtime(); if (!mVideoOn) { mHistoryCur.states |= HistoryItem.STATE_VIDEO_ON_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Video on to: " + Integer.toHexString(mHistoryCur.states)); addHistoryRecordLocked(SystemClock.elapsedRealtime()); mVideoOn = true; - mVideoOnTimer.startRunningLocked(this); + mVideoOnTimer.startRunningLocked(elapsedRealtime); } - getUidStatsLocked(uid).noteVideoTurnedOnLocked(); + getUidStatsLocked(uid).noteVideoTurnedOnLocked(elapsedRealtime); } public void noteVideoOffLocked(int uid) { + uid = mapUid(uid); + final long elapsedRealtime = SystemClock.elapsedRealtime(); if (mVideoOn) { mHistoryCur.states &= ~HistoryItem.STATE_VIDEO_ON_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Video off to: " + Integer.toHexString(mHistoryCur.states)); addHistoryRecordLocked(SystemClock.elapsedRealtime()); mVideoOn = false; - mVideoOnTimer.stopRunningLocked(this); + mVideoOnTimer.stopRunningLocked(elapsedRealtime); } - getUidStatsLocked(uid).noteVideoTurnedOffLocked(); + getUidStatsLocked(uid).noteVideoTurnedOffLocked(elapsedRealtime); } public void noteActivityResumedLocked(int uid) { - getUidStatsLocked(uid).noteActivityResumedLocked(); + uid = mapUid(uid); + getUidStatsLocked(uid).noteActivityResumedLocked(SystemClock.elapsedRealtime()); } public void noteActivityPausedLocked(int uid) { - getUidStatsLocked(uid).noteActivityPausedLocked(); + uid = mapUid(uid); + getUidStatsLocked(uid).noteActivityPausedLocked(SystemClock.elapsedRealtime()); } public void noteVibratorOnLocked(int uid, long durationMillis) { + uid = mapUid(uid); getUidStatsLocked(uid).noteVibratorOnLocked(durationMillis); } public void noteVibratorOffLocked(int uid) { + uid = mapUid(uid); getUidStatsLocked(uid).noteVibratorOffLocked(); } public void noteWifiRunningLocked(WorkSource ws) { if (!mGlobalWifiRunning) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); mHistoryCur.states |= HistoryItem.STATE_WIFI_RUNNING_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "WIFI running to: " + Integer.toHexString(mHistoryCur.states)); addHistoryRecordLocked(SystemClock.elapsedRealtime()); mGlobalWifiRunning = true; - mGlobalWifiRunningTimer.startRunningLocked(this); + mGlobalWifiRunningTimer.startRunningLocked(elapsedRealtime); int N = ws.size(); for (int i=0; i<N; i++) { - getUidStatsLocked(ws.get(i)).noteWifiRunningLocked(); + int uid = mapUid(ws.get(i)); + getUidStatsLocked(uid).noteWifiRunningLocked(elapsedRealtime); } } else { Log.w(TAG, "noteWifiRunningLocked -- called while WIFI running"); @@ -2311,13 +3010,16 @@ public final class BatteryStatsImpl extends BatteryStats { public void noteWifiRunningChangedLocked(WorkSource oldWs, WorkSource newWs) { if (mGlobalWifiRunning) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); int N = oldWs.size(); for (int i=0; i<N; i++) { - getUidStatsLocked(oldWs.get(i)).noteWifiStoppedLocked(); + int uid = mapUid(oldWs.get(i)); + getUidStatsLocked(uid).noteWifiStoppedLocked(elapsedRealtime); } N = newWs.size(); for (int i=0; i<N; i++) { - getUidStatsLocked(newWs.get(i)).noteWifiRunningLocked(); + int uid = mapUid(newWs.get(i)); + getUidStatsLocked(uid).noteWifiRunningLocked(elapsedRealtime); } } else { Log.w(TAG, "noteWifiRunningChangedLocked -- called while WIFI not running"); @@ -2326,121 +3028,165 @@ public final class BatteryStatsImpl extends BatteryStats { public void noteWifiStoppedLocked(WorkSource ws) { if (mGlobalWifiRunning) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); mHistoryCur.states &= ~HistoryItem.STATE_WIFI_RUNNING_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "WIFI stopped to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime); mGlobalWifiRunning = false; - mGlobalWifiRunningTimer.stopRunningLocked(this); + mGlobalWifiRunningTimer.stopRunningLocked(elapsedRealtime); int N = ws.size(); for (int i=0; i<N; i++) { - getUidStatsLocked(ws.get(i)).noteWifiStoppedLocked(); + int uid = mapUid(ws.get(i)); + getUidStatsLocked(uid).noteWifiStoppedLocked(elapsedRealtime); } } else { Log.w(TAG, "noteWifiStoppedLocked -- called while WIFI not running"); } } + public void noteWifiStateLocked(int wifiState, String accessPoint) { + if (DEBUG) Log.i(TAG, "WiFi state -> " + wifiState); + if (mWifiState != wifiState) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); + if (mWifiState >= 0) { + mWifiStateTimer[mWifiState].stopRunningLocked(elapsedRealtime); + } + mWifiState = wifiState; + mWifiStateTimer[wifiState].startRunningLocked(elapsedRealtime); + } + } + public void noteBluetoothOnLocked() { if (!mBluetoothOn) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); mHistoryCur.states |= HistoryItem.STATE_BLUETOOTH_ON_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Bluetooth on to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime); mBluetoothOn = true; - mBluetoothOnTimer.startRunningLocked(this); + mBluetoothOnTimer.startRunningLocked(elapsedRealtime); } } public void noteBluetoothOffLocked() { if (mBluetoothOn) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); mHistoryCur.states &= ~HistoryItem.STATE_BLUETOOTH_ON_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Bluetooth off to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime); mBluetoothOn = false; - mBluetoothOnTimer.stopRunningLocked(this); + mBluetoothOnTimer.stopRunningLocked(elapsedRealtime); + } + } + + public void noteBluetoothStateLocked(int bluetoothState) { + if (DEBUG) Log.i(TAG, "Bluetooth state -> " + bluetoothState); + if (mBluetoothState != bluetoothState) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); + if (mBluetoothState >= 0) { + mBluetoothStateTimer[mBluetoothState].stopRunningLocked(elapsedRealtime); + } + mBluetoothState = bluetoothState; + mBluetoothStateTimer[bluetoothState].startRunningLocked(elapsedRealtime); } } int mWifiFullLockNesting = 0; public void noteFullWifiLockAcquiredLocked(int uid) { + uid = mapUid(uid); + final long elapsedRealtime = SystemClock.elapsedRealtime(); if (mWifiFullLockNesting == 0) { mHistoryCur.states |= HistoryItem.STATE_WIFI_FULL_LOCK_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "WIFI full lock on to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime); } mWifiFullLockNesting++; - getUidStatsLocked(uid).noteFullWifiLockAcquiredLocked(); + getUidStatsLocked(uid).noteFullWifiLockAcquiredLocked(elapsedRealtime); } public void noteFullWifiLockReleasedLocked(int uid) { + uid = mapUid(uid); + final long elapsedRealtime = SystemClock.elapsedRealtime(); mWifiFullLockNesting--; if (mWifiFullLockNesting == 0) { mHistoryCur.states &= ~HistoryItem.STATE_WIFI_FULL_LOCK_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "WIFI full lock off to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime); } - getUidStatsLocked(uid).noteFullWifiLockReleasedLocked(); + getUidStatsLocked(uid).noteFullWifiLockReleasedLocked(elapsedRealtime); } int mWifiScanNesting = 0; public void noteWifiScanStartedLocked(int uid) { + uid = mapUid(uid); + final long elapsedRealtime = SystemClock.elapsedRealtime(); if (mWifiScanNesting == 0) { mHistoryCur.states |= HistoryItem.STATE_WIFI_SCAN_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "WIFI scan started for: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime); } mWifiScanNesting++; - getUidStatsLocked(uid).noteWifiScanStartedLocked(); + getUidStatsLocked(uid).noteWifiScanStartedLocked(elapsedRealtime); } public void noteWifiScanStoppedLocked(int uid) { + uid = mapUid(uid); + final long elapsedRealtime = SystemClock.elapsedRealtime(); mWifiScanNesting--; if (mWifiScanNesting == 0) { mHistoryCur.states &= ~HistoryItem.STATE_WIFI_SCAN_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "WIFI scan stopped for: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime); } - getUidStatsLocked(uid).noteWifiScanStoppedLocked(); + getUidStatsLocked(uid).noteWifiScanStoppedLocked(elapsedRealtime); } public void noteWifiBatchedScanStartedLocked(int uid, int csph) { - getUidStatsLocked(uid).noteWifiBatchedScanStartedLocked(csph); + uid = mapUid(uid); + final long elapsedRealtime = SystemClock.elapsedRealtime(); + getUidStatsLocked(uid).noteWifiBatchedScanStartedLocked(csph, elapsedRealtime); } public void noteWifiBatchedScanStoppedLocked(int uid) { - getUidStatsLocked(uid).noteWifiBatchedScanStoppedLocked(); + uid = mapUid(uid); + final long elapsedRealtime = SystemClock.elapsedRealtime(); + getUidStatsLocked(uid).noteWifiBatchedScanStoppedLocked(elapsedRealtime); } int mWifiMulticastNesting = 0; public void noteWifiMulticastEnabledLocked(int uid) { + uid = mapUid(uid); + final long elapsedRealtime = SystemClock.elapsedRealtime(); if (mWifiMulticastNesting == 0) { mHistoryCur.states |= HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast on to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime); } mWifiMulticastNesting++; - getUidStatsLocked(uid).noteWifiMulticastEnabledLocked(); + getUidStatsLocked(uid).noteWifiMulticastEnabledLocked(elapsedRealtime); } public void noteWifiMulticastDisabledLocked(int uid) { + uid = mapUid(uid); + final long elapsedRealtime = SystemClock.elapsedRealtime(); mWifiMulticastNesting--; if (mWifiMulticastNesting == 0) { mHistoryCur.states &= ~HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast off to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime); } - getUidStatsLocked(uid).noteWifiMulticastDisabledLocked(); + getUidStatsLocked(uid).noteWifiMulticastDisabledLocked(elapsedRealtime); } public void noteFullWifiLockAcquiredFromSourceLocked(WorkSource ws) { @@ -2499,16 +3245,45 @@ public final class BatteryStatsImpl extends BatteryStats { } } + private static String[] includeInStringArray(String[] array, String str) { + if (ArrayUtils.indexOf(array, str) >= 0) { + return array; + } + String[] newArray = new String[array.length+1]; + System.arraycopy(array, 0, newArray, 0, array.length); + newArray[array.length] = str; + return newArray; + } + + private static String[] excludeFromStringArray(String[] array, String str) { + int index = ArrayUtils.indexOf(array, str); + if (index >= 0) { + String[] newArray = new String[array.length-1]; + if (index > 0) { + System.arraycopy(array, 0, newArray, 0, index); + } + if (index < array.length-1) { + System.arraycopy(array, index+1, newArray, index, array.length-index-1); + } + return newArray; + } + return array; + } + public void noteNetworkInterfaceTypeLocked(String iface, int networkType) { if (ConnectivityManager.isNetworkTypeMobile(networkType)) { - mMobileIfaces.add(iface); + mMobileIfaces = includeInStringArray(mMobileIfaces, iface); + if (DEBUG) Slog.d(TAG, "Note mobile iface " + iface + ": " + mMobileIfaces); } else { - mMobileIfaces.remove(iface); + mMobileIfaces = excludeFromStringArray(mMobileIfaces, iface); + if (DEBUG) Slog.d(TAG, "Note non-mobile iface " + iface + ": " + mMobileIfaces); } if (ConnectivityManager.isNetworkTypeWifi(networkType)) { - mWifiIfaces.add(iface); + mWifiIfaces = includeInStringArray(mWifiIfaces, iface); + if (DEBUG) Slog.d(TAG, "Note wifi iface " + iface + ": " + mWifiIfaces); } else { - mWifiIfaces.remove(iface); + mWifiIfaces = excludeFromStringArray(mWifiIfaces, iface); + if (DEBUG) Slog.d(TAG, "Note non-wifi iface " + iface + ": " + mWifiIfaces); } } @@ -2516,37 +3291,45 @@ public final class BatteryStatsImpl extends BatteryStats { // During device boot, qtaguid isn't enabled until after the inital // loading of battery stats. Now that they're enabled, take our initial // snapshot for future delta calculation. - updateNetworkActivityLocked(); + updateNetworkActivityLocked(NET_UPDATE_ALL, SystemClock.elapsedRealtime()); } - @Override public long getScreenOnTime(long batteryRealtime, int which) { - return mScreenOnTimer.getTotalTimeLocked(batteryRealtime, which); + @Override public long getScreenOnTime(long elapsedRealtimeUs, int which) { + return mScreenOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which); + } + + @Override public int getScreenOnCount(int which) { + return mScreenOnTimer.getCountLocked(which); } @Override public long getScreenBrightnessTime(int brightnessBin, - long batteryRealtime, int which) { + long elapsedRealtimeUs, int which) { return mScreenBrightnessTimer[brightnessBin].getTotalTimeLocked( - batteryRealtime, which); + elapsedRealtimeUs, which); } @Override public int getInputEventCount(int which) { return mInputEventCounter.getCountLocked(which); } - @Override public long getPhoneOnTime(long batteryRealtime, int which) { - return mPhoneOnTimer.getTotalTimeLocked(batteryRealtime, which); + @Override public long getPhoneOnTime(long elapsedRealtimeUs, int which) { + return mPhoneOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which); + } + + @Override public int getPhoneOnCount(int which) { + return mPhoneOnTimer.getCountLocked(which); } @Override public long getPhoneSignalStrengthTime(int strengthBin, - long batteryRealtime, int which) { + long elapsedRealtimeUs, int which) { return mPhoneSignalStrengthsTimer[strengthBin].getTotalTimeLocked( - batteryRealtime, which); + elapsedRealtimeUs, which); } @Override public long getPhoneSignalScanningTime( - long batteryRealtime, int which) { + long elapsedRealtimeUs, int which) { return mPhoneSignalScanningTimer.getTotalTimeLocked( - batteryRealtime, which); + elapsedRealtimeUs, which); } @Override public int getPhoneSignalStrengthCount(int strengthBin, int which) { @@ -2554,36 +3337,85 @@ public final class BatteryStatsImpl extends BatteryStats { } @Override public long getPhoneDataConnectionTime(int dataType, - long batteryRealtime, int which) { + long elapsedRealtimeUs, int which) { return mPhoneDataConnectionsTimer[dataType].getTotalTimeLocked( - batteryRealtime, which); + elapsedRealtimeUs, which); } @Override public int getPhoneDataConnectionCount(int dataType, int which) { return mPhoneDataConnectionsTimer[dataType].getCountLocked(which); } - @Override public long getWifiOnTime(long batteryRealtime, int which) { - return mWifiOnTimer.getTotalTimeLocked(batteryRealtime, which); + @Override public long getMobileRadioActiveTime(long elapsedRealtimeUs, int which) { + return mMobileRadioActiveTimer.getTotalTimeLocked(elapsedRealtimeUs, which); + } + + @Override public int getMobileRadioActiveCount(int which) { + return mMobileRadioActiveTimer.getCountLocked(which); + } + + @Override public long getMobileRadioActiveUnknownTime(int which) { + return mMobileRadioActiveUnknownTime.getCountLocked(which); + } + + @Override public int getMobileRadioActiveUnknownCount(int which) { + return (int)mMobileRadioActiveUnknownCount.getCountLocked(which); + } + + @Override public long getWifiOnTime(long elapsedRealtimeUs, int which) { + return mWifiOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which); + } + + @Override public long getGlobalWifiRunningTime(long elapsedRealtimeUs, int which) { + return mGlobalWifiRunningTimer.getTotalTimeLocked(elapsedRealtimeUs, which); } - @Override public long getGlobalWifiRunningTime(long batteryRealtime, int which) { - return mGlobalWifiRunningTimer.getTotalTimeLocked(batteryRealtime, which); + @Override public long getWifiStateTime(int wifiState, + long elapsedRealtimeUs, int which) { + return mWifiStateTimer[wifiState].getTotalTimeLocked( + elapsedRealtimeUs, which); } - @Override public long getBluetoothOnTime(long batteryRealtime, int which) { - return mBluetoothOnTimer.getTotalTimeLocked(batteryRealtime, which); + @Override public int getWifiStateCount(int wifiState, int which) { + return mWifiStateTimer[wifiState].getCountLocked(which); + } + + @Override public long getBluetoothOnTime(long elapsedRealtimeUs, int which) { + return mBluetoothOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which); + } + + @Override public long getBluetoothStateTime(int bluetoothState, + long elapsedRealtimeUs, int which) { + return mBluetoothStateTimer[bluetoothState].getTotalTimeLocked( + elapsedRealtimeUs, which); + } + + @Override public int getBluetoothStateCount(int bluetoothState, int which) { + return mBluetoothStateTimer[bluetoothState].getCountLocked(which); + } + + @Override + public long getNetworkActivityBytes(int type, int which) { + if (type >= 0 && type < mNetworkByteActivityCounters.length) { + return mNetworkByteActivityCounters[type].getCountLocked(which); + } else { + return 0; + } } @Override - public long getNetworkActivityCount(int type, int which) { - if (type >= 0 && type < mNetworkActivityCounters.length) { - return mNetworkActivityCounters[type].getCountLocked(which); + public long getNetworkActivityPackets(int type, int which) { + if (type >= 0 && type < mNetworkPacketActivityCounters.length) { + return mNetworkPacketActivityCounters[type].getCountLocked(which); } else { return 0; } } + @Override public long getStartClockTime() { + return mStartClockTime; + } + @Override public boolean getIsOnBattery() { return mOnBattery; } @@ -2627,7 +3459,10 @@ public final class BatteryStatsImpl extends BatteryStats { Counter[] mUserActivityCounters; - LongSamplingCounter[] mNetworkActivityCounters; + LongSamplingCounter[] mNetworkByteActivityCounters; + LongSamplingCounter[] mNetworkPacketActivityCounters; + LongSamplingCounter mMobileRadioActiveTime; + LongSamplingCounter mMobileRadioActiveCount; /** * The statistics we have collected for this uid's wake locks. @@ -2657,14 +3492,14 @@ public final class BatteryStatsImpl extends BatteryStats { public Uid(int uid) { mUid = uid; mWifiRunningTimer = new StopwatchTimer(Uid.this, WIFI_RUNNING, - mWifiRunningTimers, mUnpluggables); + mWifiRunningTimers, mOnBatteryTimeBase); mFullWifiLockTimer = new StopwatchTimer(Uid.this, FULL_WIFI_LOCK, - mFullWifiLockTimers, mUnpluggables); + mFullWifiLockTimers, mOnBatteryTimeBase); mWifiScanTimer = new StopwatchTimer(Uid.this, WIFI_SCAN, - mWifiScanTimers, mUnpluggables); + mWifiScanTimers, mOnBatteryTimeBase); mWifiBatchedScanTimer = new StopwatchTimer[NUM_WIFI_BATCHED_SCAN_BINS]; mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED, - mWifiMulticastTimers, mUnpluggables); + mWifiMulticastTimers, mOnBatteryTimeBase); } @Override @@ -2693,67 +3528,67 @@ public final class BatteryStatsImpl extends BatteryStats { } @Override - public void noteWifiRunningLocked() { + public void noteWifiRunningLocked(long elapsedRealtimeMs) { if (!mWifiRunning) { mWifiRunning = true; if (mWifiRunningTimer == null) { mWifiRunningTimer = new StopwatchTimer(Uid.this, WIFI_RUNNING, - mWifiRunningTimers, mUnpluggables); + mWifiRunningTimers, mOnBatteryTimeBase); } - mWifiRunningTimer.startRunningLocked(BatteryStatsImpl.this); + mWifiRunningTimer.startRunningLocked(elapsedRealtimeMs); } } @Override - public void noteWifiStoppedLocked() { + public void noteWifiStoppedLocked(long elapsedRealtimeMs) { if (mWifiRunning) { mWifiRunning = false; - mWifiRunningTimer.stopRunningLocked(BatteryStatsImpl.this); + mWifiRunningTimer.stopRunningLocked(elapsedRealtimeMs); } } @Override - public void noteFullWifiLockAcquiredLocked() { + public void noteFullWifiLockAcquiredLocked(long elapsedRealtimeMs) { if (!mFullWifiLockOut) { mFullWifiLockOut = true; if (mFullWifiLockTimer == null) { mFullWifiLockTimer = new StopwatchTimer(Uid.this, FULL_WIFI_LOCK, - mFullWifiLockTimers, mUnpluggables); + mFullWifiLockTimers, mOnBatteryTimeBase); } - mFullWifiLockTimer.startRunningLocked(BatteryStatsImpl.this); + mFullWifiLockTimer.startRunningLocked(elapsedRealtimeMs); } } @Override - public void noteFullWifiLockReleasedLocked() { + public void noteFullWifiLockReleasedLocked(long elapsedRealtimeMs) { if (mFullWifiLockOut) { mFullWifiLockOut = false; - mFullWifiLockTimer.stopRunningLocked(BatteryStatsImpl.this); + mFullWifiLockTimer.stopRunningLocked(elapsedRealtimeMs); } } @Override - public void noteWifiScanStartedLocked() { + public void noteWifiScanStartedLocked(long elapsedRealtimeMs) { if (!mWifiScanStarted) { mWifiScanStarted = true; if (mWifiScanTimer == null) { mWifiScanTimer = new StopwatchTimer(Uid.this, WIFI_SCAN, - mWifiScanTimers, mUnpluggables); + mWifiScanTimers, mOnBatteryTimeBase); } - mWifiScanTimer.startRunningLocked(BatteryStatsImpl.this); + mWifiScanTimer.startRunningLocked(elapsedRealtimeMs); } } @Override - public void noteWifiScanStoppedLocked() { + public void noteWifiScanStoppedLocked(long elapsedRealtimeMs) { if (mWifiScanStarted) { mWifiScanStarted = false; - mWifiScanTimer.stopRunningLocked(BatteryStatsImpl.this); + mWifiScanTimer.stopRunningLocked(elapsedRealtimeMs); } } @Override - public void noteWifiBatchedScanStartedLocked(int csph) { + public void noteWifiBatchedScanStartedLocked(int csph, long elapsedRealtimeMs) { int bin = 0; while (csph > 8 && bin < NUM_WIFI_BATCHED_SCAN_BINS) { csph = csph >> 3; @@ -2764,66 +3599,66 @@ public final class BatteryStatsImpl extends BatteryStats { if (mWifiBatchedScanBinStarted != NO_BATCHED_SCAN_STARTED) { mWifiBatchedScanTimer[mWifiBatchedScanBinStarted]. - stopRunningLocked(BatteryStatsImpl.this); + stopRunningLocked(elapsedRealtimeMs); } mWifiBatchedScanBinStarted = bin; if (mWifiBatchedScanTimer[bin] == null) { makeWifiBatchedScanBin(bin, null); } - mWifiBatchedScanTimer[bin].startRunningLocked(BatteryStatsImpl.this); + mWifiBatchedScanTimer[bin].startRunningLocked(elapsedRealtimeMs); } @Override - public void noteWifiBatchedScanStoppedLocked() { + public void noteWifiBatchedScanStoppedLocked(long elapsedRealtimeMs) { if (mWifiBatchedScanBinStarted != NO_BATCHED_SCAN_STARTED) { mWifiBatchedScanTimer[mWifiBatchedScanBinStarted]. - stopRunningLocked(BatteryStatsImpl.this); + stopRunningLocked(elapsedRealtimeMs); mWifiBatchedScanBinStarted = NO_BATCHED_SCAN_STARTED; } } @Override - public void noteWifiMulticastEnabledLocked() { + public void noteWifiMulticastEnabledLocked(long elapsedRealtimeMs) { if (!mWifiMulticastEnabled) { mWifiMulticastEnabled = true; if (mWifiMulticastTimer == null) { mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED, - mWifiMulticastTimers, mUnpluggables); + mWifiMulticastTimers, mOnBatteryTimeBase); } - mWifiMulticastTimer.startRunningLocked(BatteryStatsImpl.this); + mWifiMulticastTimer.startRunningLocked(elapsedRealtimeMs); } } @Override - public void noteWifiMulticastDisabledLocked() { + public void noteWifiMulticastDisabledLocked(long elapsedRealtimeMs) { if (mWifiMulticastEnabled) { mWifiMulticastEnabled = false; - mWifiMulticastTimer.stopRunningLocked(BatteryStatsImpl.this); + mWifiMulticastTimer.stopRunningLocked(elapsedRealtimeMs); } } public StopwatchTimer createAudioTurnedOnTimerLocked() { if (mAudioTurnedOnTimer == null) { mAudioTurnedOnTimer = new StopwatchTimer(Uid.this, AUDIO_TURNED_ON, - null, mUnpluggables); + null, mOnBatteryTimeBase); } return mAudioTurnedOnTimer; } @Override - public void noteAudioTurnedOnLocked() { + public void noteAudioTurnedOnLocked(long elapsedRealtimeMs) { if (!mAudioTurnedOn) { mAudioTurnedOn = true; - createAudioTurnedOnTimerLocked().startRunningLocked(BatteryStatsImpl.this); + createAudioTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs); } } @Override - public void noteAudioTurnedOffLocked() { + public void noteAudioTurnedOffLocked(long elapsedRealtimeMs) { if (mAudioTurnedOn) { mAudioTurnedOn = false; if (mAudioTurnedOnTimer != null) { - mAudioTurnedOnTimer.stopRunningLocked(BatteryStatsImpl.this); + mAudioTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs); } } } @@ -2831,25 +3666,25 @@ public final class BatteryStatsImpl extends BatteryStats { public StopwatchTimer createVideoTurnedOnTimerLocked() { if (mVideoTurnedOnTimer == null) { mVideoTurnedOnTimer = new StopwatchTimer(Uid.this, VIDEO_TURNED_ON, - null, mUnpluggables); + null, mOnBatteryTimeBase); } return mVideoTurnedOnTimer; } @Override - public void noteVideoTurnedOnLocked() { + public void noteVideoTurnedOnLocked(long elapsedRealtimeMs) { if (!mVideoTurnedOn) { mVideoTurnedOn = true; - createVideoTurnedOnTimerLocked().startRunningLocked(BatteryStatsImpl.this); + createVideoTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs); } } @Override - public void noteVideoTurnedOffLocked() { + public void noteVideoTurnedOffLocked(long elapsedRealtimeMs) { if (mVideoTurnedOn) { mVideoTurnedOn = false; if (mVideoTurnedOnTimer != null) { - mVideoTurnedOnTimer.stopRunningLocked(BatteryStatsImpl.this); + mVideoTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs); } } } @@ -2857,28 +3692,27 @@ public final class BatteryStatsImpl extends BatteryStats { public StopwatchTimer createForegroundActivityTimerLocked() { if (mForegroundActivityTimer == null) { mForegroundActivityTimer = new StopwatchTimer( - Uid.this, FOREGROUND_ACTIVITY, null, mUnpluggables); + Uid.this, FOREGROUND_ACTIVITY, null, mOnBatteryTimeBase); } return mForegroundActivityTimer; } @Override - public void noteActivityResumedLocked() { + public void noteActivityResumedLocked(long elapsedRealtimeMs) { // We always start, since we want multiple foreground PIDs to nest - createForegroundActivityTimerLocked().startRunningLocked(BatteryStatsImpl.this); + createForegroundActivityTimerLocked().startRunningLocked(elapsedRealtimeMs); } @Override - public void noteActivityPausedLocked() { + public void noteActivityPausedLocked(long elapsedRealtimeMs) { if (mForegroundActivityTimer != null) { - mForegroundActivityTimer.stopRunningLocked(BatteryStatsImpl.this); + mForegroundActivityTimer.stopRunningLocked(elapsedRealtimeMs); } } public BatchTimer createVibratorOnTimerLocked() { if (mVibratorOnTimer == null) { - mVibratorOnTimer = new BatchTimer(Uid.this, VIBRATOR_ON, - mUnpluggables, BatteryStatsImpl.this.mOnBatteryInternal); + mVibratorOnTimer = new BatchTimer(Uid.this, VIBRATOR_ON, mOnBatteryTimeBase); } return mVibratorOnTimer; } @@ -2894,61 +3728,60 @@ public final class BatteryStatsImpl extends BatteryStats { } @Override - public long getWifiRunningTime(long batteryRealtime, int which) { + public long getWifiRunningTime(long elapsedRealtimeUs, int which) { if (mWifiRunningTimer == null) { return 0; } - return mWifiRunningTimer.getTotalTimeLocked(batteryRealtime, which); + return mWifiRunningTimer.getTotalTimeLocked(elapsedRealtimeUs, which); } @Override - public long getFullWifiLockTime(long batteryRealtime, int which) { + public long getFullWifiLockTime(long elapsedRealtimeUs, int which) { if (mFullWifiLockTimer == null) { return 0; } - return mFullWifiLockTimer.getTotalTimeLocked(batteryRealtime, which); + return mFullWifiLockTimer.getTotalTimeLocked(elapsedRealtimeUs, which); } @Override - public long getWifiScanTime(long batteryRealtime, int which) { + public long getWifiScanTime(long elapsedRealtimeUs, int which) { if (mWifiScanTimer == null) { return 0; } - return mWifiScanTimer.getTotalTimeLocked(batteryRealtime, which); + return mWifiScanTimer.getTotalTimeLocked(elapsedRealtimeUs, which); } @Override - public long getWifiBatchedScanTime(int csphBin, long batteryRealtime, int which) { + public long getWifiBatchedScanTime(int csphBin, long elapsedRealtimeUs, int which) { if (csphBin < 0 || csphBin >= NUM_WIFI_BATCHED_SCAN_BINS) return 0; if (mWifiBatchedScanTimer[csphBin] == null) { return 0; } - return mWifiBatchedScanTimer[csphBin].getTotalTimeLocked(batteryRealtime, which); + return mWifiBatchedScanTimer[csphBin].getTotalTimeLocked(elapsedRealtimeUs, which); } @Override - public long getWifiMulticastTime(long batteryRealtime, int which) { + public long getWifiMulticastTime(long elapsedRealtimeUs, int which) { if (mWifiMulticastTimer == null) { return 0; } - return mWifiMulticastTimer.getTotalTimeLocked(batteryRealtime, - which); + return mWifiMulticastTimer.getTotalTimeLocked(elapsedRealtimeUs, which); } @Override - public long getAudioTurnedOnTime(long batteryRealtime, int which) { + public long getAudioTurnedOnTime(long elapsedRealtimeUs, int which) { if (mAudioTurnedOnTimer == null) { return 0; } - return mAudioTurnedOnTimer.getTotalTimeLocked(batteryRealtime, which); + return mAudioTurnedOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which); } @Override - public long getVideoTurnedOnTime(long batteryRealtime, int which) { + public long getVideoTurnedOnTime(long elapsedRealtimeUs, int which) { if (mVideoTurnedOnTimer == null) { return 0; } - return mVideoTurnedOnTimer.getTotalTimeLocked(batteryRealtime, which); + return mVideoTurnedOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which); } @Override @@ -2997,10 +3830,10 @@ public final class BatteryStatsImpl extends BatteryStats { } if (in == null) { mWifiBatchedScanTimer[i] = new StopwatchTimer(this, WIFI_BATCHED_SCAN, collected, - mUnpluggables); + mOnBatteryTimeBase); } else { mWifiBatchedScanTimer[i] = new StopwatchTimer(this, WIFI_BATCHED_SCAN, collected, - mUnpluggables, in); + mOnBatteryTimeBase, in); } } @@ -3008,42 +3841,77 @@ public final class BatteryStatsImpl extends BatteryStats { void initUserActivityLocked() { mUserActivityCounters = new Counter[NUM_USER_ACTIVITY_TYPES]; for (int i=0; i<NUM_USER_ACTIVITY_TYPES; i++) { - mUserActivityCounters[i] = new Counter(mUnpluggables); + mUserActivityCounters[i] = new Counter(mOnBatteryTimeBase); } } - void noteNetworkActivityLocked(int type, long delta) { - if (mNetworkActivityCounters == null) { + void noteNetworkActivityLocked(int type, long deltaBytes, long deltaPackets) { + if (mNetworkByteActivityCounters == null) { initNetworkActivityLocked(); } if (type >= 0 && type < NUM_NETWORK_ACTIVITY_TYPES) { - mNetworkActivityCounters[type].addCountLocked(delta); + mNetworkByteActivityCounters[type].addCountLocked(deltaBytes); + mNetworkPacketActivityCounters[type].addCountLocked(deltaPackets); } else { Slog.w(TAG, "Unknown network activity type " + type + " was specified.", new Throwable()); } } + void noteMobileRadioActiveTimeLocked(long batteryUptime) { + if (mNetworkByteActivityCounters == null) { + initNetworkActivityLocked(); + } + mMobileRadioActiveTime.addCountLocked(batteryUptime); + mMobileRadioActiveCount.addCountLocked(1); + } + @Override public boolean hasNetworkActivity() { - return mNetworkActivityCounters != null; + return mNetworkByteActivityCounters != null; + } + + @Override + public long getNetworkActivityBytes(int type, int which) { + if (mNetworkByteActivityCounters != null && type >= 0 + && type < mNetworkByteActivityCounters.length) { + return mNetworkByteActivityCounters[type].getCountLocked(which); + } else { + return 0; + } } @Override - public long getNetworkActivityCount(int type, int which) { - if (mNetworkActivityCounters != null && type >= 0 - && type < mNetworkActivityCounters.length) { - return mNetworkActivityCounters[type].getCountLocked(which); + public long getNetworkActivityPackets(int type, int which) { + if (mNetworkPacketActivityCounters != null && type >= 0 + && type < mNetworkPacketActivityCounters.length) { + return mNetworkPacketActivityCounters[type].getCountLocked(which); } else { return 0; } } + @Override + public long getMobileRadioActiveTime(int which) { + return mMobileRadioActiveTime != null + ? mMobileRadioActiveTime.getCountLocked(which) : 0; + } + + @Override + public int getMobileRadioActiveCount(int which) { + return mMobileRadioActiveCount != null + ? (int)mMobileRadioActiveCount.getCountLocked(which) : 0; + } + void initNetworkActivityLocked() { - mNetworkActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES]; + mNetworkByteActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES]; + mNetworkPacketActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES]; for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) { - mNetworkActivityCounters[i] = new LongSamplingCounter(mUnpluggables); + mNetworkByteActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase); + mNetworkPacketActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase); } + mMobileRadioActiveTime = new LongSamplingCounter(mOnBatteryTimeBase); + mMobileRadioActiveCount = new LongSamplingCounter(mOnBatteryTimeBase); } /** @@ -3054,42 +3922,42 @@ public final class BatteryStatsImpl extends BatteryStats { boolean active = false; if (mWifiRunningTimer != null) { - active |= !mWifiRunningTimer.reset(BatteryStatsImpl.this, false); + active |= !mWifiRunningTimer.reset(false); active |= mWifiRunning; } if (mFullWifiLockTimer != null) { - active |= !mFullWifiLockTimer.reset(BatteryStatsImpl.this, false); + active |= !mFullWifiLockTimer.reset(false); active |= mFullWifiLockOut; } if (mWifiScanTimer != null) { - active |= !mWifiScanTimer.reset(BatteryStatsImpl.this, false); + active |= !mWifiScanTimer.reset(false); active |= mWifiScanStarted; } if (mWifiBatchedScanTimer != null) { for (int i = 0; i < NUM_WIFI_BATCHED_SCAN_BINS; i++) { if (mWifiBatchedScanTimer[i] != null) { - active |= !mWifiBatchedScanTimer[i].reset(BatteryStatsImpl.this, false); + active |= !mWifiBatchedScanTimer[i].reset(false); } } active |= (mWifiBatchedScanBinStarted != NO_BATCHED_SCAN_STARTED); } if (mWifiMulticastTimer != null) { - active |= !mWifiMulticastTimer.reset(BatteryStatsImpl.this, false); + active |= !mWifiMulticastTimer.reset(false); active |= mWifiMulticastEnabled; } if (mAudioTurnedOnTimer != null) { - active |= !mAudioTurnedOnTimer.reset(BatteryStatsImpl.this, false); + active |= !mAudioTurnedOnTimer.reset(false); active |= mAudioTurnedOn; } if (mVideoTurnedOnTimer != null) { - active |= !mVideoTurnedOnTimer.reset(BatteryStatsImpl.this, false); + active |= !mVideoTurnedOnTimer.reset(false); active |= mVideoTurnedOn; } if (mForegroundActivityTimer != null) { - active |= !mForegroundActivityTimer.reset(BatteryStatsImpl.this, false); + active |= !mForegroundActivityTimer.reset(false); } if (mVibratorOnTimer != null) { - if (mVibratorOnTimer.reset(BatteryStatsImpl.this, false)) { + if (mVibratorOnTimer.reset(false)) { mVibratorOnTimer.detach(); mVibratorOnTimer = null; } else { @@ -3103,10 +3971,13 @@ public final class BatteryStatsImpl extends BatteryStats { } } - if (mNetworkActivityCounters != null) { + if (mNetworkByteActivityCounters != null) { for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) { - mNetworkActivityCounters[i].reset(false); + mNetworkByteActivityCounters[i].reset(false); + mNetworkPacketActivityCounters[i].reset(false); } + mMobileRadioActiveTime.reset(false); + mMobileRadioActiveCount.reset(false); } if (mWakelockStats.size() > 0) { @@ -3142,10 +4013,12 @@ public final class BatteryStatsImpl extends BatteryStats { mProcessStats.clear(); } if (mPids.size() > 0) { - for (int i=0; !active && i<mPids.size(); i++) { + for (int i=mPids.size()-1; i>=0; i--) { Pid pid = mPids.valueAt(i); - if (pid.mWakeStart != 0) { + if (pid.mWakeNesting > 0) { active = true; + } else { + mPids.removeAt(i); } } } @@ -3167,8 +4040,6 @@ public final class BatteryStatsImpl extends BatteryStats { mPackageStats.clear(); } - mPids.clear(); - if (!active) { if (mWifiRunningTimer != null) { mWifiRunningTimer.detach(); @@ -3204,29 +4075,31 @@ public final class BatteryStatsImpl extends BatteryStats { mUserActivityCounters[i].detach(); } } - if (mNetworkActivityCounters != null) { + if (mNetworkByteActivityCounters != null) { for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) { - mNetworkActivityCounters[i].detach(); + mNetworkByteActivityCounters[i].detach(); + mNetworkPacketActivityCounters[i].detach(); } } + mPids.clear(); } return !active; } - void writeToParcelLocked(Parcel out, long batteryRealtime) { + void writeToParcelLocked(Parcel out, long elapsedRealtimeUs) { out.writeInt(mWakelockStats.size()); for (Map.Entry<String, Uid.Wakelock> wakelockEntry : mWakelockStats.entrySet()) { out.writeString(wakelockEntry.getKey()); Uid.Wakelock wakelock = wakelockEntry.getValue(); - wakelock.writeToParcelLocked(out, batteryRealtime); + wakelock.writeToParcelLocked(out, elapsedRealtimeUs); } out.writeInt(mSensorStats.size()); for (Map.Entry<Integer, Uid.Sensor> sensorEntry : mSensorStats.entrySet()) { out.writeInt(sensorEntry.getKey()); Uid.Sensor sensor = sensorEntry.getValue(); - sensor.writeToParcelLocked(out, batteryRealtime); + sensor.writeToParcelLocked(out, elapsedRealtimeUs); } out.writeInt(mProcessStats.size()); @@ -3245,57 +4118,57 @@ public final class BatteryStatsImpl extends BatteryStats { if (mWifiRunningTimer != null) { out.writeInt(1); - mWifiRunningTimer.writeToParcel(out, batteryRealtime); + mWifiRunningTimer.writeToParcel(out, elapsedRealtimeUs); } else { out.writeInt(0); } if (mFullWifiLockTimer != null) { out.writeInt(1); - mFullWifiLockTimer.writeToParcel(out, batteryRealtime); + mFullWifiLockTimer.writeToParcel(out, elapsedRealtimeUs); } else { out.writeInt(0); } if (mWifiScanTimer != null) { out.writeInt(1); - mWifiScanTimer.writeToParcel(out, batteryRealtime); + mWifiScanTimer.writeToParcel(out, elapsedRealtimeUs); } else { out.writeInt(0); } for (int i = 0; i < NUM_WIFI_BATCHED_SCAN_BINS; i++) { if (mWifiBatchedScanTimer[i] != null) { out.writeInt(1); - mWifiBatchedScanTimer[i].writeToParcel(out, batteryRealtime); + mWifiBatchedScanTimer[i].writeToParcel(out, elapsedRealtimeUs); } else { out.writeInt(0); } } if (mWifiMulticastTimer != null) { out.writeInt(1); - mWifiMulticastTimer.writeToParcel(out, batteryRealtime); + mWifiMulticastTimer.writeToParcel(out, elapsedRealtimeUs); } else { out.writeInt(0); } if (mAudioTurnedOnTimer != null) { out.writeInt(1); - mAudioTurnedOnTimer.writeToParcel(out, batteryRealtime); + mAudioTurnedOnTimer.writeToParcel(out, elapsedRealtimeUs); } else { out.writeInt(0); } if (mVideoTurnedOnTimer != null) { out.writeInt(1); - mVideoTurnedOnTimer.writeToParcel(out, batteryRealtime); + mVideoTurnedOnTimer.writeToParcel(out, elapsedRealtimeUs); } else { out.writeInt(0); } if (mForegroundActivityTimer != null) { out.writeInt(1); - mForegroundActivityTimer.writeToParcel(out, batteryRealtime); + mForegroundActivityTimer.writeToParcel(out, elapsedRealtimeUs); } else { out.writeInt(0); } if (mVibratorOnTimer != null) { out.writeInt(1); - mVibratorOnTimer.writeToParcel(out, batteryRealtime); + mVibratorOnTimer.writeToParcel(out, elapsedRealtimeUs); } else { out.writeInt(0); } @@ -3307,23 +4180,26 @@ public final class BatteryStatsImpl extends BatteryStats { } else { out.writeInt(0); } - if (mNetworkActivityCounters != null) { + if (mNetworkByteActivityCounters != null) { out.writeInt(1); for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) { - mNetworkActivityCounters[i].writeToParcel(out); + mNetworkByteActivityCounters[i].writeToParcel(out); + mNetworkPacketActivityCounters[i].writeToParcel(out); } + mMobileRadioActiveTime.writeToParcel(out); + mMobileRadioActiveCount.writeToParcel(out); } else { out.writeInt(0); } } - void readFromParcelLocked(ArrayList<Unpluggable> unpluggables, Parcel in) { + void readFromParcelLocked(TimeBase timeBase, TimeBase screenOffTimeBase, Parcel in) { int numWakelocks = in.readInt(); mWakelockStats.clear(); for (int j = 0; j < numWakelocks; j++) { String wakelockName = in.readString(); Uid.Wakelock wakelock = new Wakelock(); - wakelock.readFromParcelLocked(unpluggables, in); + wakelock.readFromParcelLocked(timeBase, screenOffTimeBase, in); // We will just drop some random set of wakelocks if // the previous run of the system was an older version // that didn't impose a limit. @@ -3335,7 +4211,7 @@ public final class BatteryStatsImpl extends BatteryStats { for (int k = 0; k < numSensors; k++) { int sensorNumber = in.readInt(); Uid.Sensor sensor = new Sensor(sensorNumber); - sensor.readFromParcelLocked(mUnpluggables, in); + sensor.readFromParcelLocked(mOnBatteryTimeBase, in); mSensorStats.put(sensorNumber, sensor); } @@ -3360,21 +4236,21 @@ public final class BatteryStatsImpl extends BatteryStats { mWifiRunning = false; if (in.readInt() != 0) { mWifiRunningTimer = new StopwatchTimer(Uid.this, WIFI_RUNNING, - mWifiRunningTimers, mUnpluggables, in); + mWifiRunningTimers, mOnBatteryTimeBase, in); } else { mWifiRunningTimer = null; } mFullWifiLockOut = false; if (in.readInt() != 0) { mFullWifiLockTimer = new StopwatchTimer(Uid.this, FULL_WIFI_LOCK, - mFullWifiLockTimers, mUnpluggables, in); + mFullWifiLockTimers, mOnBatteryTimeBase, in); } else { mFullWifiLockTimer = null; } mWifiScanStarted = false; if (in.readInt() != 0) { mWifiScanTimer = new StopwatchTimer(Uid.this, WIFI_SCAN, - mWifiScanTimers, mUnpluggables, in); + mWifiScanTimers, mOnBatteryTimeBase, in); } else { mWifiScanTimer = null; } @@ -3389,51 +4265,58 @@ public final class BatteryStatsImpl extends BatteryStats { mWifiMulticastEnabled = false; if (in.readInt() != 0) { mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED, - mWifiMulticastTimers, mUnpluggables, in); + mWifiMulticastTimers, mOnBatteryTimeBase, in); } else { mWifiMulticastTimer = null; } mAudioTurnedOn = false; if (in.readInt() != 0) { mAudioTurnedOnTimer = new StopwatchTimer(Uid.this, AUDIO_TURNED_ON, - null, mUnpluggables, in); + null, mOnBatteryTimeBase, in); } else { mAudioTurnedOnTimer = null; } mVideoTurnedOn = false; if (in.readInt() != 0) { mVideoTurnedOnTimer = new StopwatchTimer(Uid.this, VIDEO_TURNED_ON, - null, mUnpluggables, in); + null, mOnBatteryTimeBase, in); } else { mVideoTurnedOnTimer = null; } if (in.readInt() != 0) { mForegroundActivityTimer = new StopwatchTimer( - Uid.this, FOREGROUND_ACTIVITY, null, mUnpluggables, in); + Uid.this, FOREGROUND_ACTIVITY, null, mOnBatteryTimeBase, in); } else { mForegroundActivityTimer = null; } if (in.readInt() != 0) { - mVibratorOnTimer = new BatchTimer(Uid.this, VIBRATOR_ON, - mUnpluggables, BatteryStatsImpl.this.mOnBatteryInternal, in); + mVibratorOnTimer = new BatchTimer(Uid.this, VIBRATOR_ON, mOnBatteryTimeBase, in); } else { mVibratorOnTimer = null; } if (in.readInt() != 0) { mUserActivityCounters = new Counter[NUM_USER_ACTIVITY_TYPES]; for (int i=0; i<NUM_USER_ACTIVITY_TYPES; i++) { - mUserActivityCounters[i] = new Counter(mUnpluggables, in); + mUserActivityCounters[i] = new Counter(mOnBatteryTimeBase, in); } } else { mUserActivityCounters = null; } if (in.readInt() != 0) { - mNetworkActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES]; + mNetworkByteActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES]; + mNetworkPacketActivityCounters + = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES]; for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) { - mNetworkActivityCounters[i] = new LongSamplingCounter(mUnpluggables, in); + mNetworkByteActivityCounters[i] + = new LongSamplingCounter(mOnBatteryTimeBase, in); + mNetworkPacketActivityCounters[i] + = new LongSamplingCounter(mOnBatteryTimeBase, in); } + mMobileRadioActiveTime = new LongSamplingCounter(mOnBatteryTimeBase, in); + mMobileRadioActiveCount = new LongSamplingCounter(mOnBatteryTimeBase, in); } else { - mNetworkActivityCounters = null; + mNetworkByteActivityCounters = null; + mNetworkPacketActivityCounters = null; } } @@ -3464,24 +4347,24 @@ public final class BatteryStatsImpl extends BatteryStats { * return a new Timer, or null. */ private StopwatchTimer readTimerFromParcel(int type, ArrayList<StopwatchTimer> pool, - ArrayList<Unpluggable> unpluggables, Parcel in) { + TimeBase timeBase, Parcel in) { if (in.readInt() == 0) { return null; } - return new StopwatchTimer(Uid.this, type, pool, unpluggables, in); + return new StopwatchTimer(Uid.this, type, pool, timeBase, in); } boolean reset() { boolean wlactive = false; if (mTimerFull != null) { - wlactive |= !mTimerFull.reset(BatteryStatsImpl.this, false); + wlactive |= !mTimerFull.reset(false); } if (mTimerPartial != null) { - wlactive |= !mTimerPartial.reset(BatteryStatsImpl.this, false); + wlactive |= !mTimerPartial.reset(false); } if (mTimerWindow != null) { - wlactive |= !mTimerWindow.reset(BatteryStatsImpl.this, false); + wlactive |= !mTimerWindow.reset(false); } if (!wlactive) { if (mTimerFull != null) { @@ -3500,19 +4383,19 @@ public final class BatteryStatsImpl extends BatteryStats { return !wlactive; } - void readFromParcelLocked(ArrayList<Unpluggable> unpluggables, Parcel in) { + void readFromParcelLocked(TimeBase timeBase, TimeBase screenOffTimeBase, Parcel in) { mTimerPartial = readTimerFromParcel(WAKE_TYPE_PARTIAL, - mPartialTimers, unpluggables, in); + mPartialTimers, screenOffTimeBase, in); mTimerFull = readTimerFromParcel(WAKE_TYPE_FULL, - mFullTimers, unpluggables, in); + mFullTimers, timeBase, in); mTimerWindow = readTimerFromParcel(WAKE_TYPE_WINDOW, - mWindowTimers, unpluggables, in); + mWindowTimers, timeBase, in); } - void writeToParcelLocked(Parcel out, long batteryRealtime) { - Timer.writeTimerToParcel(out, mTimerPartial, batteryRealtime); - Timer.writeTimerToParcel(out, mTimerFull, batteryRealtime); - Timer.writeTimerToParcel(out, mTimerWindow, batteryRealtime); + void writeToParcelLocked(Parcel out, long elapsedRealtimeUs) { + Timer.writeTimerToParcel(out, mTimerPartial, elapsedRealtimeUs); + Timer.writeTimerToParcel(out, mTimerFull, elapsedRealtimeUs); + Timer.writeTimerToParcel(out, mTimerWindow, elapsedRealtimeUs); } @Override @@ -3534,8 +4417,7 @@ public final class BatteryStatsImpl extends BatteryStats { mHandle = handle; } - private StopwatchTimer readTimerFromParcel(ArrayList<Unpluggable> unpluggables, - Parcel in) { + private StopwatchTimer readTimerFromParcel(TimeBase timeBase, Parcel in) { if (in.readInt() == 0) { return null; } @@ -3545,23 +4427,23 @@ public final class BatteryStatsImpl extends BatteryStats { pool = new ArrayList<StopwatchTimer>(); mSensorTimers.put(mHandle, pool); } - return new StopwatchTimer(Uid.this, 0, pool, unpluggables, in); + return new StopwatchTimer(Uid.this, 0, pool, timeBase, in); } boolean reset() { - if (mTimer.reset(BatteryStatsImpl.this, true)) { + if (mTimer.reset(true)) { mTimer = null; return true; } return false; } - void readFromParcelLocked(ArrayList<Unpluggable> unpluggables, Parcel in) { - mTimer = readTimerFromParcel(unpluggables, in); + void readFromParcelLocked(TimeBase timeBase, Parcel in) { + mTimer = readTimerFromParcel(timeBase, in); } - void writeToParcelLocked(Parcel out, long batteryRealtime) { - Timer.writeTimerToParcel(out, mTimer, batteryRealtime); + void writeToParcelLocked(Parcel out, long elapsedRealtimeUs) { + Timer.writeTimerToParcel(out, mTimer, elapsedRealtimeUs); } @Override @@ -3578,7 +4460,12 @@ public final class BatteryStatsImpl extends BatteryStats { /** * The statistics associated with a particular process. */ - public final class Proc extends BatteryStats.Uid.Proc implements Unpluggable { + public final class Proc extends BatteryStats.Uid.Proc implements TimeBaseObs { + /** + * Remains true until removed from the stats. + */ + boolean mActive = true; + /** * Total time (in 1/100 sec) spent executing in user code. */ @@ -3664,26 +4551,27 @@ public final class BatteryStatsImpl extends BatteryStats { ArrayList<ExcessivePower> mExcessivePower; Proc() { - mUnpluggables.add(this); + mOnBatteryTimeBase.add(this); mSpeedBins = new SamplingCounter[getCpuSpeedSteps()]; } - public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) { + public void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime) { mUnpluggedUserTime = mUserTime; mUnpluggedSystemTime = mSystemTime; mUnpluggedForegroundTime = mForegroundTime; mUnpluggedStarts = mStarts; } - public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) { + public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) { } void detach() { - mUnpluggables.remove(this); + mActive = false; + mOnBatteryTimeBase.remove(this); for (int i = 0; i < mSpeedBins.length; i++) { SamplingCounter c = mSpeedBins[i]; if (c != null) { - mUnpluggables.remove(c); + mOnBatteryTimeBase.remove(c); mSpeedBins[i] = null; } } @@ -3812,7 +4700,7 @@ public final class BatteryStatsImpl extends BatteryStats { mSpeedBins = new SamplingCounter[bins >= steps ? bins : steps]; for (int i = 0; i < bins; i++) { if (in.readInt() != 0) { - mSpeedBins[i] = new SamplingCounter(mUnpluggables, in); + mSpeedBins[i] = new SamplingCounter(mOnBatteryTimeBase, in); } } @@ -3837,6 +4725,11 @@ public final class BatteryStatsImpl extends BatteryStats { } @Override + public boolean isActive() { + return mActive; + } + + @Override public long getUserTime(int which) { long val; if (which == STATS_LAST) { @@ -3907,7 +4800,7 @@ public final class BatteryStatsImpl extends BatteryStats { if (amt != 0) { SamplingCounter c = mSpeedBins[i]; if (c == null) { - mSpeedBins[i] = c = new SamplingCounter(mUnpluggables); + mSpeedBins[i] = c = new SamplingCounter(mOnBatteryTimeBase); } c.addCountAtomic(values[i]); } @@ -3928,7 +4821,7 @@ public final class BatteryStatsImpl extends BatteryStats { /** * The statistics associated with a particular package. */ - public final class Pkg extends BatteryStats.Uid.Pkg implements Unpluggable { + public final class Pkg extends BatteryStats.Uid.Pkg implements TimeBaseObs { /** * Number of times this package has done something that could wake up the * device from sleep. @@ -3959,18 +4852,18 @@ public final class BatteryStatsImpl extends BatteryStats { final HashMap<String, Serv> mServiceStats = new HashMap<String, Serv>(); Pkg() { - mUnpluggables.add(this); + mOnBatteryScreenOffTimeBase.add(this); } - public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) { + public void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime) { mUnpluggedWakeups = mWakeups; } - public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) { + public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) { } void detach() { - mUnpluggables.remove(this); + mOnBatteryScreenOffTimeBase.remove(this); } void readFromParcelLocked(Parcel in) { @@ -4029,7 +4922,7 @@ public final class BatteryStatsImpl extends BatteryStats { /** * The statistics associated with a particular service. */ - public final class Serv extends BatteryStats.Uid.Pkg.Serv implements Unpluggable { + public final class Serv extends BatteryStats.Uid.Pkg.Serv implements TimeBaseObs { /** * Total time (ms in battery uptime) the service has been left started. */ @@ -4121,20 +5014,22 @@ public final class BatteryStatsImpl extends BatteryStats { int mUnpluggedLaunches; Serv() { - mUnpluggables.add(this); + mOnBatteryTimeBase.add(this); } - public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) { - mUnpluggedStartTime = getStartTimeToNowLocked(batteryUptime); + public void onTimeStarted(long elapsedRealtime, long baseUptime, + long baseRealtime) { + mUnpluggedStartTime = getStartTimeToNowLocked(baseUptime); mUnpluggedStarts = mStarts; mUnpluggedLaunches = mLaunches; } - public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) { + public void onTimeStopped(long elapsedRealtime, long baseUptime, + long baseRealtime) { } void detach() { - mUnpluggables.remove(this); + mOnBatteryTimeBase.remove(this); } void readFromParcelLocked(Parcel in) { @@ -4369,7 +5264,7 @@ public final class BatteryStatsImpl extends BatteryStats { t = wl.mTimerPartial; if (t == null) { t = new StopwatchTimer(Uid.this, WAKE_TYPE_PARTIAL, - mPartialTimers, mUnpluggables); + mPartialTimers, mOnBatteryScreenOffTimeBase); wl.mTimerPartial = t; } return t; @@ -4377,7 +5272,7 @@ public final class BatteryStatsImpl extends BatteryStats { t = wl.mTimerFull; if (t == null) { t = new StopwatchTimer(Uid.this, WAKE_TYPE_FULL, - mFullTimers, mUnpluggables); + mFullTimers, mOnBatteryTimeBase); wl.mTimerFull = t; } return t; @@ -4385,7 +5280,7 @@ public final class BatteryStatsImpl extends BatteryStats { t = wl.mTimerWindow; if (t == null) { t = new StopwatchTimer(Uid.this, WAKE_TYPE_WINDOW, - mWindowTimers, mUnpluggables); + mWindowTimers, mOnBatteryTimeBase); wl.mTimerWindow = t; } return t; @@ -4412,34 +5307,36 @@ public final class BatteryStatsImpl extends BatteryStats { timers = new ArrayList<StopwatchTimer>(); mSensorTimers.put(sensor, timers); } - t = new StopwatchTimer(Uid.this, BatteryStats.SENSOR, timers, mUnpluggables); + t = new StopwatchTimer(Uid.this, BatteryStats.SENSOR, timers, mOnBatteryTimeBase); se.mTimer = t; return t; } - public void noteStartWakeLocked(int pid, String name, int type) { + public void noteStartWakeLocked(int pid, String name, int type, long elapsedRealtimeMs) { StopwatchTimer t = getWakeTimerLocked(name, type); if (t != null) { - t.startRunningLocked(BatteryStatsImpl.this); + t.startRunningLocked(elapsedRealtimeMs); } if (pid >= 0 && type == WAKE_TYPE_PARTIAL) { Pid p = getPidStatsLocked(pid); - if (p.mWakeStart == 0) { - p.mWakeStart = SystemClock.elapsedRealtime(); + if (p.mWakeNesting++ == 0) { + p.mWakeStartMs = elapsedRealtimeMs; } } } - public void noteStopWakeLocked(int pid, String name, int type) { + public void noteStopWakeLocked(int pid, String name, int type, long elapsedRealtimeMs) { StopwatchTimer t = getWakeTimerLocked(name, type); if (t != null) { - t.stopRunningLocked(BatteryStatsImpl.this); + t.stopRunningLocked(elapsedRealtimeMs); } if (pid >= 0 && type == WAKE_TYPE_PARTIAL) { Pid p = mPids.get(pid); - if (p != null && p.mWakeStart != 0) { - p.mWakeSum += SystemClock.elapsedRealtime() - p.mWakeStart; - p.mWakeStart = 0; + if (p != null && p.mWakeNesting > 0) { + if (p.mWakeNesting-- == 1) { + p.mWakeSumMs += elapsedRealtimeMs - p.mWakeStartMs; + p.mWakeStartMs = 0; + } } } } @@ -4458,32 +5355,32 @@ public final class BatteryStatsImpl extends BatteryStats { } } - public void noteStartSensor(int sensor) { + public void noteStartSensor(int sensor, long elapsedRealtimeMs) { StopwatchTimer t = getSensorTimerLocked(sensor, true); if (t != null) { - t.startRunningLocked(BatteryStatsImpl.this); + t.startRunningLocked(elapsedRealtimeMs); } } - public void noteStopSensor(int sensor) { + public void noteStopSensor(int sensor, long elapsedRealtimeMs) { // Don't create a timer if one doesn't already exist StopwatchTimer t = getSensorTimerLocked(sensor, false); if (t != null) { - t.stopRunningLocked(BatteryStatsImpl.this); + t.stopRunningLocked(elapsedRealtimeMs); } } - public void noteStartGps() { + public void noteStartGps(long elapsedRealtimeMs) { StopwatchTimer t = getSensorTimerLocked(Sensor.GPS, true); if (t != null) { - t.startRunningLocked(BatteryStatsImpl.this); + t.startRunningLocked(elapsedRealtimeMs); } } - public void noteStopGps() { + public void noteStopGps(long elapsedRealtimeMs) { StopwatchTimer t = getSensorTimerLocked(Sensor.GPS, false); if (t != null) { - t.stopRunningLocked(BatteryStatsImpl.this); + t.stopRunningLocked(elapsedRealtimeMs); } } @@ -4496,35 +5393,46 @@ public final class BatteryStatsImpl extends BatteryStats { mFile = new JournaledFile(new File(filename), new File(filename + ".tmp")); mHandler = new MyHandler(handler.getLooper()); mStartCount++; - mScreenOnTimer = new StopwatchTimer(null, -1, null, mUnpluggables); + mScreenOnTimer = new StopwatchTimer(null, -1, null, mOnBatteryTimeBase); for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) { - mScreenBrightnessTimer[i] = new StopwatchTimer(null, -100-i, null, mUnpluggables); + mScreenBrightnessTimer[i] = new StopwatchTimer(null, -100-i, null, mOnBatteryTimeBase); } - mInputEventCounter = new Counter(mUnpluggables); - mPhoneOnTimer = new StopwatchTimer(null, -2, null, mUnpluggables); + mInputEventCounter = new Counter(mOnBatteryTimeBase); + mPhoneOnTimer = new StopwatchTimer(null, -2, null, mOnBatteryTimeBase); for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) { - mPhoneSignalStrengthsTimer[i] = new StopwatchTimer(null, -200-i, null, mUnpluggables); + mPhoneSignalStrengthsTimer[i] = new StopwatchTimer(null, -200-i, null, + mOnBatteryTimeBase); } - mPhoneSignalScanningTimer = new StopwatchTimer(null, -200+1, null, mUnpluggables); + mPhoneSignalScanningTimer = new StopwatchTimer(null, -200+1, null, mOnBatteryTimeBase); for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) { - mPhoneDataConnectionsTimer[i] = new StopwatchTimer(null, -300-i, null, mUnpluggables); + mPhoneDataConnectionsTimer[i] = new StopwatchTimer(null, -300-i, null, + mOnBatteryTimeBase); } for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) { - mNetworkActivityCounters[i] = new LongSamplingCounter(mUnpluggables); - } - mWifiOnTimer = new StopwatchTimer(null, -3, null, mUnpluggables); - mGlobalWifiRunningTimer = new StopwatchTimer(null, -4, null, mUnpluggables); - mBluetoothOnTimer = new StopwatchTimer(null, -5, null, mUnpluggables); - mAudioOnTimer = new StopwatchTimer(null, -6, null, mUnpluggables); - mVideoOnTimer = new StopwatchTimer(null, -7, null, mUnpluggables); + mNetworkByteActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase); + mNetworkPacketActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase); + } + mMobileRadioActiveTimer = new StopwatchTimer(null, -400, null, mOnBatteryTimeBase); + mMobileRadioActivePerAppTimer = new StopwatchTimer(null, -401, null, mOnBatteryTimeBase); + mMobileRadioActiveUnknownTime = new LongSamplingCounter(mOnBatteryTimeBase); + mMobileRadioActiveUnknownCount = new LongSamplingCounter(mOnBatteryTimeBase); + mWifiOnTimer = new StopwatchTimer(null, -3, null, mOnBatteryTimeBase); + mGlobalWifiRunningTimer = new StopwatchTimer(null, -4, null, mOnBatteryTimeBase); + for (int i=0; i<NUM_WIFI_STATES; i++) { + mWifiStateTimer[i] = new StopwatchTimer(null, -600-i, null, mOnBatteryTimeBase); + } + mBluetoothOnTimer = new StopwatchTimer(null, -5, null, mOnBatteryTimeBase); + for (int i=0; i< NUM_BLUETOOTH_STATES; i++) { + mBluetoothStateTimer[i] = new StopwatchTimer(null, -500-i, null, mOnBatteryTimeBase); + } + mAudioOnTimer = new StopwatchTimer(null, -6, null, mOnBatteryTimeBase); + mVideoOnTimer = new StopwatchTimer(null, -7, null, mOnBatteryTimeBase); mOnBattery = mOnBatteryInternal = false; - initTimes(); - mTrackBatteryPastUptime = 0; - mTrackBatteryPastRealtime = 0; - mUptimeStart = mTrackBatteryUptimeStart = SystemClock.uptimeMillis() * 1000; - mRealtimeStart = mTrackBatteryRealtimeStart = SystemClock.elapsedRealtime() * 1000; - mUnpluggedBatteryUptime = getBatteryUptimeLocked(mUptimeStart); - mUnpluggedBatteryRealtime = getBatteryRealtimeLocked(mRealtimeStart); + long uptime = SystemClock.uptimeMillis() * 1000; + long realtime = SystemClock.elapsedRealtime() * 1000; + initTimes(uptime, realtime); + mUptimeStart = uptime; + mRealtimeStart = realtime; mDischargeStartLevel = 0; mDischargeUnplugLevel = 0; mDischargeCurrentLevel = 0; @@ -4557,18 +5465,21 @@ public final class BatteryStatsImpl extends BatteryStats { public boolean startIteratingOldHistoryLocked() { if (DEBUG_HISTORY) Slog.i(TAG, "ITERATING: buff size=" + mHistoryBuffer.dataSize() + " pos=" + mHistoryBuffer.dataPosition()); + if ((mHistoryIterator = mHistory) == null) { + return false; + } mHistoryBuffer.setDataPosition(0); mHistoryReadTmp.clear(); mReadOverflow = false; mIteratingHistory = true; - return (mHistoryIterator = mHistory) != null; + return true; } @Override public boolean getNextOldHistoryLocked(HistoryItem out) { boolean end = mHistoryBuffer.dataPosition() >= mHistoryBuffer.dataSize(); if (!end) { - mHistoryReadTmp.readDelta(mHistoryBuffer); + readHistoryDelta(mHistoryBuffer, mHistoryReadTmp); mReadOverflow |= mHistoryReadTmp.cmd == HistoryItem.CMD_OVERFLOW; } HistoryItem cur = mHistoryIterator; @@ -4588,9 +5499,9 @@ public final class BatteryStatsImpl extends BatteryStats { PrintWriter pw = new FastPrintWriter(new LogWriter(android.util.Log.WARN, TAG)); pw.println("Histories differ!"); pw.println("Old history:"); - (new HistoryPrinter()).printNextItem(pw, out, now); + (new HistoryPrinter()).printNextItem(pw, out, now, false); pw.println("New history:"); - (new HistoryPrinter()).printNextItem(pw, mHistoryReadTmp, now); + (new HistoryPrinter()).printNextItem(pw, mHistoryReadTmp, now, false); pw.flush(); } } @@ -4601,16 +5512,60 @@ public final class BatteryStatsImpl extends BatteryStats { public void finishIteratingOldHistoryLocked() { mIteratingHistory = false; mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize()); + mHistoryIterator = null; + } + + public int getHistoryTotalSize() { + return MAX_HISTORY_BUFFER; + } + + public int getHistoryUsedSize() { + return mHistoryBuffer.dataSize(); } @Override public boolean startIteratingHistoryLocked() { if (DEBUG_HISTORY) Slog.i(TAG, "ITERATING: buff size=" + mHistoryBuffer.dataSize() + " pos=" + mHistoryBuffer.dataPosition()); + if (mHistoryBuffer.dataSize() <= 0) { + return false; + } mHistoryBuffer.setDataPosition(0); mReadOverflow = false; mIteratingHistory = true; - return mHistoryBuffer.dataSize() > 0; + mReadHistoryStrings = new String[mHistoryTagPool.size()]; + mReadHistoryUids = new int[mHistoryTagPool.size()]; + mReadHistoryChars = 0; + for (HashMap.Entry<HistoryTag, Integer> ent : mHistoryTagPool.entrySet()) { + final HistoryTag tag = ent.getKey(); + final int idx = ent.getValue(); + mReadHistoryStrings[idx] = tag.string; + mReadHistoryUids[idx] = tag.uid; + mReadHistoryChars += tag.string.length() + 1; + } + return true; + } + + @Override + public int getHistoryStringPoolSize() { + return mReadHistoryStrings.length; + } + + @Override + public int getHistoryStringPoolBytes() { + // Each entry is a fixed 12 bytes: 4 for index, 4 for uid, 4 for string size + // Each string character is 2 bytes. + return (mReadHistoryStrings.length * 12) + (mReadHistoryChars * 2); + } + + @Override + public String getHistoryTagPoolString(int index) { + return mReadHistoryStrings[index]; + } + + @Override + public int getHistoryTagPoolUid(int index) { + return mReadHistoryUids[index]; } @Override @@ -4624,7 +5579,7 @@ public final class BatteryStatsImpl extends BatteryStats { return false; } - out.readDelta(mHistoryBuffer); + readHistoryDelta(mHistoryBuffer, out); return true; } @@ -4632,6 +5587,7 @@ public final class BatteryStatsImpl extends BatteryStats { public void finishIteratingHistoryLocked() { mIteratingHistory = false; mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize()); + mReadHistoryStrings = null; } @Override @@ -4652,13 +5608,12 @@ public final class BatteryStatsImpl extends BatteryStats { return mScreenOn; } - void initTimes() { - mBatteryRealtime = mTrackBatteryPastUptime = 0; - mBatteryUptime = mTrackBatteryPastRealtime = 0; - mUptimeStart = mTrackBatteryUptimeStart = SystemClock.uptimeMillis() * 1000; - mRealtimeStart = mTrackBatteryRealtimeStart = SystemClock.elapsedRealtime() * 1000; - mUnpluggedBatteryUptime = getBatteryUptimeLocked(mUptimeStart); - mUnpluggedBatteryRealtime = getBatteryRealtimeLocked(mRealtimeStart); + void initTimes(long uptime, long realtime) { + mStartClockTime = System.currentTimeMillis(); + mOnBatteryTimeBase.init(uptime, realtime); + mOnBatteryScreenOffTimeBase.init(uptime, realtime); + mUptimeStart = uptime; + mRealtimeStart = realtime; } void initDischarge() { @@ -4669,31 +5624,67 @@ public final class BatteryStatsImpl extends BatteryStats { mDischargeAmountScreenOff = 0; mDischargeAmountScreenOffSinceCharge = 0; } - - public void resetAllStatsLocked() { + + public void resetAllStatsCmdLocked() { + resetAllStatsLocked(); + long uptime = SystemClock.uptimeMillis() * 1000; + long mSecRealtime = SystemClock.elapsedRealtime(); + long realtime = mSecRealtime * 1000; + mDischargeStartLevel = mHistoryCur.batteryLevel; + pullPendingStateUpdatesLocked(); + addHistoryRecordLocked(mSecRealtime); + mDischargeCurrentLevel = mDischargeUnplugLevel = mHistoryCur.batteryLevel; + mOnBatteryTimeBase.reset(uptime, realtime); + mOnBatteryScreenOffTimeBase.reset(uptime, realtime); + if ((mHistoryCur.states&HistoryItem.STATE_BATTERY_PLUGGED_FLAG) == 0) { + if (mScreenOn) { + mDischargeScreenOnUnplugLevel = mHistoryCur.batteryLevel; + mDischargeScreenOffUnplugLevel = 0; + } else { + mDischargeScreenOnUnplugLevel = 0; + mDischargeScreenOffUnplugLevel = mHistoryCur.batteryLevel; + } + mDischargeAmountScreenOn = 0; + mDischargeAmountScreenOff = 0; + } + initActiveHistoryEventsLocked(mSecRealtime); + } + + private void resetAllStatsLocked() { mStartCount = 0; - initTimes(); - mScreenOnTimer.reset(this, false); + initTimes(SystemClock.uptimeMillis() * 1000, SystemClock.elapsedRealtime() * 1000); + mScreenOnTimer.reset(false); for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) { - mScreenBrightnessTimer[i].reset(this, false); + mScreenBrightnessTimer[i].reset(false); } mInputEventCounter.reset(false); - mPhoneOnTimer.reset(this, false); - mAudioOnTimer.reset(this, false); - mVideoOnTimer.reset(this, false); + mPhoneOnTimer.reset(false); + mAudioOnTimer.reset(false); + mVideoOnTimer.reset(false); for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) { - mPhoneSignalStrengthsTimer[i].reset(this, false); + mPhoneSignalStrengthsTimer[i].reset(false); } - mPhoneSignalScanningTimer.reset(this, false); + mPhoneSignalScanningTimer.reset(false); for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) { - mPhoneDataConnectionsTimer[i].reset(this, false); + mPhoneDataConnectionsTimer[i].reset(false); } for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) { - mNetworkActivityCounters[i].reset(false); + mNetworkByteActivityCounters[i].reset(false); + mNetworkPacketActivityCounters[i].reset(false); + } + mMobileRadioActiveTimer.reset(false); + mMobileRadioActivePerAppTimer.reset(false); + mMobileRadioActiveUnknownTime.reset(false); + mMobileRadioActiveUnknownCount.reset(false); + mWifiOnTimer.reset(false); + mGlobalWifiRunningTimer.reset(false); + for (int i=0; i<NUM_WIFI_STATES; i++) { + mWifiStateTimer[i].reset(false); + } + mBluetoothOnTimer.reset(false); + for (int i=0; i< NUM_BLUETOOTH_STATES; i++) { + mBluetoothStateTimer[i].reset(false); } - mWifiOnTimer.reset(this, false); - mGlobalWifiRunningTimer.reset(this, false); - mBluetoothOnTimer.reset(this, false); for (int i=0; i<mUidStats.size(); i++) { if (mUidStats.valueAt(i).reset()) { @@ -4704,7 +5695,7 @@ public final class BatteryStatsImpl extends BatteryStats { if (mKernelWakelockStats.size() > 0) { for (SamplingTimer timer : mKernelWakelockStats.values()) { - mUnpluggables.remove(timer); + mOnBatteryScreenOffTimeBase.remove(timer); } mKernelWakelockStats.clear(); } @@ -4714,6 +5705,23 @@ public final class BatteryStatsImpl extends BatteryStats { clearHistoryLocked(); } + private void initActiveHistoryEventsLocked(long nowRealtime) { + for (int i=0; i<HistoryItem.EVENT_COUNT; i++) { + HashMap<String, SparseBooleanArray> active = mActiveEvents[i]; + if (active == null) { + continue; + } + for (HashMap.Entry<String, SparseBooleanArray> ent : active.entrySet()) { + SparseBooleanArray uids = ent.getValue(); + for (int j=0; j<uids.size(); j++) { + if (uids.valueAt(j)) { + addHistoryEventLocked(nowRealtime, i, ent.getKey(), uids.keyAt(j)); + } + } + } + } + } + void updateDischargeScreenLevelsLocked(boolean oldScreenOn, boolean newScreenOn) { if (oldScreenOn) { int diff = mDischargeScreenOnUnplugLevel - mDischargeCurrentLevel; @@ -4737,9 +5745,11 @@ public final class BatteryStatsImpl extends BatteryStats { } } - void setOnBattery(boolean onBattery, int oldStatus, int level) { - synchronized(this) { - setOnBatteryLocked(onBattery, oldStatus, level); + public void pullPendingStateUpdatesLocked() { + updateKernelWakelocksLocked(); + updateNetworkActivityLocked(NET_UPDATE_ALL, SystemClock.elapsedRealtime()); + if (mOnBatteryInternal) { + updateDischargeScreenLevelsLocked(mScreenOn, mScreenOn); } } @@ -4758,24 +5768,24 @@ public final class BatteryStatsImpl extends BatteryStats { // battery was last full, or the level is at 100, or // we have gone through a significant charge (from a very low // level to a now very high level). + boolean reset = false; if (oldStatus == BatteryManager.BATTERY_STATUS_FULL || level >= 90 || (mDischargeCurrentLevel < 20 && level >= 80)) { doWrite = true; resetAllStatsLocked(); mDischargeStartLevel = level; + reset = true; } - updateKernelWakelocksLocked(); - updateNetworkActivityLocked(); + pullPendingStateUpdatesLocked(); mHistoryCur.batteryLevel = (byte)level; mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Battery unplugged to: " + Integer.toHexString(mHistoryCur.states)); + mHistoryCur.currentTime = System.currentTimeMillis(); + addHistoryBufferLocked(mSecRealtime, HistoryItem.CMD_CURRENT_TIME); + mHistoryCur.currentTime = 0; addHistoryRecordLocked(mSecRealtime); - mTrackBatteryUptimeStart = uptime; - mTrackBatteryRealtimeStart = realtime; - mUnpluggedBatteryUptime = getBatteryUptimeLocked(uptime); - mUnpluggedBatteryRealtime = getBatteryRealtimeLocked(realtime); mDischargeCurrentLevel = mDischargeUnplugLevel = level; if (mScreenOn) { mDischargeScreenOnUnplugLevel = level; @@ -4786,24 +5796,24 @@ public final class BatteryStatsImpl extends BatteryStats { } mDischargeAmountScreenOn = 0; mDischargeAmountScreenOff = 0; - doUnplugLocked(realtime, mUnpluggedBatteryUptime, mUnpluggedBatteryRealtime); + updateTimeBasesLocked(true, !mScreenOn, uptime, realtime); + if (reset) { + initActiveHistoryEventsLocked(mSecRealtime); + } } else { - updateKernelWakelocksLocked(); - updateNetworkActivityLocked(); + pullPendingStateUpdatesLocked(); mHistoryCur.batteryLevel = (byte)level; mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Battery plugged to: " + Integer.toHexString(mHistoryCur.states)); addHistoryRecordLocked(mSecRealtime); - mTrackBatteryPastUptime += uptime - mTrackBatteryUptimeStart; - mTrackBatteryPastRealtime += realtime - mTrackBatteryRealtimeStart; mDischargeCurrentLevel = level; if (level < mDischargeUnplugLevel) { mLowDischargeAmountSinceCharge += mDischargeUnplugLevel-level-1; mHighDischargeAmountSinceCharge += mDischargeUnplugLevel-level; } updateDischargeScreenLevelsLocked(mScreenOn, mScreenOn); - doPlugLocked(realtime, getBatteryUptimeLocked(uptime), getBatteryRealtimeLocked(realtime)); + updateTimeBasesLocked(false, !mScreenOn, uptime, realtime); } if (doWrite || (mLastWriteTime + (60 * 1000)) < mSecRealtime) { if (mFile != null) { @@ -4882,7 +5892,7 @@ public final class BatteryStatsImpl extends BatteryStats { if (!onBattery && status == BatteryManager.BATTERY_STATUS_FULL) { // We don't record history while we are plugged in and fully charged. // The next time we are unplugged, history will be cleared. - mRecordingHistory = false; + mRecordingHistory = DEBUG; } } } @@ -4902,8 +5912,8 @@ public final class BatteryStatsImpl extends BatteryStats { SamplingTimer kwlt = mKernelWakelockStats.get(name); if (kwlt == null) { - kwlt = new SamplingTimer(mUnpluggables, mOnBatteryInternal, - true /* track reported values */); + kwlt = new SamplingTimer(mOnBatteryScreenOffTimeBase, + true /* track reported val */); mKernelWakelockStats.put(name, kwlt); } kwlt.updateCurrentReportedCount(kws.mCount); @@ -4922,48 +5932,124 @@ public final class BatteryStatsImpl extends BatteryStats { } } - private void updateNetworkActivityLocked() { + static final int NET_UPDATE_MOBILE = 1<<0; + static final int NET_UPDATE_WIFI = 1<<1; + static final int NET_UPDATE_ALL = 0xffff; + + private void updateNetworkActivityLocked(int which, long elapsedRealtimeMs) { if (!SystemProperties.getBoolean(PROP_QTAGUID_ENABLED, false)) return; - final NetworkStats snapshot; - try { - snapshot = mNetworkStatsFactory.readNetworkStatsDetail(); - } catch (IOException e) { - Log.wtf(TAG, "Failed to read network stats", e); - return; - } + if ((which&NET_UPDATE_MOBILE) != 0 && mMobileIfaces.length > 0) { + final NetworkStats snapshot; + final NetworkStats last = mCurMobileSnapshot; + try { + snapshot = mNetworkStatsFactory.readNetworkStatsDetail(UID_ALL, + mMobileIfaces, NetworkStats.TAG_NONE, mLastMobileSnapshot); + } catch (IOException e) { + Log.wtf(TAG, "Failed to read mobile network stats", e); + return; + } - if (mLastSnapshot == null) { - mLastSnapshot = snapshot; - return; - } + mCurMobileSnapshot = snapshot; + mLastMobileSnapshot = last; - final NetworkStats delta = snapshot.subtract(mLastSnapshot); - mLastSnapshot = snapshot; + if (mOnBatteryInternal) { + final NetworkStats delta = NetworkStats.subtract(snapshot, last, + null, null, mTmpNetworkStats); + mTmpNetworkStats = delta; + + long radioTime = mMobileRadioActivePerAppTimer.checkpointRunningLocked( + elapsedRealtimeMs); + long totalPackets = delta.getTotalPackets(); + + final int size = delta.size(); + for (int i = 0; i < size; i++) { + final NetworkStats.Entry entry = delta.getValues(i, mTmpNetworkStatsEntry); + + if (entry.rxBytes == 0 || entry.txBytes == 0) continue; + + final Uid u = getUidStatsLocked(mapUid(entry.uid)); + u.noteNetworkActivityLocked(NETWORK_MOBILE_RX_DATA, entry.rxBytes, + entry.rxPackets); + u.noteNetworkActivityLocked(NETWORK_MOBILE_TX_DATA, entry.txBytes, + entry.txPackets); + + if (radioTime > 0) { + // Distribute total radio active time in to this app. + long appPackets = entry.rxPackets + entry.txPackets; + long appRadioTime = (radioTime*appPackets)/totalPackets; + u.noteMobileRadioActiveTimeLocked(appRadioTime); + // Remove this app from the totals, so that we don't lose any time + // due to rounding. + radioTime -= appRadioTime; + totalPackets -= appPackets; + } - NetworkStats.Entry entry = null; - final int size = delta.size(); - for (int i = 0; i < size; i++) { - entry = delta.getValues(i, entry); + mNetworkByteActivityCounters[NETWORK_MOBILE_RX_DATA].addCountLocked( + entry.rxBytes); + mNetworkByteActivityCounters[NETWORK_MOBILE_TX_DATA].addCountLocked( + entry.txBytes); + mNetworkPacketActivityCounters[NETWORK_MOBILE_RX_DATA].addCountLocked( + entry.rxPackets); + mNetworkPacketActivityCounters[NETWORK_MOBILE_TX_DATA].addCountLocked( + entry.txPackets); + } + + if (radioTime > 0) { + // Whoops, there is some radio time we can't blame on an app! + mMobileRadioActiveUnknownTime.addCountLocked(radioTime); + mMobileRadioActiveUnknownCount.addCountLocked(1); + } + } + } - if (entry.rxBytes == 0 || entry.txBytes == 0) continue; - if (entry.tag != NetworkStats.TAG_NONE) continue; + if ((which&NET_UPDATE_WIFI) != 0 && mWifiIfaces.length > 0) { + final NetworkStats snapshot; + final NetworkStats last = mCurWifiSnapshot; + try { + snapshot = mNetworkStatsFactory.readNetworkStatsDetail(UID_ALL, + mWifiIfaces, NetworkStats.TAG_NONE, mLastWifiSnapshot); + } catch (IOException e) { + Log.wtf(TAG, "Failed to read wifi network stats", e); + return; + } - final Uid u = getUidStatsLocked(entry.uid); + mCurWifiSnapshot = snapshot; + mLastWifiSnapshot = last; - if (mMobileIfaces.contains(entry.iface)) { - u.noteNetworkActivityLocked(NETWORK_MOBILE_RX_BYTES, entry.rxBytes); - u.noteNetworkActivityLocked(NETWORK_MOBILE_TX_BYTES, entry.txBytes); + if (mOnBatteryInternal) { + final NetworkStats delta = NetworkStats.subtract(snapshot, last, + null, null, mTmpNetworkStats); + mTmpNetworkStats = delta; + + final int size = delta.size(); + for (int i = 0; i < size; i++) { + final NetworkStats.Entry entry = delta.getValues(i, mTmpNetworkStatsEntry); + + if (DEBUG) { + final NetworkStats.Entry cur = snapshot.getValues(i, null); + Slog.d(TAG, "Wifi uid " + entry.uid + ": delta rx=" + entry.rxBytes + + " tx=" + entry.txBytes + ", cur rx=" + cur.rxBytes + + " tx=" + cur.txBytes); + } - mNetworkActivityCounters[NETWORK_MOBILE_RX_BYTES].addCountLocked(entry.rxBytes); - mNetworkActivityCounters[NETWORK_MOBILE_TX_BYTES].addCountLocked(entry.txBytes); + if (entry.rxBytes == 0 || entry.txBytes == 0) continue; - } else if (mWifiIfaces.contains(entry.iface)) { - u.noteNetworkActivityLocked(NETWORK_WIFI_RX_BYTES, entry.rxBytes); - u.noteNetworkActivityLocked(NETWORK_WIFI_TX_BYTES, entry.txBytes); + final Uid u = getUidStatsLocked(mapUid(entry.uid)); + u.noteNetworkActivityLocked(NETWORK_WIFI_RX_DATA, entry.rxBytes, + entry.rxPackets); + u.noteNetworkActivityLocked(NETWORK_WIFI_TX_DATA, entry.txBytes, + entry.txPackets); - mNetworkActivityCounters[NETWORK_WIFI_RX_BYTES].addCountLocked(entry.rxBytes); - mNetworkActivityCounters[NETWORK_WIFI_TX_BYTES].addCountLocked(entry.txBytes); + mNetworkByteActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked( + entry.rxBytes); + mNetworkByteActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked( + entry.txBytes); + mNetworkPacketActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked( + entry.rxPackets); + mNetworkPacketActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked( + entry.txPackets); + } } } } @@ -4982,7 +6068,7 @@ public final class BatteryStatsImpl extends BatteryStats { case STATS_SINCE_CHARGED: return mUptime + (curTime-mUptimeStart); case STATS_LAST: return mLastUptime; case STATS_CURRENT: return (curTime-mUptimeStart); - case STATS_SINCE_UNPLUGGED: return (curTime-mTrackBatteryUptimeStart); + case STATS_SINCE_UNPLUGGED: return (curTime- mOnBatteryTimeBase.getUptimeStart()); } return 0; } @@ -4993,69 +6079,43 @@ public final class BatteryStatsImpl extends BatteryStats { case STATS_SINCE_CHARGED: return mRealtime + (curTime-mRealtimeStart); case STATS_LAST: return mLastRealtime; case STATS_CURRENT: return (curTime-mRealtimeStart); - case STATS_SINCE_UNPLUGGED: return (curTime-mTrackBatteryRealtimeStart); + case STATS_SINCE_UNPLUGGED: return (curTime- mOnBatteryTimeBase.getRealtimeStart()); } return 0; } @Override public long computeBatteryUptime(long curTime, int which) { - switch (which) { - case STATS_SINCE_CHARGED: - return mBatteryUptime + getBatteryUptime(curTime); - case STATS_LAST: - return mBatteryLastUptime; - case STATS_CURRENT: - return getBatteryUptime(curTime); - case STATS_SINCE_UNPLUGGED: - return getBatteryUptimeLocked(curTime) - mUnpluggedBatteryUptime; - } - return 0; + return mOnBatteryTimeBase.computeUptime(curTime, which); } @Override public long computeBatteryRealtime(long curTime, int which) { - switch (which) { - case STATS_SINCE_CHARGED: - return mBatteryRealtime + getBatteryRealtimeLocked(curTime); - case STATS_LAST: - return mBatteryLastRealtime; - case STATS_CURRENT: - return getBatteryRealtimeLocked(curTime); - case STATS_SINCE_UNPLUGGED: - return getBatteryRealtimeLocked(curTime) - mUnpluggedBatteryRealtime; - } - return 0; + return mOnBatteryTimeBase.computeRealtime(curTime, which); } - long getBatteryUptimeLocked(long curTime) { - long time = mTrackBatteryPastUptime; - if (mOnBatteryInternal) { - time += curTime - mTrackBatteryUptimeStart; - } - return time; + @Override + public long computeBatteryScreenOffUptime(long curTime, int which) { + return mOnBatteryScreenOffTimeBase.computeUptime(curTime, which); + } + + @Override + public long computeBatteryScreenOffRealtime(long curTime, int which) { + return mOnBatteryScreenOffTimeBase.computeRealtime(curTime, which); } long getBatteryUptimeLocked() { - return getBatteryUptime(SystemClock.uptimeMillis() * 1000); + return mOnBatteryTimeBase.getUptime(SystemClock.uptimeMillis() * 1000); } @Override public long getBatteryUptime(long curTime) { - return getBatteryUptimeLocked(curTime); - } - - long getBatteryRealtimeLocked(long curTime) { - long time = mTrackBatteryPastRealtime; - if (mOnBatteryInternal) { - time += curTime - mTrackBatteryRealtimeStart; - } - return time; + return mOnBatteryTimeBase.getUptime(curTime); } @Override public long getBatteryRealtime(long curTime) { - return getBatteryRealtimeLocked(curTime); + return mOnBatteryTimeBase.getRealtime(curTime); } @Override @@ -5175,24 +6235,7 @@ public final class BatteryStatsImpl extends BatteryStats { * if needed. */ public Uid.Proc getProcessStatsLocked(int uid, String name) { - Uid u = getUidStatsLocked(uid); - return u.getProcessStatsLocked(name); - } - - /** - * Retrieve the statistics object for a particular process, given - * the name of the process. - * @param name process name - * @return the statistics object for the process - */ - public Uid.Proc getProcessStatsLocked(String name, int pid) { - int uid; - if (mUidCache.containsKey(name)) { - uid = mUidCache.get(name); - } else { - uid = Process.getUidForPid(pid); - mUidCache.put(name, uid); - } + uid = mapUid(uid); Uid u = getUidStatsLocked(uid); return u.getProcessStatsLocked(name); } @@ -5202,6 +6245,7 @@ public final class BatteryStatsImpl extends BatteryStats { * if needed. */ public Uid.Pkg getPackageStatsLocked(int uid, String pkg) { + uid = mapUid(uid); Uid u = getUidStatsLocked(uid); return u.getPackageStatsLocked(pkg); } @@ -5211,6 +6255,7 @@ public final class BatteryStatsImpl extends BatteryStats { * if needed. */ public Uid.Pkg.Serv getServiceStatsLocked(int uid, String pkg, String name) { + uid = mapUid(uid); Uid u = getUidStatsLocked(uid); return u.getServiceStatsLocked(pkg, name); } @@ -5251,7 +6296,7 @@ public final class BatteryStatsImpl extends BatteryStats { time = (time*uidRunningTime)/totalRunningTime; SamplingCounter uidSc = uidProc.mSpeedBins[sb]; if (uidSc == null) { - uidSc = new SamplingCounter(mUnpluggables); + uidSc = new SamplingCounter(mOnBatteryTimeBase); uidProc.mSpeedBins[sb] = uidSc; } uidSc.mCount.addAndGet((int)time); @@ -5388,15 +6433,20 @@ public final class BatteryStatsImpl extends BatteryStats { stream.close(); readSummaryFromParcel(in); - } catch(java.io.IOException e) { + } catch(Exception e) { Slog.e("BatteryStats", "Error reading battery statistics", e); } - long now = SystemClock.elapsedRealtime(); - if (USE_OLD_HISTORY) { - addHistoryRecordLocked(now, HistoryItem.CMD_START); + if (mHistoryBuffer.dataPosition() > 0) { + long now = SystemClock.elapsedRealtime(); + if (USE_OLD_HISTORY) { + addHistoryRecordLocked(now, HistoryItem.CMD_START); + } + addHistoryBufferLocked(now, HistoryItem.CMD_START); + mHistoryCur.currentTime = System.currentTimeMillis(); + addHistoryBufferLocked(now, HistoryItem.CMD_CURRENT_TIME); + mHistoryCur.currentTime = 0; } - addHistoryBufferLocked(now, HistoryItem.CMD_START); } public int describeContents() { @@ -5408,6 +6458,25 @@ public final class BatteryStatsImpl extends BatteryStats { mHistoryBuffer.setDataSize(0); mHistoryBuffer.setDataPosition(0); + mHistoryTagPool.clear(); + mNextHistoryTagIdx = 0; + mNumHistoryTagChars = 0; + + int numTags = in.readInt(); + for (int i=0; i<numTags; i++) { + int idx = in.readInt(); + String str = in.readString(); + int uid = in.readInt(); + HistoryTag tag = new HistoryTag(); + tag.string = str; + tag.uid = uid; + tag.poolIdx = idx; + mHistoryTagPool.put(tag, idx); + if (idx >= mNextHistoryTagIdx) { + mNextHistoryTagIdx = idx+1; + } + mNumHistoryTagChars += tag.string.length() + 1; + } int bufSize = in.readInt(); int curPos = in.dataPosition(); @@ -5476,6 +6545,13 @@ public final class BatteryStatsImpl extends BatteryStats { Slog.i(TAG, sb.toString()); } out.writeLong(mHistoryBaseTime + mLastHistoryTime); + out.writeInt(mHistoryTagPool.size()); + for (HashMap.Entry<HistoryTag, Integer> ent : mHistoryTagPool.entrySet()) { + HistoryTag tag = ent.getKey(); + out.writeInt(ent.getValue()); + out.writeString(tag.string); + out.writeInt(tag.uid); + } out.writeInt(mHistoryBuffer.dataSize()); if (DEBUG_HISTORY) Slog.i(TAG, "***************** WRITING HISTORY: " + mHistoryBuffer.dataSize() + " bytes at " + out.dataPosition()); @@ -5509,10 +6585,11 @@ public final class BatteryStatsImpl extends BatteryStats { readHistory(in, true); mStartCount = in.readInt(); - mBatteryUptime = in.readLong(); - mBatteryRealtime = in.readLong(); mUptime = in.readLong(); mRealtime = in.readLong(); + mStartClockTime = in.readLong(); + mOnBatteryTimeBase.readSummaryFromParcel(in); + mOnBatteryScreenOffTimeBase.readSummaryFromParcel(in); mDischargeUnplugLevel = in.readInt(); mDischargeCurrentLevel = in.readInt(); mLowDischargeAmountSinceCharge = in.readInt(); @@ -5538,14 +6615,26 @@ public final class BatteryStatsImpl extends BatteryStats { mPhoneDataConnectionsTimer[i].readSummaryFromParcelLocked(in); } for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) { - mNetworkActivityCounters[i].readSummaryFromParcelLocked(in); - } + mNetworkByteActivityCounters[i].readSummaryFromParcelLocked(in); + mNetworkPacketActivityCounters[i].readSummaryFromParcelLocked(in); + } + mMobileRadioActive = false; + mMobileRadioActiveTimer.readSummaryFromParcelLocked(in); + mMobileRadioActivePerAppTimer.readSummaryFromParcelLocked(in); + mMobileRadioActiveUnknownTime.readSummaryFromParcelLocked(in); + mMobileRadioActiveUnknownCount.readSummaryFromParcelLocked(in); mWifiOn = false; mWifiOnTimer.readSummaryFromParcelLocked(in); mGlobalWifiRunning = false; mGlobalWifiRunningTimer.readSummaryFromParcelLocked(in); + for (int i=0; i<NUM_WIFI_STATES; i++) { + mWifiStateTimer[i].readSummaryFromParcelLocked(in); + } mBluetoothOn = false; mBluetoothOnTimer.readSummaryFromParcelLocked(in); + for (int i=0; i< NUM_BLUETOOTH_STATES; i++) { + mBluetoothStateTimer[i].readSummaryFromParcelLocked(in); + } int NKW = in.readInt(); if (NKW > 10000) { @@ -5560,6 +6649,9 @@ public final class BatteryStatsImpl extends BatteryStats { } sNumSpeedSteps = in.readInt(); + if (sNumSpeedSteps < 0 || sNumSpeedSteps > 100) { + throw new BadParcelableException("Bad speed steps in data: " + sNumSpeedSteps); + } final int NU = in.readInt(); if (NU > 10000) { @@ -5619,12 +6711,15 @@ public final class BatteryStatsImpl extends BatteryStats { } if (in.readInt() != 0) { - if (u.mNetworkActivityCounters == null) { + if (u.mNetworkByteActivityCounters == null) { u.initNetworkActivityLocked(); } for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) { - u.mNetworkActivityCounters[i].readSummaryFromParcelLocked(in); + u.mNetworkByteActivityCounters[i].readSummaryFromParcelLocked(in); + u.mNetworkPacketActivityCounters[i].readSummaryFromParcelLocked(in); } + u.mMobileRadioActiveTime.readSummaryFromParcelLocked(in); + u.mMobileRadioActiveCount.readSummaryFromParcelLocked(in); } int NW = in.readInt(); @@ -5678,7 +6773,7 @@ public final class BatteryStatsImpl extends BatteryStats { p.mSpeedBins = new SamplingCounter[NSB]; for (int i=0; i<NSB; i++) { if (in.readInt() != 0) { - p.mSpeedBins[i] = new SamplingCounter(mUnpluggables); + p.mSpeedBins[i] = new SamplingCounter(mOnBatteryTimeBase); p.mSpeedBins[i].readSummaryFromParcelLocked(in); } } @@ -5719,50 +6814,58 @@ public final class BatteryStatsImpl extends BatteryStats { * @param out the Parcel to be written to. */ public void writeSummaryToParcel(Parcel out) { - // Need to update with current kernel wake lock counts. - updateKernelWakelocksLocked(); - updateNetworkActivityLocked(); + pullPendingStateUpdatesLocked(); final long NOW_SYS = SystemClock.uptimeMillis() * 1000; final long NOWREAL_SYS = SystemClock.elapsedRealtime() * 1000; - final long NOW = getBatteryUptimeLocked(NOW_SYS); - final long NOWREAL = getBatteryRealtimeLocked(NOWREAL_SYS); out.writeInt(VERSION); writeHistory(out, true); out.writeInt(mStartCount); - out.writeLong(computeBatteryUptime(NOW_SYS, STATS_SINCE_CHARGED)); - out.writeLong(computeBatteryRealtime(NOWREAL_SYS, STATS_SINCE_CHARGED)); out.writeLong(computeUptime(NOW_SYS, STATS_SINCE_CHARGED)); out.writeLong(computeRealtime(NOWREAL_SYS, STATS_SINCE_CHARGED)); + out.writeLong(mStartClockTime); + mOnBatteryTimeBase.writeSummaryToParcel(out, NOW_SYS, NOWREAL_SYS); + mOnBatteryScreenOffTimeBase.writeSummaryToParcel(out, NOW_SYS, NOWREAL_SYS); out.writeInt(mDischargeUnplugLevel); out.writeInt(mDischargeCurrentLevel); out.writeInt(getLowDischargeAmountSinceCharge()); out.writeInt(getHighDischargeAmountSinceCharge()); out.writeInt(getDischargeAmountScreenOnSinceCharge()); out.writeInt(getDischargeAmountScreenOffSinceCharge()); - - mScreenOnTimer.writeSummaryFromParcelLocked(out, NOWREAL); + + mScreenOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) { - mScreenBrightnessTimer[i].writeSummaryFromParcelLocked(out, NOWREAL); + mScreenBrightnessTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS); } mInputEventCounter.writeSummaryFromParcelLocked(out); - mPhoneOnTimer.writeSummaryFromParcelLocked(out, NOWREAL); + mPhoneOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) { - mPhoneSignalStrengthsTimer[i].writeSummaryFromParcelLocked(out, NOWREAL); + mPhoneSignalStrengthsTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS); } - mPhoneSignalScanningTimer.writeSummaryFromParcelLocked(out, NOWREAL); + mPhoneSignalScanningTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) { - mPhoneDataConnectionsTimer[i].writeSummaryFromParcelLocked(out, NOWREAL); + mPhoneDataConnectionsTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS); } for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) { - mNetworkActivityCounters[i].writeSummaryFromParcelLocked(out); + mNetworkByteActivityCounters[i].writeSummaryFromParcelLocked(out); + mNetworkPacketActivityCounters[i].writeSummaryFromParcelLocked(out); + } + mMobileRadioActiveTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); + mMobileRadioActivePerAppTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); + mMobileRadioActiveUnknownTime.writeSummaryFromParcelLocked(out); + mMobileRadioActiveUnknownCount.writeSummaryFromParcelLocked(out); + mWifiOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); + mGlobalWifiRunningTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); + for (int i=0; i<NUM_WIFI_STATES; i++) { + mWifiStateTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS); + } + mBluetoothOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); + for (int i=0; i< NUM_BLUETOOTH_STATES; i++) { + mBluetoothStateTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS); } - mWifiOnTimer.writeSummaryFromParcelLocked(out, NOWREAL); - mGlobalWifiRunningTimer.writeSummaryFromParcelLocked(out, NOWREAL); - mBluetoothOnTimer.writeSummaryFromParcelLocked(out, NOWREAL); out.writeInt(mKernelWakelockStats.size()); for (Map.Entry<String, SamplingTimer> ent : mKernelWakelockStats.entrySet()) { @@ -5770,7 +6873,7 @@ public final class BatteryStatsImpl extends BatteryStats { if (kwlt != null) { out.writeInt(1); out.writeString(ent.getKey()); - ent.getValue().writeSummaryFromParcelLocked(out, NOWREAL); + ent.getValue().writeSummaryFromParcelLocked(out, NOWREAL_SYS); } else { out.writeInt(0); } @@ -5785,57 +6888,57 @@ public final class BatteryStatsImpl extends BatteryStats { if (u.mWifiRunningTimer != null) { out.writeInt(1); - u.mWifiRunningTimer.writeSummaryFromParcelLocked(out, NOWREAL); + u.mWifiRunningTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); } else { out.writeInt(0); } if (u.mFullWifiLockTimer != null) { out.writeInt(1); - u.mFullWifiLockTimer.writeSummaryFromParcelLocked(out, NOWREAL); + u.mFullWifiLockTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); } else { out.writeInt(0); } if (u.mWifiScanTimer != null) { out.writeInt(1); - u.mWifiScanTimer.writeSummaryFromParcelLocked(out, NOWREAL); + u.mWifiScanTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); } else { out.writeInt(0); } for (int i = 0; i < Uid.NUM_WIFI_BATCHED_SCAN_BINS; i++) { if (u.mWifiBatchedScanTimer[i] != null) { out.writeInt(1); - u.mWifiBatchedScanTimer[i].writeSummaryFromParcelLocked(out, NOWREAL); + u.mWifiBatchedScanTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS); } else { out.writeInt(0); } } if (u.mWifiMulticastTimer != null) { out.writeInt(1); - u.mWifiMulticastTimer.writeSummaryFromParcelLocked(out, NOWREAL); + u.mWifiMulticastTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); } else { out.writeInt(0); } if (u.mAudioTurnedOnTimer != null) { out.writeInt(1); - u.mAudioTurnedOnTimer.writeSummaryFromParcelLocked(out, NOWREAL); + u.mAudioTurnedOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); } else { out.writeInt(0); } if (u.mVideoTurnedOnTimer != null) { out.writeInt(1); - u.mVideoTurnedOnTimer.writeSummaryFromParcelLocked(out, NOWREAL); + u.mVideoTurnedOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); } else { out.writeInt(0); } if (u.mForegroundActivityTimer != null) { out.writeInt(1); - u.mForegroundActivityTimer.writeSummaryFromParcelLocked(out, NOWREAL); + u.mForegroundActivityTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); } else { out.writeInt(0); } if (u.mVibratorOnTimer != null) { out.writeInt(1); - u.mVibratorOnTimer.writeSummaryFromParcelLocked(out, NOWREAL); + u.mVibratorOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); } else { out.writeInt(0); } @@ -5849,13 +6952,16 @@ public final class BatteryStatsImpl extends BatteryStats { } } - if (u.mNetworkActivityCounters == null) { + if (u.mNetworkByteActivityCounters == null) { out.writeInt(0); } else { out.writeInt(1); for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) { - u.mNetworkActivityCounters[i].writeSummaryFromParcelLocked(out); + u.mNetworkByteActivityCounters[i].writeSummaryFromParcelLocked(out); + u.mNetworkPacketActivityCounters[i].writeSummaryFromParcelLocked(out); } + u.mMobileRadioActiveTime.writeSummaryFromParcelLocked(out); + u.mMobileRadioActiveCount.writeSummaryFromParcelLocked(out); } int NW = u.mWakelockStats.size(); @@ -5867,19 +6973,19 @@ public final class BatteryStatsImpl extends BatteryStats { Uid.Wakelock wl = ent.getValue(); if (wl.mTimerFull != null) { out.writeInt(1); - wl.mTimerFull.writeSummaryFromParcelLocked(out, NOWREAL); + wl.mTimerFull.writeSummaryFromParcelLocked(out, NOWREAL_SYS); } else { out.writeInt(0); } if (wl.mTimerPartial != null) { out.writeInt(1); - wl.mTimerPartial.writeSummaryFromParcelLocked(out, NOWREAL); + wl.mTimerPartial.writeSummaryFromParcelLocked(out, NOWREAL_SYS); } else { out.writeInt(0); } if (wl.mTimerWindow != null) { out.writeInt(1); - wl.mTimerWindow.writeSummaryFromParcelLocked(out, NOWREAL); + wl.mTimerWindow.writeSummaryFromParcelLocked(out, NOWREAL_SYS); } else { out.writeInt(0); } @@ -5895,7 +7001,7 @@ public final class BatteryStatsImpl extends BatteryStats { Uid.Sensor se = ent.getValue(); if (se.mTimer != null) { out.writeInt(1); - se.mTimer.writeSummaryFromParcelLocked(out, NOWREAL); + se.mTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); } else { out.writeInt(0); } @@ -5942,7 +7048,8 @@ public final class BatteryStatsImpl extends BatteryStats { : ps.mServiceStats.entrySet()) { out.writeString(sent.getKey()); BatteryStatsImpl.Uid.Pkg.Serv ss = sent.getValue(); - long time = ss.getStartTimeToNowLocked(NOW); + long time = ss.getStartTimeToNowLocked( + mOnBatteryTimeBase.getUptime(NOW_SYS)); out.writeLong(time); out.writeInt(ss.mStarts); out.writeInt(ss.mLaunches); @@ -5966,51 +7073,60 @@ public final class BatteryStatsImpl extends BatteryStats { readHistory(in, false); mStartCount = in.readInt(); - mBatteryUptime = in.readLong(); - mBatteryLastUptime = 0; - mBatteryRealtime = in.readLong(); - mBatteryLastRealtime = 0; + mStartClockTime = in.readLong(); + mUptime = in.readLong(); + mUptimeStart = in.readLong(); + mLastUptime = 0; + mRealtime = in.readLong(); + mRealtimeStart = in.readLong(); + mLastRealtime = 0; + mOnBattery = in.readInt() != 0; + mOnBatteryInternal = false; // we are no longer really running. + mOnBatteryTimeBase.readFromParcel(in); + mOnBatteryScreenOffTimeBase.readFromParcel(in); + mScreenOn = false; - mScreenOnTimer = new StopwatchTimer(null, -1, null, mUnpluggables, in); + mScreenOnTimer = new StopwatchTimer(null, -1, null, mOnBatteryTimeBase, in); for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) { - mScreenBrightnessTimer[i] = new StopwatchTimer(null, -100-i, - null, mUnpluggables, in); + mScreenBrightnessTimer[i] = new StopwatchTimer(null, -100-i, null, mOnBatteryTimeBase, + in); } - mInputEventCounter = new Counter(mUnpluggables, in); + mInputEventCounter = new Counter(mOnBatteryTimeBase, in); mPhoneOn = false; - mPhoneOnTimer = new StopwatchTimer(null, -2, null, mUnpluggables, in); + mPhoneOnTimer = new StopwatchTimer(null, -2, null, mOnBatteryTimeBase, in); for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) { mPhoneSignalStrengthsTimer[i] = new StopwatchTimer(null, -200-i, - null, mUnpluggables, in); + null, mOnBatteryTimeBase, in); } - mPhoneSignalScanningTimer = new StopwatchTimer(null, -200+1, null, mUnpluggables, in); + mPhoneSignalScanningTimer = new StopwatchTimer(null, -200+1, null, mOnBatteryTimeBase, in); for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) { mPhoneDataConnectionsTimer[i] = new StopwatchTimer(null, -300-i, - null, mUnpluggables, in); + null, mOnBatteryTimeBase, in); } for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) { - mNetworkActivityCounters[i] = new LongSamplingCounter(mUnpluggables, in); - } + mNetworkByteActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase, in); + mNetworkPacketActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase, in); + } + mMobileRadioActive = false; + mMobileRadioActiveTimer = new StopwatchTimer(null, -400, null, mOnBatteryTimeBase, in); + mMobileRadioActivePerAppTimer = new StopwatchTimer(null, -401, null, mOnBatteryTimeBase, + in); + mMobileRadioActiveUnknownTime = new LongSamplingCounter(mOnBatteryTimeBase, in); + mMobileRadioActiveUnknownCount = new LongSamplingCounter(mOnBatteryTimeBase, in); mWifiOn = false; - mWifiOnTimer = new StopwatchTimer(null, -2, null, mUnpluggables, in); + mWifiOnTimer = new StopwatchTimer(null, -2, null, mOnBatteryTimeBase, in); mGlobalWifiRunning = false; - mGlobalWifiRunningTimer = new StopwatchTimer(null, -2, null, mUnpluggables, in); + mGlobalWifiRunningTimer = new StopwatchTimer(null, -2, null, mOnBatteryTimeBase, in); + for (int i=0; i<NUM_WIFI_STATES; i++) { + mWifiStateTimer[i] = new StopwatchTimer(null, -600-i, + null, mOnBatteryTimeBase, in); + } mBluetoothOn = false; - mBluetoothOnTimer = new StopwatchTimer(null, -2, null, mUnpluggables, in); - mUptime = in.readLong(); - mUptimeStart = in.readLong(); - mLastUptime = 0; - mRealtime = in.readLong(); - mRealtimeStart = in.readLong(); - mLastRealtime = 0; - mOnBattery = in.readInt() != 0; - mOnBatteryInternal = false; // we are no longer really running. - mTrackBatteryPastUptime = in.readLong(); - mTrackBatteryUptimeStart = in.readLong(); - mTrackBatteryPastRealtime = in.readLong(); - mTrackBatteryRealtimeStart = in.readLong(); - mUnpluggedBatteryUptime = in.readLong(); - mUnpluggedBatteryRealtime = in.readLong(); + mBluetoothOnTimer = new StopwatchTimer(null, -2, null, mOnBatteryTimeBase, in); + for (int i=0; i< NUM_BLUETOOTH_STATES; i++) { + mBluetoothStateTimer[i] = new StopwatchTimer(null, -500-i, + null, mOnBatteryTimeBase, in); + } mDischargeUnplugLevel = in.readInt(); mDischargeCurrentLevel = in.readInt(); mLowDischargeAmountSinceCharge = in.readInt(); @@ -6021,9 +7137,6 @@ public final class BatteryStatsImpl extends BatteryStats { mDischargeAmountScreenOffSinceCharge = in.readInt(); mLastWriteTime = in.readLong(); - mRadioDataUptime = in.readLong(); - mRadioDataStart = -1; - mBluetoothPingCount = in.readInt(); mBluetoothPingStart = -1; @@ -6033,7 +7146,7 @@ public final class BatteryStatsImpl extends BatteryStats { if (in.readInt() != 0) { String wakelockName = in.readString(); in.readInt(); // Extra 0/1 written by Timer.writeTimerToParcel - SamplingTimer kwlt = new SamplingTimer(mUnpluggables, mOnBattery, in); + SamplingTimer kwlt = new SamplingTimer(mOnBatteryTimeBase, in); mKernelWakelockStats.put(wakelockName, kwlt); } } @@ -6054,7 +7167,7 @@ public final class BatteryStatsImpl extends BatteryStats { for (int i = 0; i < numUids; i++) { int uid = in.readInt(); Uid u = new Uid(uid); - u.readFromParcelLocked(mUnpluggables, in); + u.readFromParcelLocked(mOnBatteryTimeBase, mOnBatteryScreenOffTimeBase, in); mUidStats.append(uid, u); } } @@ -6070,51 +7183,57 @@ public final class BatteryStatsImpl extends BatteryStats { @SuppressWarnings("unused") void writeToParcelLocked(Parcel out, boolean inclUids, int flags) { // Need to update with current kernel wake lock counts. - updateKernelWakelocksLocked(); - updateNetworkActivityLocked(); + pullPendingStateUpdatesLocked(); final long uSecUptime = SystemClock.uptimeMillis() * 1000; final long uSecRealtime = SystemClock.elapsedRealtime() * 1000; - final long batteryUptime = getBatteryUptimeLocked(uSecUptime); - final long batteryRealtime = getBatteryRealtimeLocked(uSecRealtime); + final long batteryRealtime = mOnBatteryTimeBase.getRealtime(uSecRealtime); + final long batteryScreenOffRealtime = mOnBatteryScreenOffTimeBase.getRealtime(uSecRealtime); out.writeInt(MAGIC); writeHistory(out, false); out.writeInt(mStartCount); - out.writeLong(mBatteryUptime); - out.writeLong(mBatteryRealtime); - mScreenOnTimer.writeToParcel(out, batteryRealtime); + out.writeLong(mStartClockTime); + out.writeLong(mUptime); + out.writeLong(mUptimeStart); + out.writeLong(mRealtime); + out.writeLong(mRealtimeStart); + out.writeInt(mOnBattery ? 1 : 0); + mOnBatteryTimeBase.writeToParcel(out, uSecUptime, uSecRealtime); + mOnBatteryScreenOffTimeBase.writeToParcel(out, uSecUptime, uSecRealtime); + + mScreenOnTimer.writeToParcel(out, uSecRealtime); for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) { - mScreenBrightnessTimer[i].writeToParcel(out, batteryRealtime); + mScreenBrightnessTimer[i].writeToParcel(out, uSecRealtime); } mInputEventCounter.writeToParcel(out); - mPhoneOnTimer.writeToParcel(out, batteryRealtime); + mPhoneOnTimer.writeToParcel(out, uSecRealtime); for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) { - mPhoneSignalStrengthsTimer[i].writeToParcel(out, batteryRealtime); + mPhoneSignalStrengthsTimer[i].writeToParcel(out, uSecRealtime); } - mPhoneSignalScanningTimer.writeToParcel(out, batteryRealtime); + mPhoneSignalScanningTimer.writeToParcel(out, uSecRealtime); for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) { - mPhoneDataConnectionsTimer[i].writeToParcel(out, batteryRealtime); + mPhoneDataConnectionsTimer[i].writeToParcel(out, uSecRealtime); } for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) { - mNetworkActivityCounters[i].writeToParcel(out); + mNetworkByteActivityCounters[i].writeToParcel(out); + mNetworkPacketActivityCounters[i].writeToParcel(out); + } + mMobileRadioActiveTimer.writeToParcel(out, uSecRealtime); + mMobileRadioActivePerAppTimer.writeToParcel(out, uSecRealtime); + mMobileRadioActiveUnknownTime.writeToParcel(out); + mMobileRadioActiveUnknownCount.writeToParcel(out); + mWifiOnTimer.writeToParcel(out, uSecRealtime); + mGlobalWifiRunningTimer.writeToParcel(out, uSecRealtime); + for (int i=0; i<NUM_WIFI_STATES; i++) { + mWifiStateTimer[i].writeToParcel(out, uSecRealtime); + } + mBluetoothOnTimer.writeToParcel(out, uSecRealtime); + for (int i=0; i< NUM_BLUETOOTH_STATES; i++) { + mBluetoothStateTimer[i].writeToParcel(out, uSecRealtime); } - mWifiOnTimer.writeToParcel(out, batteryRealtime); - mGlobalWifiRunningTimer.writeToParcel(out, batteryRealtime); - mBluetoothOnTimer.writeToParcel(out, batteryRealtime); - out.writeLong(mUptime); - out.writeLong(mUptimeStart); - out.writeLong(mRealtime); - out.writeLong(mRealtimeStart); - out.writeInt(mOnBattery ? 1 : 0); - out.writeLong(batteryUptime); - out.writeLong(mTrackBatteryUptimeStart); - out.writeLong(batteryRealtime); - out.writeLong(mTrackBatteryRealtimeStart); - out.writeLong(mUnpluggedBatteryUptime); - out.writeLong(mUnpluggedBatteryRealtime); out.writeInt(mDischargeUnplugLevel); out.writeInt(mDischargeCurrentLevel); out.writeInt(mLowDischargeAmountSinceCharge); @@ -6125,9 +7244,6 @@ public final class BatteryStatsImpl extends BatteryStats { out.writeInt(mDischargeAmountScreenOffSinceCharge); out.writeLong(mLastWriteTime); - // Write radio uptime for data - out.writeLong(getRadioDataUptime()); - out.writeInt(getBluetoothPingCount()); if (inclUids) { @@ -6137,7 +7253,7 @@ public final class BatteryStatsImpl extends BatteryStats { if (kwlt != null) { out.writeInt(1); out.writeString(ent.getKey()); - Timer.writeTimerToParcel(out, kwlt, batteryRealtime); + Timer.writeTimerToParcel(out, kwlt, uSecRealtime); } else { out.writeInt(0); } @@ -6155,7 +7271,7 @@ public final class BatteryStatsImpl extends BatteryStats { out.writeInt(mUidStats.keyAt(i)); Uid uid = mUidStats.valueAt(i); - uid.writeToParcelLocked(out, batteryRealtime); + uid.writeToParcelLocked(out, uSecRealtime); } } else { out.writeInt(0); @@ -6175,12 +7291,15 @@ public final class BatteryStatsImpl extends BatteryStats { public void prepareForDumpLocked() { // Need to retrieve current kernel wake lock stats before printing. - updateKernelWakelocksLocked(); - updateNetworkActivityLocked(); + pullPendingStateUpdatesLocked(); } - public void dumpLocked(PrintWriter pw, boolean isUnpluggedOnly, int reqUid) { + public void dumpLocked(Context context, PrintWriter pw, int flags, int reqUid, long histStart) { if (DEBUG) { + pw.println("mOnBatteryTimeBase:"); + mOnBatteryTimeBase.dump(pw, " "); + pw.println("mOnBatteryScreenOffTimeBase:"); + mOnBatteryScreenOffTimeBase.dump(pw, " "); Printer pr = new PrintWriterPrinter(pw); pr.println("*** Screen timer:"); mScreenOnTimer.logState(pr, " "); @@ -6202,13 +7321,23 @@ public final class BatteryStatsImpl extends BatteryStats { pr.println("*** Data connection type #" + i + ":"); mPhoneDataConnectionsTimer[i].logState(pr, " "); } + pr.println("*** Mobile network active timer:"); + mMobileRadioActiveTimer.logState(pr, " "); pr.println("*** Wifi timer:"); mWifiOnTimer.logState(pr, " "); pr.println("*** WifiRunning timer:"); mGlobalWifiRunningTimer.logState(pr, " "); + for (int i=0; i<NUM_WIFI_STATES; i++) { + pr.println("*** Wifi state #" + i + ":"); + mWifiStateTimer[i].logState(pr, " "); + } pr.println("*** Bluetooth timer:"); mBluetoothOnTimer.logState(pr, " "); + for (int i=0; i< NUM_BLUETOOTH_STATES; i++) { + pr.println("*** Bluetooth active type #" + i + ":"); + mBluetoothStateTimer[i].logState(pr, " "); + } } - super.dumpLocked(pw, isUnpluggedOnly, reqUid); + super.dumpLocked(context, pw, flags, reqUid, histStart); } } diff --git a/core/java/com/android/internal/os/WrapperInit.java b/core/java/com/android/internal/os/WrapperInit.java index c6b3e7c..1766f7b 100644 --- a/core/java/com/android/internal/os/WrapperInit.java +++ b/core/java/com/android/internal/os/WrapperInit.java @@ -25,7 +25,6 @@ import java.io.FileOutputStream; import java.io.IOException; import libcore.io.IoUtils; -import libcore.io.Libcore; import dalvik.system.Zygote; diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index bf62745..05c57e8 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -244,9 +244,11 @@ public class ZygoteInit { } static void preload() { + Log.d(TAG, "begin preload"); preloadClasses(); preloadResources(); preloadOpenGL(); + Log.d(TAG, "end preload"); } private static void preloadOpenGL() { diff --git a/core/java/com/android/internal/preference/YesNoPreference.java b/core/java/com/android/internal/preference/YesNoPreference.java index cf68a58..7abf416 100644 --- a/core/java/com/android/internal/preference/YesNoPreference.java +++ b/core/java/com/android/internal/preference/YesNoPreference.java @@ -31,15 +31,19 @@ import android.util.AttributeSet; */ public class YesNoPreference extends DialogPreference { private boolean mWasPositiveResult; - - public YesNoPreference(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + + public YesNoPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public YesNoPreference(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); } public YesNoPreference(Context context, AttributeSet attrs) { this(context, attrs, com.android.internal.R.attr.yesNoPreferenceStyle); } - + public YesNoPreference(Context context) { this(context, null); } diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index 97ea7d8..4734712 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -41,11 +41,11 @@ interface IStatusBarService out List<IBinder> notificationKeys, out List<StatusBarNotification> notifications, out int[] switches, out List<IBinder> binders); void onPanelRevealed(); - void onNotificationClick(String pkg, String tag, int id); + void onNotificationClick(String pkg, String tag, int id, int userId); void onNotificationError(String pkg, String tag, int id, - int uid, int initialPid, String message); - void onClearAllNotifications(); - void onNotificationClear(String pkg, String tag, int id); + int uid, int initialPid, String message, int userId); + void onClearAllNotifications(int userId); + void onNotificationClear(String pkg, String tag, int id, int userId); void setSystemUiVisibility(int vis, int mask); void setHardKeyboardEnabled(boolean enabled); void toggleRecentApps(); diff --git a/core/java/com/android/internal/view/ActionBarPolicy.java b/core/java/com/android/internal/view/ActionBarPolicy.java index 25086c5..bee59dc 100644 --- a/core/java/com/android/internal/view/ActionBarPolicy.java +++ b/core/java/com/android/internal/view/ActionBarPolicy.java @@ -19,11 +19,9 @@ package com.android.internal.view; import com.android.internal.R; import android.content.Context; -import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.os.Build; -import android.view.ViewConfiguration; /** * Allows components to query for various configuration policy decisions diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 12ced68..45ca7fc 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -32,7 +32,9 @@ import com.android.internal.view.IInputMethodClient; * this file. */ interface IInputMethodManager { + // TODO: Use ParceledListSlice instead List<InputMethodInfo> getInputMethodList(); + // TODO: Use ParceledListSlice instead List<InputMethodInfo> getEnabledInputMethodList(); List<InputMethodSubtype> getEnabledInputMethodSubtypeList(in String imiId, boolean allowsImplicitlySelectedSubtypes); @@ -73,5 +75,7 @@ interface IInputMethodManager { boolean switchToNextInputMethod(in IBinder token, boolean onlyCurrentIme); boolean shouldOfferSwitchingToNextInputMethod(in IBinder token); boolean setInputMethodEnabled(String id, boolean enabled); - oneway void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes); + void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes); + int getInputMethodWindowVisibleHeight(); + oneway void notifyTextCommitted(); } diff --git a/core/java/com/android/internal/view/RotationPolicy.java b/core/java/com/android/internal/view/RotationPolicy.java index 6295314..b479cb1 100644 --- a/core/java/com/android/internal/view/RotationPolicy.java +++ b/core/java/com/android/internal/view/RotationPolicy.java @@ -18,7 +18,9 @@ package com.android.internal.view; import android.content.Context; import android.content.pm.PackageManager; +import android.content.res.Configuration; import android.database.ContentObserver; +import android.graphics.Point; import android.net.Uri; import android.os.AsyncTask; import android.os.Handler; @@ -26,15 +28,20 @@ import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; import android.util.Log; +import android.view.Display; import android.view.IWindowManager; import android.view.Surface; import android.view.WindowManagerGlobal; +import com.android.internal.R; + /** * Provides helper functions for configuring the display rotation policy. */ public final class RotationPolicy { private static final String TAG = "RotationPolicy"; + private static final int CURRENT_ROTATION = -1; + private static final int NATURAL_ROTATION = Surface.ROTATION_0; private RotationPolicy() { } @@ -57,23 +64,33 @@ public final class RotationPolicy { } /** - * Returns true if the device supports the rotation-lock toggle feature - * in the system UI or system bar. + * Returns the orientation that will be used when locking the orientation from system UI + * with {@link #setRotationLock}. * - * When the rotation-lock toggle is supported, the "auto-rotate screen" option in - * Display settings should be hidden, but it should remain available in Accessibility - * settings. + * If the device only supports locking to its natural orientation, this will be either + * Configuration.ORIENTATION_PORTRAIT or Configuration.ORIENTATION_LANDSCAPE, + * otherwise Configuration.ORIENTATION_UNDEFINED if any orientation is lockable. */ - public static boolean isRotationLockToggleSupported(Context context) { - return isRotationSupported(context) - && context.getResources().getConfiguration().smallestScreenWidthDp >= 600; + public static int getRotationLockOrientation(Context context) { + if (!areAllRotationsAllowed(context)) { + final Point size = new Point(); + final IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); + try { + wm.getInitialDisplaySize(Display.DEFAULT_DISPLAY, size); + return size.x < size.y ? + Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE; + } catch (RemoteException e) { + Log.w(TAG, "Unable to get the display size"); + } + } + return Configuration.ORIENTATION_UNDEFINED; } /** - * Returns true if the rotation-lock toggle should be shown in the UI. + * Returns true if the rotation-lock toggle should be shown in system UI. */ public static boolean isRotationLockToggleVisible(Context context) { - return isRotationLockToggleSupported(context) && + return isRotationSupported(context) && Settings.System.getIntForUser(context.getContentResolver(), Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, 0, UserHandle.USER_CURRENT) == 0; @@ -88,50 +105,42 @@ public final class RotationPolicy { } /** - * Enables or disables rotation lock. - * - * Should be used by the rotation lock toggle. + * Enables or disables rotation lock from the system UI toggle. */ public static void setRotationLock(Context context, final boolean enabled) { Settings.System.putIntForUser(context.getContentResolver(), Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, 0, UserHandle.USER_CURRENT); - AsyncTask.execute(new Runnable() { - @Override - public void run() { - try { - IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); - if (enabled) { - wm.freezeRotation(-1); - } else { - wm.thawRotation(); - } - } catch (RemoteException exc) { - Log.w(TAG, "Unable to save auto-rotate setting"); - } - } - }); + final int rotation = areAllRotationsAllowed(context) ? CURRENT_ROTATION : NATURAL_ROTATION; + setRotationLock(enabled, rotation); } /** - * Enables or disables rotation lock and adjusts whether the rotation lock toggle - * should be hidden for accessibility purposes. + * Enables or disables natural rotation lock from Accessibility settings. * - * Should be used by Display settings and Accessibility settings. + * If rotation is locked for accessibility, the system UI toggle is hidden to avoid confusion. */ public static void setRotationLockForAccessibility(Context context, final boolean enabled) { Settings.System.putIntForUser(context.getContentResolver(), Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, enabled ? 1 : 0, UserHandle.USER_CURRENT); + setRotationLock(enabled, NATURAL_ROTATION); + } + + private static boolean areAllRotationsAllowed(Context context) { + return context.getResources().getBoolean(R.bool.config_allowAllRotations); + } + + private static void setRotationLock(final boolean enabled, final int rotation) { AsyncTask.execute(new Runnable() { @Override public void run() { try { IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); if (enabled) { - wm.freezeRotation(Surface.ROTATION_0); + wm.freezeRotation(rotation); } else { wm.thawRotation(); } diff --git a/core/java/com/android/internal/view/menu/ActionMenuItem.java b/core/java/com/android/internal/view/menu/ActionMenuItem.java index 7ca6c1b..ed676bb 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuItem.java +++ b/core/java/com/android/internal/view/menu/ActionMenuItem.java @@ -163,7 +163,7 @@ public class ActionMenuItem implements MenuItem { public MenuItem setIcon(int iconRes) { mIconResId = iconRes; - mIconDrawable = mContext.getResources().getDrawable(iconRes); + mIconDrawable = mContext.getDrawable(iconRes); return this; } diff --git a/core/java/com/android/internal/view/menu/ActionMenuItemView.java b/core/java/com/android/internal/view/menu/ActionMenuItemView.java index 238a9c0..3cceebe 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuItemView.java +++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java @@ -28,8 +28,11 @@ import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.accessibility.AccessibilityEvent; +import android.widget.ActionMenuView; +import android.widget.ListPopupWindow; import android.widget.TextView; import android.widget.Toast; +import android.widget.ListPopupWindow.ForwardingListener; /** * @hide @@ -43,6 +46,8 @@ public class ActionMenuItemView extends TextView private CharSequence mTitle; private Drawable mIcon; private MenuBuilder.ItemInvoker mItemInvoker; + private ForwardingListener mForwardingListener; + private PopupCallback mPopupCallback; private boolean mAllowTextWithIcon; private boolean mExpandedFormat; @@ -60,13 +65,17 @@ public class ActionMenuItemView extends TextView this(context, attrs, 0); } - public ActionMenuItemView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public ActionMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public ActionMenuItemView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); final Resources res = context.getResources(); mAllowTextWithIcon = res.getBoolean( com.android.internal.R.bool.config_allowActionMenuItemTextWithIcon); - TypedArray a = context.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.ActionMenuItemView, 0, 0); + final TypedArray a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.ActionMenuItemView, defStyleAttr, defStyleRes); mMinWidth = a.getDimensionPixelSize( com.android.internal.R.styleable.ActionMenuItemView_minWidth, 0); a.recycle(); @@ -99,6 +108,7 @@ public class ActionMenuItemView extends TextView return mItemData; } + @Override public void initialize(MenuItemImpl itemData, int menuType) { mItemData = itemData; @@ -108,8 +118,24 @@ public class ActionMenuItemView extends TextView setVisibility(itemData.isVisible() ? View.VISIBLE : View.GONE); setEnabled(itemData.isEnabled()); + + if (itemData.hasSubMenu()) { + if (mForwardingListener == null) { + mForwardingListener = new ActionMenuItemForwardingListener(); + } + } } + @Override + public boolean onTouchEvent(MotionEvent e) { + if (mItemData.hasSubMenu() && mForwardingListener != null + && mForwardingListener.onTouch(this, e)) { + return true; + } + return super.onTouchEvent(e); + } + + @Override public void onClick(View v) { if (mItemInvoker != null) { mItemInvoker.invokeItem(mItemData); @@ -120,6 +146,10 @@ public class ActionMenuItemView extends TextView mItemInvoker = invoker; } + public void setPopupCallback(PopupCallback popupCallback) { + mPopupCallback = popupCallback; + } + public boolean prefersCondensedTitle() { return true; } @@ -285,4 +315,42 @@ public class ActionMenuItemView extends TextView super.setPadding((w - dw) / 2, getPaddingTop(), getPaddingRight(), getPaddingBottom()); } } + + private class ActionMenuItemForwardingListener extends ForwardingListener { + public ActionMenuItemForwardingListener() { + super(ActionMenuItemView.this); + } + + @Override + public ListPopupWindow getPopup() { + if (mPopupCallback != null) { + return mPopupCallback.getPopup(); + } + return null; + } + + @Override + protected boolean onForwardingStarted() { + // Call the invoker, then check if the expected popup is showing. + if (mItemInvoker != null && mItemInvoker.invokeItem(mItemData)) { + final ListPopupWindow popup = getPopup(); + return popup != null && popup.isShowing(); + } + return false; + } + + @Override + protected boolean onForwardingStopped() { + final ListPopupWindow popup = getPopup(); + if (popup != null) { + popup.dismiss(); + return true; + } + return false; + } + } + + public static abstract class PopupCallback { + public abstract ListPopupWindow getPopup(); + } } diff --git a/core/java/com/android/internal/view/menu/IconMenuItemView.java b/core/java/com/android/internal/view/menu/IconMenuItemView.java index 5d0b25f..de5e279 100644 --- a/core/java/com/android/internal/view/menu/IconMenuItemView.java +++ b/core/java/com/android/internal/view/menu/IconMenuItemView.java @@ -57,8 +57,8 @@ public final class IconMenuItemView extends TextView implements MenuView.ItemVie private static String sPrependShortcutLabel; - public IconMenuItemView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs); + public IconMenuItemView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); if (sPrependShortcutLabel == null) { /* @@ -68,10 +68,9 @@ public final class IconMenuItemView extends TextView implements MenuView.ItemVie sPrependShortcutLabel = getResources().getString( com.android.internal.R.string.prepend_shortcut_label); } - - TypedArray a = - context.obtainStyledAttributes( - attrs, com.android.internal.R.styleable.MenuView, defStyle, 0); + + final TypedArray a = context.obtainStyledAttributes( + attrs, com.android.internal.R.styleable.MenuView, defStyleAttr, defStyleRes); mDisabledAlpha = a.getFloat( com.android.internal.R.styleable.MenuView_itemIconDisabledAlpha, 0.8f); @@ -81,7 +80,11 @@ public final class IconMenuItemView extends TextView implements MenuView.ItemVie a.recycle(); } - + + public IconMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + public IconMenuItemView(Context context, AttributeSet attrs) { this(context, attrs, 0); } diff --git a/core/java/com/android/internal/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java index a2a4acc..692bdac 100644 --- a/core/java/com/android/internal/view/menu/ListMenuItemView.java +++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java @@ -55,13 +55,13 @@ public class ListMenuItemView extends LinearLayout implements MenuView.ItemView private boolean mForceShowIcon; - public ListMenuItemView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs); - - TypedArray a = - context.obtainStyledAttributes( - attrs, com.android.internal.R.styleable.MenuView, defStyle, 0); - + public ListMenuItemView( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + final TypedArray a = context.obtainStyledAttributes( + attrs, com.android.internal.R.styleable.MenuView, defStyleAttr, defStyleRes); + mBackground = a.getDrawable(com.android.internal.R.styleable.MenuView_itemBackground); mTextAppearance = a.getResourceId(com.android.internal.R.styleable. MenuView_itemTextAppearance, -1); @@ -72,6 +72,10 @@ public class ListMenuItemView extends LinearLayout implements MenuView.ItemView a.recycle(); } + public ListMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + public ListMenuItemView(Context context, AttributeSet attrs) { this(context, attrs, 0); } diff --git a/core/java/com/android/internal/view/menu/ListMenuPresenter.java b/core/java/com/android/internal/view/menu/ListMenuPresenter.java index e1bb3621..c476354 100644 --- a/core/java/com/android/internal/view/menu/ListMenuPresenter.java +++ b/core/java/com/android/internal/view/menu/ListMenuPresenter.java @@ -17,7 +17,6 @@ package com.android.internal.view.menu; import android.content.Context; -import android.database.DataSetObserver; import android.os.Bundle; import android.os.Parcelable; import android.util.SparseArray; diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java index 195a00d..b776226 100644 --- a/core/java/com/android/internal/view/menu/MenuBuilder.java +++ b/core/java/com/android/internal/view/menu/MenuBuilder.java @@ -919,7 +919,7 @@ public class MenuBuilder implements Menu { * sub menu is about to be shown, <var>allMenusAreClosing</var> * is false. */ - final void close(boolean allMenusAreClosing) { + public final void close(boolean allMenusAreClosing) { if (mIsClosing) return; mIsClosing = true; @@ -946,7 +946,7 @@ public class MenuBuilder implements Menu { * false if only item properties changed. * (Visibility is a structural property since it affects layout.) */ - void onItemsChanged(boolean structureChanged) { + public void onItemsChanged(boolean structureChanged) { if (!mPreventDispatchingItemsChanged) { if (structureChanged) { mIsVisibleItemsStale = true; @@ -1000,7 +1000,7 @@ public class MenuBuilder implements Menu { onItemsChanged(true); } - ArrayList<MenuItemImpl> getVisibleItems() { + public ArrayList<MenuItemImpl> getVisibleItems() { if (!mIsVisibleItemsStale) return mVisibleItems; // Refresh the visible items @@ -1085,12 +1085,12 @@ public class MenuBuilder implements Menu { mIsActionItemsStale = false; } - ArrayList<MenuItemImpl> getActionItems() { + public ArrayList<MenuItemImpl> getActionItems() { flagActionItems(); return mActionItems; } - ArrayList<MenuItemImpl> getNonActionItems() { + public ArrayList<MenuItemImpl> getNonActionItems() { flagActionItems(); return mNonActionItems; } @@ -1121,7 +1121,7 @@ public class MenuBuilder implements Menu { } if (iconRes > 0) { - mHeaderIcon = r.getDrawable(iconRes); + mHeaderIcon = getContext().getDrawable(iconRes); } else if (icon != null) { mHeaderIcon = icon; } diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java index 4d0a326..61dcaca 100644 --- a/core/java/com/android/internal/view/menu/MenuItemImpl.java +++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java @@ -385,7 +385,7 @@ public final class MenuItemImpl implements MenuItem { } if (mIconResId != NO_ICON) { - Drawable icon = mMenu.getResources().getDrawable(mIconResId); + Drawable icon = mMenu.getContext().getDrawable(mIconResId); mIconResId = NO_ICON; mIconDrawable = icon; return icon; diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java index 05e9a66..d664058 100644 --- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java +++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java @@ -23,7 +23,6 @@ import android.view.Gravity; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MenuItem; -import android.view.MotionEvent; import android.view.View; import android.view.View.MeasureSpec; import android.view.ViewGroup; diff --git a/core/java/com/android/internal/widget/AbsActionBarView.java b/core/java/com/android/internal/widget/AbsActionBarView.java index f3891c7..183478f 100644 --- a/core/java/com/android/internal/widget/AbsActionBarView.java +++ b/core/java/com/android/internal/widget/AbsActionBarView.java @@ -16,8 +16,8 @@ package com.android.internal.widget; import com.android.internal.R; -import com.android.internal.view.menu.ActionMenuPresenter; -import com.android.internal.view.menu.ActionMenuView; +import android.widget.ActionMenuPresenter; +import android.widget.ActionMenuView; import android.animation.Animator; import android.animation.AnimatorSet; @@ -47,15 +47,20 @@ public abstract class AbsActionBarView extends ViewGroup { private static final int FADE_DURATION = 200; public AbsActionBarView(Context context) { - super(context); + this(context, null); } public AbsActionBarView(Context context, AttributeSet attrs) { - super(context, attrs); + this(context, attrs, 0); } - public AbsActionBarView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public AbsActionBarView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public AbsActionBarView( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); } @Override @@ -95,9 +100,6 @@ public abstract class AbsActionBarView extends ViewGroup { public void setContentHeight(int height) { mContentHeight = height; - if (mMenuView != null) { - mMenuView.setMaxItemHeight(mContentHeight); - } requestLayout(); } diff --git a/core/java/com/android/internal/widget/ActionBarContainer.java b/core/java/com/android/internal/widget/ActionBarContainer.java index 8a49899..c2d22dd 100644 --- a/core/java/com/android/internal/widget/ActionBarContainer.java +++ b/core/java/com/android/internal/widget/ActionBarContainer.java @@ -16,10 +16,10 @@ package com.android.internal.widget; -import android.app.ActionBar; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; +import android.graphics.ColorFilter; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.ActionMode; @@ -51,7 +51,8 @@ public class ActionBarContainer extends FrameLayout { public ActionBarContainer(Context context, AttributeSet attrs) { super(context, attrs); - setBackgroundDrawable(null); + // Set a transparent background so that we project appropriately. + setBackground(new ActionBarBackgroundDrawable()); TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ActionBar); @@ -243,24 +244,6 @@ public class ActionBarContainer extends FrameLayout { } @Override - public void onDraw(Canvas canvas) { - if (getWidth() == 0 || getHeight() == 0) { - return; - } - - if (mIsSplit) { - if (mSplitBackground != null) mSplitBackground.draw(canvas); - } else { - if (mBackground != null) { - mBackground.draw(canvas); - } - if (mStackedBackground != null && mIsStacked) { - mStackedBackground.draw(canvas); - } - } - } - - @Override public ActionMode startActionModeForChild(View child, ActionMode.Callback callback) { // No starting an action mode for an action bar child! (Where would it go?) return null; @@ -291,12 +274,13 @@ public class ActionBarContainer extends FrameLayout { public void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); - final boolean hasTabs = mTabContainer != null && mTabContainer.getVisibility() != GONE; + final View tabContainer = mTabContainer; + final boolean hasTabs = tabContainer != null && tabContainer.getVisibility() != GONE; - if (mTabContainer != null && mTabContainer.getVisibility() != GONE) { + if (tabContainer != null && tabContainer.getVisibility() != GONE) { final int containerHeight = getMeasuredHeight(); - final int tabHeight = mTabContainer.getMeasuredHeight(); - mTabContainer.layout(l, containerHeight - tabHeight, r, containerHeight); + final int tabHeight = tabContainer.getMeasuredHeight(); + tabContainer.layout(l, containerHeight - tabHeight, r, containerHeight); } boolean needsInvalidate = false; @@ -307,13 +291,15 @@ public class ActionBarContainer extends FrameLayout { } } else { if (mBackground != null) { - mBackground.setBounds(mActionBarView.getLeft(), mActionBarView.getTop(), - mActionBarView.getRight(), mActionBarView.getBottom()); + final ActionBarView actionBarView = mActionBarView; + mBackground.setBounds(actionBarView.getLeft(), actionBarView.getTop(), + actionBarView.getRight(), actionBarView.getBottom()); needsInvalidate = true; } - if ((mIsStacked = hasTabs && mStackedBackground != null)) { - mStackedBackground.setBounds(mTabContainer.getLeft(), mTabContainer.getTop(), - mTabContainer.getRight(), mTabContainer.getBottom()); + mIsStacked = hasTabs; + if (hasTabs && mStackedBackground != null) { + mStackedBackground.setBounds(tabContainer.getLeft(), tabContainer.getTop(), + tabContainer.getRight(), tabContainer.getBottom()); needsInvalidate = true; } } @@ -322,4 +308,37 @@ public class ActionBarContainer extends FrameLayout { invalidate(); } } + + /** + * Dummy drawable so that we don't break background display lists and + * projection surfaces. + */ + private class ActionBarBackgroundDrawable extends Drawable { + @Override + public void draw(Canvas canvas) { + if (mIsSplit) { + if (mSplitBackground != null) mSplitBackground.draw(canvas); + } else { + if (mBackground != null) { + mBackground.draw(canvas); + } + if (mStackedBackground != null && mIsStacked) { + mStackedBackground.draw(canvas); + } + } + } + + @Override + public void setAlpha(int alpha) { + } + + @Override + public void setColorFilter(ColorFilter cf) { + } + + @Override + public int getOpacity() { + return 0; + } + } } diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java index 8bc1081..e10070f 100644 --- a/core/java/com/android/internal/widget/ActionBarContextView.java +++ b/core/java/com/android/internal/widget/ActionBarContextView.java @@ -16,8 +16,8 @@ package com.android.internal.widget; import com.android.internal.R; -import com.android.internal.view.menu.ActionMenuPresenter; -import com.android.internal.view.menu.ActionMenuView; +import android.widget.ActionMenuPresenter; +import android.widget.ActionMenuView; import com.android.internal.view.menu.MenuBuilder; import android.animation.Animator; @@ -25,7 +25,6 @@ import android.animation.Animator.AnimatorListener; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.Context; -import android.content.res.Configuration; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.text.TextUtils; @@ -74,10 +73,16 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi this(context, attrs, com.android.internal.R.attr.actionModeStyle); } - public ActionBarContextView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ActionMode, defStyle, 0); + public ActionBarContextView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public ActionBarContextView( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + final TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.ActionMode, defStyleAttr, defStyleRes); setBackgroundDrawable(a.getDrawable( com.android.internal.R.styleable.ActionMode_background)); mTitleStyleRes = a.getResourceId( diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java index 5469b63..c957b67 100644 --- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java +++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java @@ -20,6 +20,7 @@ import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.os.Build; import android.view.ViewGroup; +import android.view.WindowInsets; import com.android.internal.app.ActionBarImpl; import android.content.Context; @@ -96,7 +97,7 @@ public class ActionBarOverlayLayout extends ViewGroup { if (mLastSystemUiVisibility != 0) { int newVis = mLastSystemUiVisibility; onWindowSystemUiVisibilityChanged(newVis); - requestFitSystemWindows(); + requestApplyInsets(); } } } @@ -152,7 +153,7 @@ public class ActionBarOverlayLayout extends ViewGroup { } if ((diff&SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) { if (mActionBar != null) { - requestFitSystemWindows(); + requestApplyInsets(); } } } @@ -190,19 +191,20 @@ public class ActionBarOverlayLayout extends ViewGroup { } @Override - protected boolean fitSystemWindows(Rect insets) { + public WindowInsets onApplyWindowInsets(WindowInsets insets) { pullChildren(); final int vis = getWindowSystemUiVisibility(); final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0; + final Rect systemInsets = insets.getSystemWindowInsets(); // The top and bottom action bars are always within the content area. - boolean changed = applyInsets(mActionBarTop, insets, true, true, false, true); + boolean changed = applyInsets(mActionBarTop, systemInsets, true, true, false, true); if (mActionBarBottom != null) { - changed |= applyInsets(mActionBarBottom, insets, true, false, true, true); + changed |= applyInsets(mActionBarBottom, systemInsets, true, false, true, true); } - mBaseInnerInsets.set(insets); + mBaseInnerInsets.set(systemInsets); computeFitSystemWindows(mBaseInnerInsets, mBaseContentInsets); if (!mLastBaseContentInsets.equals(mBaseContentInsets)) { changed = true; @@ -215,9 +217,9 @@ public class ActionBarOverlayLayout extends ViewGroup { // We don't do any more at this point. To correctly compute the content/inner // insets in all cases, we need to know the measured size of the various action - // bar elements. fitSystemWindows() happens before the measure pass, so we can't + // bar elements. onApplyWindowInsets() happens before the measure pass, so we can't // do that here. Instead we will take this up in onMeasure(). - return true; + return WindowInsets.EMPTY; } @Override @@ -321,7 +323,7 @@ public class ActionBarOverlayLayout extends ViewGroup { // the app's fitSystemWindows(). We do this before measuring the content // view to keep the same semantics as the normal fitSystemWindows() call. mLastInnerInsets.set(mInnerInsets); - super.fitSystemWindows(mInnerInsets); + mContent.dispatchApplyWindowInsets(new WindowInsets(mInnerInsets)); } measureChildWithMargins(mContent, widthMeasureSpec, 0, heightMeasureSpec, 0); diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java index 786f5cf..1273c4d 100644 --- a/core/java/com/android/internal/widget/ActionBarView.java +++ b/core/java/com/android/internal/widget/ActionBarView.java @@ -41,6 +41,8 @@ import android.view.ViewGroup; import android.view.ViewParent; import android.view.Window; import android.view.accessibility.AccessibilityEvent; +import android.widget.ActionMenuPresenter; +import android.widget.ActionMenuView; import android.widget.AdapterView; import android.widget.FrameLayout; import android.widget.ImageView; @@ -52,8 +54,6 @@ import android.widget.TextView; import com.android.internal.R; import com.android.internal.transition.ActionBarTransition; import com.android.internal.view.menu.ActionMenuItem; -import com.android.internal.view.menu.ActionMenuPresenter; -import com.android.internal.view.menu.ActionMenuView; import com.android.internal.view.menu.MenuBuilder; import com.android.internal.view.menu.MenuItemImpl; import com.android.internal.view.menu.MenuPresenter; @@ -430,6 +430,7 @@ public class ActionBarView extends AbsActionBarView { mActionMenuPresenter.setItemLimit(Integer.MAX_VALUE); // Span the whole width layoutParams.width = LayoutParams.MATCH_PARENT; + layoutParams.height = mContentHeight; configPresenters(builder); menuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this); if (mSplitView != null) { @@ -696,7 +697,7 @@ public class ActionBarView extends AbsActionBarView { } public void setIcon(int resId) { - setIcon(resId != 0 ? mContext.getResources().getDrawable(resId) : null); + setIcon(resId != 0 ? mContext.getDrawable(resId) : null); } public boolean hasIcon() { @@ -711,7 +712,7 @@ public class ActionBarView extends AbsActionBarView { } public void setLogo(int resId) { - setLogo(resId != 0 ? mContext.getResources().getDrawable(resId) : null); + setLogo(resId != 0 ? mContext.getDrawable(resId) : null); } public boolean hasLogo() { @@ -1416,7 +1417,7 @@ public class ActionBarView extends AbsActionBarView { public void setUpIndicator(int resId) { mUpIndicatorRes = resId; - mUpView.setImageDrawable(resId != 0 ? getResources().getDrawable(resId) : null); + mUpView.setImageDrawable(resId != 0 ? getContext().getDrawable(resId) : null); } @Override diff --git a/core/java/com/android/internal/widget/DialogTitle.java b/core/java/com/android/internal/widget/DialogTitle.java index b86c438..7ea3d6b 100644 --- a/core/java/com/android/internal/widget/DialogTitle.java +++ b/core/java/com/android/internal/widget/DialogTitle.java @@ -28,10 +28,13 @@ import android.widget.TextView; * the text to the available space. */ public class DialogTitle extends TextView { - - public DialogTitle(Context context, AttributeSet attrs, - int defStyle) { - super(context, attrs, defStyle); + + public DialogTitle(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public DialogTitle(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); } public DialogTitle(Context context, AttributeSet attrs) { diff --git a/core/java/com/android/internal/widget/FaceUnlockView.java b/core/java/com/android/internal/widget/FaceUnlockView.java index e3c1247..121e601 100644 --- a/core/java/com/android/internal/widget/FaceUnlockView.java +++ b/core/java/com/android/internal/widget/FaceUnlockView.java @@ -18,8 +18,6 @@ package com.android.internal.widget; import android.content.Context; import android.util.AttributeSet; -import android.util.Log; -import android.view.View; import android.widget.RelativeLayout; public class FaceUnlockView extends RelativeLayout { diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 8602260..2d79491 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -24,13 +24,13 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; -import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.os.storage.IMountService; +import android.os.storage.StorageManager; import android.provider.Settings; import android.telephony.TelephonyManager; import android.text.TextUtils; @@ -499,6 +499,13 @@ public class LockPatternUtils { getLockSettings().setLockPattern(patternToString(pattern), getCurrentOrCallingUserId()); DevicePolicyManager dpm = getDevicePolicyManager(); if (pattern != null) { + + int userHandle = getCurrentOrCallingUserId(); + if (userHandle == UserHandle.USER_OWNER) { + String stringPattern = patternToString(pattern); + updateEncryptionPassword(StorageManager.CRYPT_TYPE_PATTERN, stringPattern); + } + setBoolean(PATTERN_EVER_CHOSEN_KEY, true); if (!isFallback) { deleteGallery(); @@ -566,7 +573,7 @@ public class LockPatternUtils { } /** Update the encryption password if it is enabled **/ - private void updateEncryptionPassword(String password) { + private void updateEncryptionPassword(int type, String password) { DevicePolicyManager dpm = getDevicePolicyManager(); if (dpm.getStorageEncryptionStatus(getCurrentOrCallingUserId()) != DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE) { @@ -581,7 +588,7 @@ public class LockPatternUtils { IMountService mountService = IMountService.Stub.asInterface(service); try { - mountService.changeEncryptionPassword(password); + mountService.changeEncryptionPassword(type, password); } catch (RemoteException e) { Log.e(TAG, "Error changing encryption password", e); } @@ -624,12 +631,15 @@ public class LockPatternUtils { getLockSettings().setLockPassword(password, userHandle); DevicePolicyManager dpm = getDevicePolicyManager(); if (password != null) { + int computedQuality = computePasswordQuality(password); + if (userHandle == UserHandle.USER_OWNER) { // Update the encryption password. - updateEncryptionPassword(password); + int type = computedQuality == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC + ? StorageManager.CRYPT_TYPE_PIN : StorageManager.CRYPT_TYPE_PASSWORD; + updateEncryptionPassword(type, password); } - int computedQuality = computePasswordQuality(password); if (!isFallback) { deleteGallery(); setLong(PASSWORD_TYPE_KEY, Math.max(quality, computedQuality), userHandle); @@ -676,8 +686,7 @@ public class LockPatternUtils { 0, 0, 0, 0, 0, 0, 0, userHandle); } // Add the password to the password history. We assume all - // password - // hashes have the same length for simplicity of implementation. + // password hashes have the same length for simplicity of implementation. String passwordHistory = getString(PASSWORD_HISTORY_KEY, userHandle); if (passwordHistory == null) { passwordHistory = new String(); @@ -696,6 +705,11 @@ public class LockPatternUtils { } setString(PASSWORD_HISTORY_KEY, passwordHistory, userHandle); } else { + if (userHandle == UserHandle.USER_OWNER) { + // Update the encryption password. + updateEncryptionPassword(StorageManager.CRYPT_TYPE_DEFAULT, password); + } + dpm.setActivePasswordState( DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0, 0, userHandle); @@ -1201,7 +1215,7 @@ public class LockPatternUtils { private void setLong(String secureSettingKey, long value, int userHandle) { try { - getLockSettings().setLong(secureSettingKey, value, getCurrentOrCallingUserId()); + getLockSettings().setLong(secureSettingKey, value, userHandle); } catch (RemoteException re) { // What can we do? Log.e(TAG, "Couldn't write long " + secureSettingKey + re); diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java index b066d70..26cb4e5 100644 --- a/core/java/com/android/internal/widget/LockPatternView.java +++ b/core/java/com/android/internal/widget/LockPatternView.java @@ -868,12 +868,10 @@ public class LockPatternView extends View { // TODO: the path should be created and cached every time we hit-detect a cell // only the last segment of the path should be computed here - // draw the path of the pattern (unless the user is in progress, and - // we are in stealth mode) - final boolean drawPath = (!mInStealthMode || mPatternDisplayMode == DisplayMode.Wrong); + // draw the path of the pattern (unless we are in stealth mode) + final boolean drawPath = !mInStealthMode; - // draw the arrows associated with the path (unless the user is in progress, and - // we are in stealth mode) + // draw the arrows associated with the path (unless we are in stealth mode) boolean oldFlag = (mPaint.getFlags() & Paint.FILTER_BITMAP_FLAG) != 0; mPaint.setFilterBitmap(true); // draw with higher quality since we render with transforms if (drawPath) { @@ -974,7 +972,7 @@ public class LockPatternView extends View { Bitmap outerCircle; Bitmap innerCircle; - if (!partOfPattern || (mInStealthMode && mPatternDisplayMode != DisplayMode.Wrong)) { + if (!partOfPattern || mInStealthMode) { // unselected circle outerCircle = mBitmapCircleDefault; innerCircle = mBitmapBtnDefault; diff --git a/core/java/com/android/internal/widget/PasswordEntryKeyboard.java b/core/java/com/android/internal/widget/PasswordEntryKeyboard.java index 3c01c69..7483e75 100644 --- a/core/java/com/android/internal/widget/PasswordEntryKeyboard.java +++ b/core/java/com/android/internal/widget/PasswordEntryKeyboard.java @@ -16,7 +16,6 @@ package com.android.internal.widget; -import java.util.Locale; import android.content.Context; import android.content.res.Resources; import android.content.res.XmlResourceParser; @@ -73,8 +72,8 @@ public class PasswordEntryKeyboard extends Keyboard { private void init(Context context) { final Resources res = context.getResources(); - mShiftIcon = res.getDrawable(R.drawable.sym_keyboard_shift); - mShiftLockIcon = res.getDrawable(R.drawable.sym_keyboard_shift_locked); + mShiftIcon = context.getDrawable(R.drawable.sym_keyboard_shift); + mShiftLockIcon = context.getDrawable(R.drawable.sym_keyboard_shift_locked); sSpacebarVerticalCorrection = res.getDimensionPixelOffset( R.dimen.password_keyboard_spacebar_vertical_correction); } diff --git a/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java b/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java index a3df291..b2c9dc5 100644 --- a/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java +++ b/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java @@ -21,9 +21,7 @@ import android.content.res.Resources; import android.inputmethodservice.Keyboard; import android.inputmethodservice.KeyboardView; import android.inputmethodservice.KeyboardView.OnKeyboardActionListener; -import android.os.Handler; import android.os.SystemClock; -import android.os.Vibrator; import android.provider.Settings; import android.util.Log; import android.view.HapticFeedbackConstants; diff --git a/core/java/com/android/internal/widget/PasswordEntryKeyboardView.java b/core/java/com/android/internal/widget/PasswordEntryKeyboardView.java index b37adff..d27346b 100644 --- a/core/java/com/android/internal/widget/PasswordEntryKeyboardView.java +++ b/core/java/com/android/internal/widget/PasswordEntryKeyboardView.java @@ -29,11 +29,16 @@ public class PasswordEntryKeyboardView extends KeyboardView { static final int KEYCODE_NEXT_LANGUAGE = -104; public PasswordEntryKeyboardView(Context context, AttributeSet attrs) { - super(context, attrs); + this(context, attrs, 0); } - public PasswordEntryKeyboardView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public PasswordEntryKeyboardView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public PasswordEntryKeyboardView( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); } @Override diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java index d82831f..e339c44 100644 --- a/core/java/com/android/internal/widget/PointerLocationView.java +++ b/core/java/com/android/internal/widget/PointerLocationView.java @@ -31,11 +31,13 @@ import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; +import android.view.WindowManagerPolicy.PointerEventListener; import android.view.MotionEvent.PointerCoords; import java.util.ArrayList; -public class PointerLocationView extends View implements InputDeviceListener { +public class PointerLocationView extends View implements InputDeviceListener, + PointerEventListener { private static final String TAG = "Pointer"; // The system property key used to specify an alternate velocity tracker strategy @@ -520,7 +522,8 @@ public class PointerLocationView extends View implements InputDeviceListener { .toString()); } - public void addPointerEvent(MotionEvent event) { + @Override + public void onPointerEvent(MotionEvent event) { final int action = event.getAction(); int NP = mPointers.size(); @@ -648,7 +651,7 @@ public class PointerLocationView extends View implements InputDeviceListener { @Override public boolean onTouchEvent(MotionEvent event) { - addPointerEvent(event); + onPointerEvent(event); if (event.getAction() == MotionEvent.ACTION_DOWN && !isFocused()) { requestFocus(); @@ -660,7 +663,7 @@ public class PointerLocationView extends View implements InputDeviceListener { public boolean onGenericMotionEvent(MotionEvent event) { final int source = event.getSource(); if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) { - addPointerEvent(event); + onPointerEvent(event); } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { logMotionEvent("Joystick", event); } else if ((source & InputDevice.SOURCE_CLASS_POSITION) != 0) { diff --git a/core/java/com/android/internal/widget/RotarySelector.java b/core/java/com/android/internal/widget/RotarySelector.java index 4e405f4..f856027 100644 --- a/core/java/com/android/internal/widget/RotarySelector.java +++ b/core/java/com/android/internal/widget/RotarySelector.java @@ -24,7 +24,6 @@ import android.graphics.Paint; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; -import android.graphics.drawable.Drawable; import android.os.UserHandle; import android.os.Vibrator; import android.provider.Settings; diff --git a/core/java/com/android/internal/widget/ScrollingTabContainerView.java b/core/java/com/android/internal/widget/ScrollingTabContainerView.java index fa29e6e..d6bd1d6 100644 --- a/core/java/com/android/internal/widget/ScrollingTabContainerView.java +++ b/core/java/com/android/internal/widget/ScrollingTabContainerView.java @@ -23,7 +23,6 @@ import android.animation.TimeInterpolator; import android.app.ActionBar; import android.content.Context; import android.content.res.Configuration; -import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.text.TextUtils; import android.text.TextUtils.TruncateAt; diff --git a/core/java/com/android/internal/widget/SizeAdaptiveLayout.java b/core/java/com/android/internal/widget/SizeAdaptiveLayout.java index ba113a3..961e471 100644 --- a/core/java/com/android/internal/widget/SizeAdaptiveLayout.java +++ b/core/java/com/android/internal/widget/SizeAdaptiveLayout.java @@ -79,17 +79,20 @@ public class SizeAdaptiveLayout extends ViewGroup { private int mModestyPanelTop; public SizeAdaptiveLayout(Context context) { - super(context); - initialize(); + this(context, null); } public SizeAdaptiveLayout(Context context, AttributeSet attrs) { - super(context, attrs); - initialize(); + this(context, attrs, 0); + } + + public SizeAdaptiveLayout(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); } - public SizeAdaptiveLayout(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public SizeAdaptiveLayout( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); initialize(); } @@ -151,6 +154,10 @@ public class SizeAdaptiveLayout extends ViewGroup { if (DEBUG) Log.d(TAG, this + " measure spec: " + MeasureSpec.toString(heightMeasureSpec)); View model = selectActiveChild(heightMeasureSpec); + if (model == null) { + setMeasuredDimension(0, 0); + return; + } SizeAdaptiveLayout.LayoutParams lp = (SizeAdaptiveLayout.LayoutParams) model.getLayoutParams(); if (DEBUG) Log.d(TAG, "active min: " + lp.minHeight + " max: " + lp.maxHeight); @@ -239,6 +246,8 @@ public class SizeAdaptiveLayout extends ViewGroup { int measureSpec = View.MeasureSpec.makeMeasureSpec(bottom - top, View.MeasureSpec.EXACTLY); mActiveChild = selectActiveChild(measureSpec); + if (mActiveChild == null) return; + mActiveChild.setVisibility(View.VISIBLE); if (mLastActive != mActiveChild && mLastActive != null) { diff --git a/core/java/com/android/internal/widget/SubtitleView.java b/core/java/com/android/internal/widget/SubtitleView.java index 071193c..117463a 100644 --- a/core/java/com/android/internal/widget/SubtitleView.java +++ b/core/java/com/android/internal/widget/SubtitleView.java @@ -18,7 +18,6 @@ package com.android.internal.widget; import android.content.ContentResolver; import android.content.Context; -import android.content.res.Resources.Theme; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Canvas; @@ -33,7 +32,6 @@ import android.text.StaticLayout; import android.text.TextPaint; import android.util.AttributeSet; import android.util.DisplayMetrics; -import android.util.TypedValue; import android.view.View; import android.view.accessibility.CaptioningManager.CaptionStyle; @@ -41,6 +39,12 @@ public class SubtitleView extends View { // Ratio of inner padding to font size. private static final float INNER_PADDING_RATIO = 0.125f; + /** Color used for the shadowed edge of a bevel. */ + private static final int COLOR_BEVEL_DARK = 0x80000000; + + /** Color used for the illuminated edge of a bevel. */ + private static final int COLOR_BEVEL_LIGHT = 0x80FFFFFF; + // Styled dimensions. private final float mCornerRadius; private final float mOutlineWidth; @@ -79,12 +83,15 @@ public class SubtitleView extends View { this(context, attrs, 0); } - public SubtitleView(Context context, AttributeSet attrs, int defStyle) { + public SubtitleView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public SubtitleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs); - final Theme theme = context.getTheme(); - final TypedArray a = theme.obtainStyledAttributes( - attrs, android.R.styleable.TextView, defStyle, 0); + final TypedArray a = context.obtainStyledAttributes( + attrs, android.R.styleable.TextView, defStyleAttr, defStyleRes); CharSequence text = ""; int textSize = 15; @@ -112,7 +119,6 @@ public class SubtitleView extends View { // Set up density-dependent properties. // TODO: Move these to a default style. final Resources res = getContext().getResources(); - final DisplayMetrics m = res.getDisplayMetrics(); mCornerRadius = res.getDimensionPixelSize(com.android.internal.R.dimen.subtitle_corner_radius); mOutlineWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.subtitle_outline_width); mShadowRadius = res.getDimensionPixelSize(com.android.internal.R.dimen.subtitle_shadow_radius); @@ -311,7 +317,8 @@ public class SubtitleView extends View { } } - if (mEdgeType == CaptionStyle.EDGE_TYPE_OUTLINE) { + final int edgeType = mEdgeType; + if (edgeType == CaptionStyle.EDGE_TYPE_OUTLINE) { textPaint.setStrokeJoin(Join.ROUND); textPaint.setStrokeWidth(mOutlineWidth); textPaint.setColor(mEdgeColor); @@ -320,8 +327,24 @@ public class SubtitleView extends View { for (int i = 0; i < lineCount; i++) { layout.drawText(c, i, i); } - } else if (mEdgeType == CaptionStyle.EDGE_TYPE_DROP_SHADOW) { + } else if (edgeType == CaptionStyle.EDGE_TYPE_DROP_SHADOW) { textPaint.setShadowLayer(mShadowRadius, mShadowOffsetX, mShadowOffsetY, mEdgeColor); + } else if (edgeType == CaptionStyle.EDGE_TYPE_RAISED + || edgeType == CaptionStyle.EDGE_TYPE_DEPRESSED) { + final boolean raised = edgeType == CaptionStyle.EDGE_TYPE_RAISED; + final int colorUp = raised ? Color.WHITE : mEdgeColor; + final int colorDown = raised ? mEdgeColor : Color.WHITE; + final float offset = mShadowRadius / 2f; + + textPaint.setColor(mForegroundColor); + textPaint.setStyle(Style.FILL); + textPaint.setShadowLayer(mShadowRadius, -offset, -offset, colorUp); + + for (int i = 0; i < lineCount; i++) { + layout.drawText(c, i, i); + } + + textPaint.setShadowLayer(mShadowRadius, offset, offset, colorDown); } textPaint.setColor(mForegroundColor); diff --git a/core/java/com/android/internal/widget/TextProgressBar.java b/core/java/com/android/internal/widget/TextProgressBar.java index e898aa4..7ca07d4 100644 --- a/core/java/com/android/internal/widget/TextProgressBar.java +++ b/core/java/com/android/internal/widget/TextProgressBar.java @@ -19,7 +19,6 @@ package com.android.internal.widget; import android.content.Context; import android.os.SystemClock; import android.util.AttributeSet; -import android.util.Log; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; @@ -59,9 +58,13 @@ public class TextProgressBar extends RelativeLayout implements OnChronometerTick boolean mChronometerFollow = false; int mChronometerGravity = Gravity.NO_GRAVITY; + + public TextProgressBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } - public TextProgressBar(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public TextProgressBar(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); } public TextProgressBar(Context context, AttributeSet attrs) { diff --git a/core/java/com/android/internal/widget/WaveView.java b/core/java/com/android/internal/widget/WaveView.java index d33d50c..0c5993b 100644 --- a/core/java/com/android/internal/widget/WaveView.java +++ b/core/java/com/android/internal/widget/WaveView.java @@ -28,7 +28,6 @@ import android.graphics.drawable.BitmapDrawable; import android.os.UserHandle; import android.os.Vibrator; import android.provider.Settings; -import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; diff --git a/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java b/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java index cd1ccd3..93ea5b3 100644 --- a/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java +++ b/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java @@ -234,7 +234,7 @@ public class GlowPadView extends View { mMagneticTargets = a.getBoolean(R.styleable.GlowPadView_magneticTargets, mMagneticTargets); int pointId = getResourceId(a, R.styleable.GlowPadView_pointDrawable); - Drawable pointDrawable = pointId != 0 ? res.getDrawable(pointId) : null; + Drawable pointDrawable = pointId != 0 ? context.getDrawable(pointId) : null; mGlowRadius = a.getDimension(R.styleable.GlowPadView_glowRadius, 0.0f); TypedValue outValue = new TypedValue(); diff --git a/core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java b/core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java index 16bec16..5a4c441 100644 --- a/core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java +++ b/core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java @@ -18,7 +18,6 @@ package com.android.internal.widget.multiwaveview; import android.content.res.Resources; import android.graphics.Canvas; -import android.graphics.ColorFilter; import android.graphics.drawable.Drawable; import android.graphics.drawable.StateListDrawable; import android.util.Log; |